Warum eine effektive parallele Programmierung eine skalierbare Speicherzuordnung beinhalten muss

Multicore-Prozessor? Ja.

Programm schreiben, um parallel zu laufen? Ja.

Haben Sie daran gedacht, einen skalierbaren Speicherzuweiser zu verwenden? Nein? Dann lesen Sie weiter…

Nach meiner Erfahrung ist es ein oft übersehenes Element, um sicherzustellen, dass die „Speicherzuweisung“ für ein Programm für die Parallelität bereit ist, damit ein paralleles Programm gut funktioniert. Ich kann Ihnen einen unglaublich einfachen Weg zeigen, um festzustellen, ob dies ein Problem für ein kompiliertes Programm ist (C, C ++, Fortran usw.) - und wie Sie es beheben können.

Ein kritischer Teil jedes parallelen Programms ist die skalierbare Speicherzuweisung, die die Verwendung  neuer  sowie expliziter Aufrufe von  malloc, calloc oder realloc umfasst . Zu den Optionen gehören TBBmalloc (Intel Threading Building Blocks), jemalloc und tcmalloc. TBBmalloc verfügt über eine neuartige "Proxy" -Funktion, mit der Sie jedes kompilierte Programm in weniger als 5 Minuten ausprobieren können.

Die Leistungsvorteile der Verwendung eines skalierbaren Speicherzuordners sind erheblich. TBBmalloc gehörte zu den ersten weit verbreiteten skalierbaren Speicherzuordnungen, nicht zuletzt, weil es mit TBB kostenlos geliefert wurde, um die Bedeutung der Einbeziehung von Überlegungen zur Speicherzuweisung in jedes parallele Programm hervorzuheben. Es ist bis heute äußerst beliebt und immer noch einer der besten skalierbaren Speicherzuordnungen auf dem Markt.

Eine einfache Lösung ohne Codeänderungen

Mit den Proxy-Methoden können wir new / delete und malloc / calloc / realloc / free / etc. Global ersetzen . Routinen mit einer dynamischen Technik zum Ersetzen der Speicherschnittstelle. Diese automatische Methode zum Ersetzen der Standardfunktionen für die dynamische Speicherzuweisung ist bei weitem die beliebteste Methode zur Verwendung von TBBmalloc. Es ist einfach und ausreichend für die meisten Programme.

Die Details des auf jedem Betriebssystem verwendeten Mechanismus variieren etwas, aber der Nettoeffekt ist überall gleich.

Wir beginnen unsere 5-minütige Testversion mit dem Herunterladen und Installieren von Threading-Bausteinen (kostenlos von //threadingbuildingblocks.org; sie sind auch Teil der Intel Parallel Studio-Produkte).

Verwenden Sie Proxy unter Linux

Unter Linux können wir den Austausch entweder durch Laden der Proxy-Bibliothek zum Laden des Programms mithilfe der Umgebungsvariablen LD_PRELOAD (ohne Änderung der ausführbaren Datei) oder durch Verknüpfen der ausführbaren Hauptdatei mit der Proxy-Bibliothek ( -ltbbmalloc_proxy ) durchführen. Der Linux-Programmlader muss in der Lage sein, die Proxy-Bibliothek und die skalierbare Speicherzuweisungsbibliothek zum Zeitpunkt des Programmladens zu finden. Dazu können wir das Verzeichnis mit den Bibliotheken in die Umgebungsvariable LD_LIBRARY_PATH aufnehmen oder zu /etc/ld.so.conf hinzufügen .

Versuchen Sie Folgendes:

Zeit ./a.out (oder wie auch immer unser Programm heißt)

export LD_PRELOAD = libtbbmalloc_proxy.so.2

Zeit ./a.out (oder wie auch immer unser Programm heißt)

Verwenden Sie Proxy unter macOS

Unter macOS können wir das Ersetzen entweder durch Laden der Proxy-Bibliothek zum Laden des Programms mithilfe der Umgebungsvariablen DYLD_INSERT_LIBRARIES (ohne Änderung der ausführbaren Datei) oder durch Verknüpfen der ausführbaren Hauptdatei mit der Proxy-Bibliothek ( -ltbbmalloc_proxy ) durchführen. Der macOS-Programmlader muss in der Lage sein, die Proxy-Bibliothek und die skalierbare Speicherzuweisungsbibliothek zum Zeitpunkt des Programmladens zu finden. Dazu können wir das Verzeichnis mit den Bibliotheken in die Umgebungsvariable DYLD_LIBRARY_PATH aufnehmen .

Versuchen Sie Folgendes:

Zeit ./a.out (oder wie auch immer unser Programm heißt)

export DYLD_INSERT_LIBRARIES = $ TBBROOT / lib / libtbbmalloc_proxy.dylib

Zeit ./a.out (oder wie auch immer unser Programm heißt)

Verwenden Sie Proxy unter Windows

Unter Windows müssen wir unsere ausführbare Datei ändern. Wir können entweder das Laden der Proxy-Bibliothek erzwingen, indem wir in unserem Quellcode ein #include "tbb / tbbmalloc_proxy.h" hinzufügen oder beim Erstellen der ausführbaren Datei bestimmte Linker-Optionen verwenden:

Für win32:

            tbbmalloc_proxy.lib / INCLUDE: "___ TBB_malloc_proxy"

Für win64:

            tbbmalloc_proxy.lib / INCLUDE: "__ TBB_malloc_proxy"

Der Windows-Programmlader muss in der Lage sein, die Proxy-Bibliothek und die skalierbare Speicherzuweisungsbibliothek zum Zeitpunkt des Programmladens zu finden. Dazu können wir das Verzeichnis mit den Bibliotheken in die Umgebungsvariable PATH aufnehmen . Probieren Sie es aus, indem Sie den Visual Studio-Leistungsprofiler verwenden, um das Programm mit und ohne Include- oder Link-Option zeitlich zu steuern.

Testen der Verwendung unserer Proxy-Bibliothek mit einem kleinen Programm

Ich ermutige Sie, es mit Ihrem eigenen Programm wie oben beschrieben zu versuchen. Führen Sie es mit und ohne Proxy aus und sehen Sie, welchen Nutzen Ihre Anwendung hat. Anwendungen mit viel Parallelität und vielen Speicherzuordnungen sehen oft 10-20% Boosts (ich habe auch einmal einen 400% Boost gesehen), während Programme mit wenig Parallelität oder wenigen Allokationen möglicherweise überhaupt keine Wirkung sehen. Die zuvor beschriebenen Schnelltests mit der Proxy-Bibliothek zeigen Ihnen, in welcher Kategorie sich Ihre Anwendung befindet.

Ich habe auch ein kurzes Programm geschrieben, um die Auswirkungen zu veranschaulichen und auf einfache Weise zu überprüfen, ob die Dinge installiert sind und wie erwartet funktionieren. Wir können die Proxy-Bibliothek mit einem einfachen Programm ausprobieren:

#einschließen

#include "tbb / tbb.h"

Verwenden des Namespace tbb;

const int N = 1000000;

int main () {

doppelt * a [N];

parallel_for (0, N-1, [&] (int i) {a [i] = neues Doppel;});

parallel_for (0, N-1, [&] (int i) {lösche ein [i];});

return 0;

}}

