2016-07-21 19 views
2

Ich möchte mit Neon-Histogramm Statistik Code optimieren intrinsics.But ich nicht succeed.Here der C-Code ist getan:Wie kann man Histogrammstatistiken mit Neon-Intrinsics optimieren?

#define NUM (7*1024*1024) 
uint8 src_data[NUM]; 
uint32 histogram_result[256] = {0}; 
for (int i = 0; i < NUM; i++) 
{ 
    histogram_result[src_data[i]]++; 
} 

Historam Statistik mehr wie serielle processing.It ist schwierig, mit Neon intrinsics.Does zu optimieren Wer weiß wie man optimiert? Vielen Dank im Voraus.

+1

Es ist fast unmöglich, Histogramm mit jeder Art von SIMD, Neon enthalten zu optimieren. Die einzige nennenswerte Ausnahme ist AVX-512. –

+1

Wie Paul sagte, benötigt Ihr Algorithmus Sammelladungen und Scatter-Stores, die die meisten SIMD-Befehlssätze nicht bieten. –

Antwort

3

Sie können die Speicher nicht direkt vektorisieren, aber Sie können sie pipelieren, und Sie können die Adressberechnung auf 32-Bit-Plattformen (und in geringerem Maße auf 64-Bit-Plattformen) vektorisieren.

Das erste, was Sie tun wollen, was eigentlich nicht NEON benötigen, zu profitieren ist das Histogramm Array entrollen, so dass Sie mehr Daten im Flug auf einmal haben können:

#define NUM (7*1024*1024) 
uint8 src_data[NUM]; 
uint32 histogram_result[256][4] = {{0}}; 
for (int i = 0; i < NUM; i += 4) 
{ 
    uint32_t *p0 = &histogram_result[src_data[i + 0]][0]; 
    uint32_t *p1 = &histogram_result[src_data[i + 1]][1]; 
    uint32_t *p2 = &histogram_result[src_data[i + 2]][2]; 
    uint32_t *p3 = &histogram_result[src_data[i + 3]][3]; 
    uint32_t c0 = *p0; 
    uint32_t c1 = *p1; 
    uint32_t c2 = *p2; 
    uint32_t c3 = *p3; 
    *p0 = c0 + 1; 
    *p1 = c1 + 1; 
    *p2 = c2 + 1; 
    *p3 = c3 + 1; 
} 

for (int i = 0; i < 256; i++) 
{ 
    packed_result[i] = histogram_result[i][0] 
        + histogram_result[i][1] 
        + histogram_result[i][2] 
        + histogram_result[i][3]; 
} 

Hinweis dass p0 bis p3 niemals auf die gleiche Adresse zeigen kann, also ist das Neuordnen ihrer Lese- und Schreibvorgänge in Ordnung.

Daraus können Sie die Berechnung von p0 zu p3 mit intrinsics vektorisieren, und Sie können die Finalisierungsschleife vektorisieren.

Testen Sie es als erstes (weil ich nicht!). Dann können Sie mit der Strukturierung des Arrays als result[4][256] anstelle von result[256][4] oder mit einem kleineren oder größeren Unroll-Faktor experimentieren.

Anwendung einiger NEON intrinsics dazu:

uint32 histogram_result[256 * 4] = {0}; 
static const uint16_t offsets[] = { 0x000, 0x001, 0x002, 0x003, 
            0x000, 0x001, 0x002, 0x003 }; 
uint16x8_t voffs = vld1q_u16(offsets); 
for (int i = 0; i < NUM; i += 8) { 
    uint8x8_t p = vld1_u8(&src_data[i]); 
    uint16x8_t p16 = vshll_n_u8(p, 16); 
    p16 = vaddq_u16(p16, voffs); 
    uint32_t c0 = histogram_result[vget_lane_u16(p16, 0)]; 
    uint32_t c1 = histogram_result[vget_lane_u16(p16, 1)]; 
    uint32_t c2 = histogram_result[vget_lane_u16(p16, 2)]; 
    uint32_t c3 = histogram_result[vget_lane_u16(p16, 3)]; 
    histogram_result[vget_lane_u16(p16, 0)] = c0 + 1; 
    c0 = histogram_result[vget_lane_u16(p16, 4)]; 
    histogram_result[vget_lane_u16(p16, 1)] = c1 + 1; 
    c1 = histogram_result[vget_lane_u16(p16, 5)]; 
    histogram_result[vget_lane_u16(p16, 2)] = c2 + 1; 
    c2 = histogram_result[vget_lane_u16(p16, 6)]; 
    histogram_result[vget_lane_u16(p16, 3)] = c3 + 1; 
    c3 = histogram_result[vget_lane_u16(p16, 7)]; 
    histogram_result[vget_lane_u16(p16, 4)] = c0 + 1; 
    histogram_result[vget_lane_u16(p16, 5)] = c1 + 1; 
    histogram_result[vget_lane_u16(p16, 6)] = c2 + 1; 
    histogram_result[vget_lane_u16(p16, 7)] = c3 + 1; 
} 

Mit dem Histogramm Array abgerollt x8 anstatt x4 möchten Sie vielleicht acht skalare Speicher anstelle von vier verwenden, aber Sie müssen bedenken, dass die acht Zählregister impliziert und acht Adreßregister, die mehr Register als 32-Bit-ARM hat (da Sie SP und PC nicht verwenden können).

Leider, mit Adressberechnung in den Händen von NEON intrinsics, ich denke, der Compiler kann nicht sicher begründen, wie es in der Lage sein, Reads und schreibt neu zu ordnen, so müssen Sie sie explizit neu anordnen und hoffe, dass Sie mache es auf die bestmögliche Weise.

+1

Ich mag diese Idee zu versuchen, Store-Forwarding-Verzögerungen für mehrere Inkremente des gleichen Eintrags zu reduzieren. Es ist jedoch problematisch, mehr Speicher zu bereinigen, und das gilt auch für die Nullsetzung. –

+0

'Ergebnis [4] [256]' ist viel schneller am Ende zu verarbeiten, da Sie nur vertikale SIMD-Operationen benötigen. Sie können die 4 (oder 3 oder 7) Partitionen für 4 SIMD-Elemente parallel summieren. Wenn Sie eine Menge von Eingabedaten verarbeiten, dann lohnt es sich, 'result [256] [4]' zu verwenden und eine horizontale Summe der N Partitionen für jeden Bucket zu erstellen, wenn Ihre Daten eine Verteilung haben, die zu besseres Cache-Verhalten während der Inkrementphase. z.B. Ein Lauf mit dem gleichen Eingabewert erzeugt 4 Zugriffe auf die gleiche Cachezeile anstatt auf 4 Cachezeilen mit einer Schrittweite von 256 * sizeof (int) '. –

+0

'können Sie die Berechnung von p0 zu p3 mit intrinsics vektorisieren. Nicht sinnvoll, ohne sammeln + streuen. Mit hocheffizienten AVX2-Erfassungen (z. B. auf Skylake) könnte es ein Gewinn sein, eine Sammelladung durchzuführen und dann manuell zu streuen, aber die Adressen in einem SIMD-Vektor zu berechnen und dann jedes Vektorelement in ein Ganzzahlregister zur Verwendung als Zeiger zu extrahieren teuer. Noch schlimmer, wenn Sie auch die inkrementierten Zähler in den Speicher extrahieren müssen.(Für x86 gibt es jedoch eine Anweisung ('pextrd'), die ein Vektorelement direkt in den Speicher extrahieren kann (mit einem sofortigen Elementindex)). –