Ich muss eine Liste von Zahlenkombinationen erstellen. Die Zahlen sind ziemlich klein, so dass ich byte
eher als verwenden kann int
. Es sind jedoch viele verschachtelte Schleifen erforderlich, um jede mögliche Kombination zu erhalten. Ich frage mich, ob es eine effizientere Möglichkeit gibt, das zu tun, wonach ich suche. Code ist bisher:
var data = new List<byte[]>();
for (byte a = 0; a < 2; a++)
for (byte b = 0; b < 3; b++)
for (byte c = 0; c < 4; c++)
for (byte d = 0; d < 3; d++)
for (byte e = 0; e < 4; e++)
for (byte f = 0; f < 3; f++)
for (byte g = 0; g < 3; g++)
for (byte h = 0; h < 4; h++)
for (byte i = 0; i < 2; i++)
for (byte j = 0; j < 4; j++)
for (byte k = 0; k < 4; k++)
for (byte l = 0; l < 3; l++)
for (byte m = 0; m < 4; m++)
{
data.Add(new [] {a, b, c, d, e, f, g, h, i, j, k, l, m});
}
Ich habe überlegt, so etwas wie ein zu verwenden BitArray
, bin mir aber nicht sicher, wie ich es integrieren könnte.
Alle Empfehlungen wäre sehr dankbar. Alternativ ist dies vielleicht der schnellste Weg, um das zu tun, was ich will?
BEARBEITEN Einige kurze Punkte (und Entschuldigung, ich habe diese nicht in den ursprünglichen Beitrag aufgenommen):
- Die Zahlen und deren Reihenfolge (2, 3, 4, 3, 4, 3, 3 usw.) sind sehr wichtig. Daher hilft die Verwendung einer Lösung wie dem Generieren von Permutationen mit LINQ nicht, da die Maxima in jeder 'Spalte' sind anders
- Ich bin kein Mathematiker, also entschuldige ich mich, wenn ich die Fachbegriffe wie "Permutationen" und "Kombinationen" nicht richtig verwende :)
- Ich tun müssen alle diese Kombinationen füllen auf einmal - ich kann nicht nur eine greifen oder eine andere basiert auf einem Index
- Die Verwendung
byte
ist schneller als die Verwendungint
, das garantiere ich . Bei der Speichernutzung ist es auch viel besser, mehr als 67 Millionen Bytes als Ints zu haben - Mein oberstes Ziel ist es, nach einer schnelleren Alternative zu verschachtelten Schleifen zu suchen.
- Ich habe überlegt, parallel zu programmieren, aber aufgrund der iterativen Natur dessen, was ich erreichen möchte, konnte ich keinen Weg finden, dies erfolgreich zu tun (auch nicht mit
ConcurrentBag
) - aber ich bin froh, dass ich mich als falsch erwiesen habe :)
FAZIT
Caramiriel hat eine gute Mikrooptimierung bereitgestellt, die einige Zeit in den Schleifen spart, daher habe ich diese Antwort als richtig markiert. Eric erwähnte auch, dass es schneller ist, die Liste vorab zuzuweisen. Aber zu diesem Zeitpunkt scheinen die verschachtelten Schleifen tatsächlich der schnellstmögliche Weg zu sein (deprimierend, ich weiß!).
Wenn Sie genau das ausprobieren möchten StopWatch
, womit ich ein Benchmarking durchgeführt habe , verwenden Sie 13 Schleifen, die bis zu 4 in jeder Schleife zählen - das ergibt ungefähr 67 m + Zeilen in der Liste. Auf meinem Computer (i5-3320M 2.6GHz) dauert die optimierte Version ca. 2.2s.
quelle
Antworten:
Sie können die Eigenschaften einer Struktur verwenden und die Struktur im Voraus zuweisen. Ich habe im folgenden Beispiel einige Ebenen abgeschnitten, aber ich bin sicher, dass Sie die Einzelheiten herausfinden können. Läuft ungefähr 5-6 mal schneller als das Original (Release-Modus).
Der Block:
Die Schleife:
Es ist schneller, weil nicht jedes Mal, wenn Sie es der Liste hinzufügen, eine neue Liste zugewiesen wird. Da diese Liste erstellt wird, muss auf jeden anderen Wert (a, b, c, d, e) verwiesen werden. Sie können davon ausgehen, dass jeder Wert innerhalb der Schleife nur einmal geändert wird, sodass wir ihn dafür optimieren können (Datenlokalität).
Lesen Sie auch die Kommentare für Nebenwirkungen.
Die Antwort wurde bearbeitet, um ein
T[]
anstelle von a zu verwendenList<T>
.quelle
List<T>.Add
Methode kopiert .List<T>.Add()
Methode über den Punkt hinausgeht, an dem sie gespeichert werden kann. Dadurch wird die Größe geändert (doppelte Größe), wodurch mehr als 2 GB Speicher benötigt werden. Versuchen Sie die Vorbelegung mithilfe der neuen Liste <ByteBlock> (maxPerLevel.Aggregate (1, (x, y) => x * y)), obwohl es bereits 'zufällig' ist, dass Sie einen vollständigen 2-GB-Block dieser Daten im Speicher benötigen. Beachten Sie auch, dass data.ToArray (); ist teuer, weil es zu diesem Zeitpunkt zweimal Elemente im Speicher hält. [umformuliert]Was Sie tun, ist zu zählen (mit variablem Radix, aber immer noch zu zählen).
Da Sie C # verwenden, möchten Sie vermutlich nicht mit nützlichen Speicherlayouts und Datenstrukturen spielen, mit denen Sie Ihren Code wirklich optimieren können.
Hier poste ich also etwas anderes, das vielleicht nicht zu Ihrem Fall passt, aber es ist erwähnenswert: Falls Sie tatsächlich spärlich auf die Liste zugreifen, hier eine Klasse, mit der Sie das i-te Element in linearer Zeit (eher) berechnen können als exponentiell wie die anderen Antworten)
Sie können diese Klasse auf diese Weise verwenden
Jetzt
c[i]
ist das gleiche wie Ihre Liste, nennen Sie esl
,l[i]
.Wie Sie sehen können, können Sie all diese Schleifen leicht vermeiden :), selbst wenn Sie die gesamte Liste vorab berechnen, da Sie einfach einen Carry-Ripple-Zähler implementieren können.
Zähler sind ein sehr studiertes Fach, ich rate dringend, nach Literatur zu suchen, wenn Sie das Gefühl haben.
quelle
Methode 1
Eine Möglichkeit, dies zu beschleunigen, besteht darin, die Kapazität anzugeben, wenn Sie diese weiterhin verwenden
List<byte[]>
möchten.Methode 2
Darüber hinaus können Sie
System.Array
direkt verwenden, um einen schnelleren Zugriff zu erhalten. Ich empfehle diesen Ansatz, wenn Ihre Frage darauf besteht, dass jedes Element im Voraus physisch im Speicher gespeichert wird.Methode 3
Alternativ können Sie die folgende Technik für eine kostengünstige Initialisierung verwenden, die nur spärlich für den Zugriff geeignet ist. Dies ist besonders günstig, wenn möglicherweise nur einige Elemente benötigt werden und die Bestimmung aller Elemente im Voraus als unnötig angesehen wird. Darüber hinaus können Techniken wie diese die einzig praktikable Option sein, wenn mit größeren Elementen gearbeitet wird, wenn der Speicher knapp wird.
Bei dieser Implementierung muss jedes Element beim Zugriff im Handumdrehen träge bestimmt werden. Dies kostet natürlich zusätzliche CPU, die beim Zugriff anfällt.
quelle
Auf meinem Computer werden dadurch die Kombinationen in 222 ms gegenüber 760 ms (die 13 für Schleifen) generiert:
quelle
Verwenden der Erweiterungsmethode unter http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
quelle
List verfügt intern über ein Array, in dem die Werte mit einer festen Länge gespeichert werden. Wenn Sie List.Add aufrufen, wird geprüft, ob genügend Speicherplatz vorhanden ist. Wenn das neue Element nicht hinzugefügt werden kann, wird ein neues Array mit einer größeren Größe erstellt. Kopieren Sie alle vorherigen Elemente und fügen Sie dann ein neues hinzu. Dies dauert einige Zyklen.
Da Sie die Anzahl der Elemente bereits kennen, können Sie eine Liste mit der richtigen Größe erstellen, die bereits viel schneller sein sollte.
Sie sind sich auch nicht sicher, wie Sie auf die Werte zugreifen, aber Sie können dieses Element erstellen und das Bild im Code speichern (das Laden von der Festplatte ist wahrscheinlich langsamer als das, was Sie jetzt tun. Wie oft lesen / schreiben Sie darauf Ding?
quelle
Hier ist ein anderer Weg, der nur 2 Schleifen benötigt. Die Idee ist, das erste Element zu erhöhen, und wenn diese Zahl überschritten wird, erhöhen Sie das nächste.
Anstatt die Daten anzuzeigen, können Sie currentValues.Clone verwenden und diese geklonte Version zu Ihrer Liste hinzufügen. Für mich lief das schneller als deine Version.
quelle
Alle Ihre Zahlen sind Kompilierungszeitkonstanten.
Was ist mit dem Abrollen aller Schleifen in einer Liste (mit Ihrem Programm zum Schreiben von Code):
Das sollte zumindest den Overhead der for-Schleifen (falls vorhanden) wegnehmen.
Ich kenne C # nicht allzu gut, aber es scheint einige Möglichkeiten zu geben, Objekte zu serialisieren. Was wäre, wenn Sie diese Liste gerade erstellt und in irgendeiner Form serialisiert hätten? Ich bin mir nicht sicher, ob die Deserialisierung schneller ist als das Erstellen der Liste und das Hinzufügen der Elemente.
quelle
Benötigen Sie das Ergebnis als Array von Arrays? Mit dem aktuellen Setup ist die Länge der inneren Arrays festgelegt und kann durch Strukturen ersetzt werden. Dies würde es ermöglichen, das gesamte Objekt als einen fortlaufenden Speicherblock zu reservieren und einen einfacheren Zugriff auf die Elemente zu ermöglichen (nicht sicher, wie Sie dieses Objekt später verwenden).
Der folgende Ansatz ist viel schneller (41 ms gegenüber 1071 ms für das Original auf meiner Box):
quelle
Was ist mit
Parallel.For()
der Ausführung? (Lob für die Strukturoptimierung an @Caramiriel ). Ich habe die Werte leicht geändert (a ist 5 statt 2), damit ich mehr Vertrauen in die Ergebnisse habe._join()
ist eine private Methode, definiert als:Auf meinem System läuft diese Version ungefähr sechsmal schneller (1,718 Sekunden gegenüber 0,266 Sekunden).
quelle
Parallel.For
und VS ist abgestürzt!Einige Ihrer Zahlen passen vollständig auf eine ganzzahlige Anzahl von Bits, sodass Sie sie mit der Nummer der oberen Ebene "packen" können:
Dies macht den Code natürlich weniger lesbar, aber Sie haben eine Schleife gespeichert. Dies kann jedes Mal erfolgen, wenn eine der Zahlen eine Zweierpotenz ist, in Ihrem Fall also sieben Mal.
quelle
Hier ist eine andere Lösung. Außerhalb von VS läuft es so schnell wie 437,5 ms, was 26% schneller ist als der ursprüngliche Code (593,7 auf meinem Computer):
quelle