Mein Beispielprogramm verwendet viel Stapelspeicher, daher ist " ulimit –s unbegrenzt " (Linux / macOS) oder " / STACK: 10000000 " (Visual Studio: Eigenschaften> Konfigurationseigenschaften> Linker> System> Stapelreservegröße) wichtig um sofortige Abstürze zu vermeiden.

Nach dem Kompilieren sind hier die verschiedenen Möglichkeiten aufgeführt, wie ich mein kleines Programm ausgeführt habe, um die Geschwindigkeit mit und ohne Proxy-Bibliothek zu ermitteln.

Beim Ausführen und Timing von tbb_mem.cpp auf einer virtuellen Quadcore- Linux-Maschine sah ich Folgendes:

% time ./tbb_mem 

echte 0m0.160s

Benutzer 0m0.072s

sys 0m0.048s

%.

% exportLD_PRELOAD = $ TBBROOT / lib / libtbbmalloc_proxy.dylib

%.

% time ./tbb_mem 

echte 0m0.043s

Benutzer 0m0.048s

sys 0m0.028s

Beim Ausführen und Timing von tbb_mem.cpp auf einem Quadcore-iMac (macOS) sah ich Folgendes:

% time ./tbb_mem

echte 0m0.046s

Benutzer 0m0.078s

sys 0m0.053s

%.

% export DYLD_INSERT_LIBRARIES = $ TBBROOT / lib / libtbbmalloc_proxy.dylib

%.

% time ./tbb_mem 

echte 0m0.019s

Benutzer 0m0.032s

sys 0m0.009s

Unter Windows habe ich mit dem Visual Studio „Performance Profiler“ auf einem Quadcore-Intel NUC (Core i7) Zeiten von 94 ms ohne den skalierbaren Speicherprofiler und 50 ms damit gesehen (Hinzufügen von #include "tbb / tbbmalloc_proxy.h" zum Beispielprogramm). .

Überlegungen zur Zusammenstellung

Persönlich hatte ich kein Problem mit Compilern, die "Malloc-Optimierungen" durchführen, aber technisch würde ich vorschlagen, dass beim Kompilieren mit Programmen solche Compiler "Malloc-Optimierungen" deaktiviert werden sollten. Es kann ratsam sein, die Compiler-Dokumentation Ihres bevorzugten Compilers zu überprüfen. Bei Intel-Compilern oder gcc ist es beispielsweise am besten, die folgenden Flags zu übergeben:

-fno-builtin-malloc (unter Windows: / Qfno-builtin-malloc)

-fno-builtin-calloc (unter Windows: / Qfno-builtin-calloc)

-fno-builtin-realloc (unter Windows: / Qfno-builtin-realloc)

-fno-builtin-free (unter Windows: / Qfno-builtin-free)

Die Nichtverwendung dieser Flags verursacht möglicherweise kein Problem, aber es ist keine schlechte Idee, sicher zu sein.

Zusammenfassung

Die Verwendung eines skalierbaren Speicherzuordners ist ein wesentliches Element in jedem parallelen Programm. Ich habe gezeigt, dass TBBmalloc einfach injiziert werden kann, ohne dass Codeänderungen erforderlich sind (obwohl das Hinzufügen eines "Include" unter Windows meine bevorzugte Windows-Lösung ist). Möglicherweise sehen Sie eine schöne Beschleunigung mit nur 5 Minuten Arbeit, und Sie können sie problemlos auf mehrere Anwendungen anwenden. Unter Linux und MacOS können Sie möglicherweise sogar Programme beschleunigen, ohne den Quellcode zu haben!

Klicken Sie hier, um Ihre kostenlose 30-Tage-Testversion von Intel Parallel Studio XE herunterzuladen.