Vor kurzem habe ich an einer Multithread-Implementierung einer Funktion zur Berechnung der Poisson-Verteilung (amath_pdist) gearbeitet. Ziel war es, die Arbeitslast auf mehrere Threads aufzuteilen, um die Leistung insbesondere bei großen Arrays zu verbessern. Anstatt jedoch die erwartete Beschleunigung zu erreichen, bemerkte ich mit zunehmender Größe des Arrays eine deutliche Verlangsamung.
Nach einigen Nachforschungen habe ich den Schuldigen entdeckt: falsches Teilen. In diesem Beitrag erkläre ich, was falsches Teilen ist, zeige den ursprünglichen Code, der das Problem verursacht, und teile die Korrekturen, die zu einer erheblichen Leistungsverbesserung geführt haben.
Falsches Teilen tritt auf, wenn mehrere Threads an verschiedenen Teilen eines gemeinsam genutzten Arrays arbeiten, sich ihre Daten jedoch in derselben Cache-Zeile befinden. Cache-Zeilen sind die kleinste Dateneinheit, die zwischen Speicher und CPU-Cache übertragen wird (normalerweise 64 Byte). Wenn ein Thread in einen Teil einer Cache-Zeile schreibt, macht er die Zeile für andere Threads ungültig – selbst wenn diese an logisch unabhängigen Daten arbeiten. Diese unnötige Ungültigmachung führt zu erheblichen Leistungseinbußen aufgrund des wiederholten Neuladens von Cache-Zeilen.
Hier ist eine vereinfachte Version meines Originalcodes:
void *calculate_pdist_segment(void *data) { struct pdist_segment *segment = (struct pdist_segment *)data; size_t interval_a = segment->interval_a, interval_b = segment->interval_b; double lambda = segment->lambda; int *d = segment->data; for (size_t i = interval_a; i < interval_b; i++) { segment->pdist[i] = pow(lambda, d[i]) * exp(-lambda) / tgamma(d[i] + 1); } return NULL; } double *amath_pdist(int *data, double lambda, size_t n_elements, size_t n_threads) { double *pdist = malloc(sizeof(double) * n_elements); pthread_t threads[n_threads]; struct pdist_segment segments[n_threads]; size_t step = n_elements / n_threads; for (size_t i = 0; i < n_threads; i++) { segments[i].data = data; segments[i].lambda = lambda; segments[i].pdist = pdist; segments[i].interval_a = step * i; segments[i].interval_b = (i == n_threads - 1) ? n_elements : (step * (i + 1)); pthread_create(&threads[i], NULL, calculate_pdist_segment, &segments[i]); } for (size_t i = 0; i < n_threads; i++) { pthread_join(threads[i], NULL); } return pdist; }
Im obigen Code:
Dieses Problem ließ sich bei größeren Arrays schlecht skalieren. Obwohl das Grenzproblem klein erscheinen mag, erhöhte die schiere Anzahl der Iterationen die Kosten für Cache-Ungültigmachungen, was zu unnötigem Overhead von Sekunden führte.
Um das Problem zu beheben, habe ich posix_memalign verwendet, um sicherzustellen, dass das pdist-Array an 64-Byte-Grenzen ausgerichtet war. Dies garantiert, dass Threads auf völlig unabhängigen Cache-Zeilen arbeiten, wodurch falsches Teilen vermieden wird.
Hier ist der aktualisierte Code:
double *amath_pdist(int *data, double lambda, size_t n_elements, size_t n_threads) { double *pdist; if (posix_memalign((void **)&pdist, 64, sizeof(double) * n_elements) != 0) { perror("Failed to allocate aligned memory"); return NULL; } pthread_t threads[n_threads]; struct pdist_segment segments[n_threads]; size_t step = n_elements / n_threads; for (size_t i = 0; i < n_threads; i++) { segments[i].data = data; segments[i].lambda = lambda; segments[i].pdist = pdist; segments[i].interval_a = step * i; segments[i].interval_b = (i == n_threads - 1) ? n_elements : (step * (i + 1)); pthread_create(&threads[i], NULL, calculate_pdist_segment, &segments[i]); } for (size_t i = 0; i < n_threads; i++) { pthread_join(threads[i], NULL); } return pdist; }
Ausgerichteter Speicher:
Kein Cache-Line-Sharing:
Verbesserte Cache-Effizienz:
Nach der Anwendung des Fixes sank die Laufzeit der Funktion amath_pdist erheblich. Für einen Datensatz, den ich testete, sank die Zeit der Wanduhr von 10,92 Sekunden auf 0,06 Sekunden.
Danke fürs Lesen!
Wer neugierig auf den Code ist, kann ihn hier finden
Das obige ist der detaillierte Inhalt vonVerstehen und Lösen falscher Freigaben in Multithread-Anwendungen anhand eines tatsächlichen Problems, das ich hatte. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!