2017-02-04 3 views
4

Ich möchte std::vector<int> mit Null mit openmp füllen. Wie geht das schnell?Parallel füllen std :: Vektor mit Null

Ich hörte, dass die Schleife über den Vektor, um jedes Element auf Null zu setzen, langsam war, und std::fill war viel schneller. Stimmt das jetzt noch?

Fastest way to reset every value of std::vector<int> to 0

Muss man die std::vector<int> manuell in Bereiche unterteilen, #pragma omp for Schleife über jedes Threads verwenden, und dann in der Schleife std::fill verwenden?

+0

Ja, es ist immer noch wahr. Und ja, wenn Sie OpenMP verwenden, müssen Sie die Jobs selbst zuweisen. Wie auch immer Sie es tun, wenn Sie sich darum kümmern, dass es schnell ist, sollten Sie es messen. Ich würde vorschlagen, die Leistung von 2 großen Jobs zuzuteilen (der Thread, auf den verwiesen wurde, benutzte 16 Ints in einem MMX-Register, dies ist wahrscheinlich die kleinste mögliche Jobgröße). Abhängig von der Länge des Vektors ist es möglicherweise einfacher, nur einen Thread einzufügen. Stellen Sie sicher, dass Sie den Kreuzungspunkt messen und finden. Dies ist ein Kommentar, weil er deine Frage nicht wirklich gut beantwortet. Es sind nur Gedanken, die du wahrscheinlich selbst gehabt hast. – OmnipotentEntity

+1

GCC 6.3 und Clang 3.9.0 kompilieren beide eine "Schleife und zuweisen 0 überall" und "std :: fill" in einen (Tail) Aufruf an "memset". Es ist nicht genau der gleiche Code, aber das schwere Heben ist das gleiche. – harold

+4

I * bet *, dass das Füllen des Vektors mit Null einen minimalen Teil Ihrer Zeit belegt. Mach dir keine Sorgen darüber, bis du Beweise dafür hast, dass dies der Problembereich ist. –

Antwort

3

Sie den Vektor in Stücke für jeden Thread aufspalten kann mit std::fill gefüllt werden:

#pragma omp parallel 
{ 
    auto tid = omp_get_thread_num(); 
    auto chunksize = v.size()/omp_get_num_threads(); 
    auto begin = v.begin() + chunksize * tid; 
    auto end = (tid == omp_get_num_threads() -1) ? v.end() : begin + chunksize); 
    std::fill(begin, end, 0); 
} 

Sie es weiter verbessern, kann chunksize zur nächsten Cache-Line/Speicherwortgröße (128 Byte = 32 int s durch Runden). Angenommen, dass v.data() ähnlich ausgerichtet ist. Auf diese Weise vermeiden Sie falsche Weitergabeprobleme.

Auf einem Haswell System mit zwei Sockeln und 24 Kernen bekomme ich eine Geschwindigkeit von 9x: 3.6s für 1 Thread, 0.4s für 24 Threads, 4.8B ints = ~ 48 GB/s, die Ergebnisse variieren ein wenig und das ist keine wissenschaftliche Analyse. Aber es ist nicht zu weit von der Speicherbandbreite des Systems entfernt.

Für allgemeine Leistung sollten Sie besorgt sein, Ihren Vektor nicht nur für diese Operation, sondern auch für weitere Operationen (sei es Lesen oder Schreiben) auf die gleiche Weise zu teilen, wenn möglich. Auf diese Weise erhöhen Sie die Wahrscheinlichkeit, dass sich die Daten tatsächlich im Cache befinden, wenn Sie sie benötigen, oder zumindest auf demselben NUMA-Knoten.

Seltsamerweise ist auf meinem System std::fill(..., 1); schneller als std::fill(..., 0) für einen einzelnen Thread, aber langsamer für 24 Threads. Beide mit gcc 6.1.0 und icc 17.0.1. Ich schätze, ich werde das in eine separate Frage schreiben.

+0

Bei a Ein kurzer Blick, der nicht die beste Aufteilung der Berechnung unter den Threads zu sein scheint, wenn die Größe des Vektors nicht durch die Anzahl der Threads teilbar ist. Der letzte Thread benötigt möglicherweise weniger Arbeit als andere Threads. Danke für das schöne Beispiel für 'omp parallel'. Ich weiß nicht, dass wir tid so verwenden können, und immer noch omp-Schleifen über alle Threads in meinem Code haben ... –

+0

Nein der letzte Thread bekommt mehr Arbeit, aber höchstens 'nthreads - 1' mehr, was ist unerheblich. Alternativ können Sie 'chunksize = (v.size() - 1)/nthreads + 1' verwenden, was etwas ausgewogener ist. Aber ich würde argumentieren (going go edit das), es ist eigentlich wichtiger, die Brocken auszurichten. – Zulan

+0

Was bedeutet das Ausrichten der Brocken? –