Xxhash: __uint128_t ist nicht auf 32-Bit portierbar

Erstellt am 7. März 2019  ·  6Kommentare  ·  Quelle: Cyan4973/xxHash

Mir ist aufgefallen, dass Sie __uint128_t zu xxh3 hinzugefügt haben. Das ist nicht auf 32-Bit portierbar; der Typ ist nur für 64-Bit definiert, und wie Sie unten sehen, aus einem ziemlich vernünftigen Grund.

Ich schlage folgendes vor:

static U64 mult128(U64 bot, U64 top) {
#if (SIZE_MAX > 0xFFFFFFFFULL) || defined(__SIZEOF_UINT128__) /* TODO: better detection */
    __uint128_t prod = (__uint128_t)bot * (__uint128_t)top;
    return prod + (prod >> 64);
#else 
    /* based off of LLVM's optimization of https://github.com/calccrypto/uint128_t's
     * operator* when both upper halves are zero.
     * There's probably a better way to do this, and if it weren't for the
     * mixing in the middle, it could be easily SIMD'd.  */
    U64 BD = (top & 0xFFFFFFFF) * (bot & 0xFFFFFFFF);
    U64 AC = (top >> 32)        * (bot >> 32);

    U64 BC = (top & 0xFFFFFFFF) * (bot >> 32);
    U64 AD = (top >> 32)        * (bot & 0xFFFFFFFF);

    U64 sum1 = (BC & 0xFFFFFFFF);
    U64 sum2 = (AD & 0xFFFFFFFF);

    sum1 += (BD >> 32);
    sum2 += sum1;

    sum1 = sum2 >> 32;
    sum2 <<= 32;

    sum1 += (BD & 0xFFFFFFFF);
    sum2 += (AC & 0xFFFFFFFF);

    sum1 += (BC >> 32);
    sum2 += (AD >> 32);

    sum1 += (AC & 0xFFFFFFFF00000000ULL);
    sum2 += sum1;

    printf("%llx\n", sum2);
    return sum2;
}
#endif 
}

Ich möchte jedoch erwähnen, dass GCC diesen Code nicht wirklich mag und ihn halb vektorisiert.
Ich probiere jedoch eine potenzielle SSE2/NEON32-Version aus, die möglicherweise schneller oder langsamer ist. Den ersten Teil habe ich von MSDN bekommen , aber der zweite Teil verwirrt mich.

    uint32_t A = top >> 32;
    uint32_t B = top & 0xFFFFFFFF;
    uint32_t C = bot >> 32;
    uint32_t D = bot & 0xFFFFFFFF;
    __m128i ba = _mm_set_epi32(0, B, 0, A); // { B, A }
    __m128i dc = _mm_set_epi32(0, D, 0, C); // { D, C }
    __m128i cd = _mm_shuffle_epi32(dc, _MM_SHUFFLE(1, 0, 3, 2)); // { C, D }

    __m128i bd_ac = _mm_mul_epu32(ba, dc); // { BD, AC }
    __m128i bc_ad = _mm_mul_epu32(ba, cd); // { BC, AD }

    // this could be improved probably
#ifdef __SSE4_1__
    __m128i zero = _mm_setzero_si128();
    __m128i bc_ad_lo = _mm_blend_epi16(bc_ad, zero, 0xCC); // bd_ac & 0xFFFFFFFF;
#else 
    __m128i ff = _mm_set_epi32(0, 0xFFFFFFFF, 0, 0xFFFFFFFF);
    __m128i bc_ad_lo = _mm_and_si128(bc_ad, ff);
#endif 
    __m128i bd_hi = _mm_srli_si128(bd_ac, 12); // {   0, BD >> 32 }
    __m128i sum = _mm_add_epi64(bc_ad_lo, bd_hi);  // { bc & 0xFFFFFFFF, ad & 0xFFFFFFFF + BD >> 32 }
    __m128i sumShuf = _mm_castps_si128(_mm_shuffle_ps(_mm_castsi128_ps(sum), _mm_setzero_ps(), _MM_SHUFFLE(0, 0, 3, 2)));
    sum = _mm_add_epi64(sum, sumShuf);
   // dunno

Wenn ich es mit Vektorerweiterungen mache, gibt mir Clang Folgendes:

    U64x2 BA = { top >> 32, top & 0xFFFFFFFF };
    U64x2 DC = { bot >> 32, bot & 0xFFFFFFFF };
    U64x2 CD = { bot & 0xFFFFFFFF, bot >> 32 };
    U64x2 BD_AC = (BA & 0xFFFFFFFF) * (DC & 0xFFFFFFFF);
    U64x2 BC_AD = (BA & 0xFFFFFFFF) * (CD & 0xFFFFFFFF);
    U64x2 sum = BC_AD & 0xFFFFFFFF;
    sum[1] += BD_AC[0] >> 32;
    sum[0] += sum[1];
    U64x2 sumv2 = { sum[0] << 32, sum[0] >> 32 };
    sum = sumv2;
    sum += BD_AC & 0xFFFFFFFF;
    sum += BC_AD >> 32;
    sum[1] += (BD_AC[1] & 0xFFFFFFFF00000000);
    sum[0] += sum[1];
    return sum[0];

In der Mitte wird jedoch auf Skalar umgeschaltet. Ich wünschte irgendwie, ich hätte die halben Register von NEON…

_mull_u64_v2:                           ## <strong i="9">@mull_u64_v2</strong>
## %bb.0:
        push    esi
        call    L1$pb
L1$pb:
        pop     eax
        movd    xmm0, dword ptr [esp + 16] ## xmm0 = mem[0],zero,zero,zero
        movd    xmm2, dword ptr [esp + 20] ## xmm2 = mem[0],zero,zero,zero
        punpcklqdq      xmm2, xmm0      ## xmm2 = xmm2[0],xmm0[0]
        movd    xmm0, dword ptr [esp + 8] ## xmm0 = mem[0],zero,zero,zero
        movd    xmm3, dword ptr [esp + 12] ## xmm3 = mem[0],zero,zero,zero
        movdqa  xmm1, xmm3
        punpcklqdq      xmm1, xmm0      ## xmm1 = xmm1[0],xmm0[0]
        punpcklqdq      xmm0, xmm3      ## xmm0 = xmm0[0],xmm3[0]
        pmuludq xmm1, xmm2
        pmuludq xmm0, xmm2
        pshufd  xmm2, xmm1, 229         ## xmm2 = xmm1[1,1,2,3]
        movd    edx, xmm2
        pshufd  xmm2, xmm0, 78          ## xmm2 = xmm0[2,3,0,1]
        movd    esi, xmm2
        xor     ecx, ecx
        add     esi, edx
        setb    cl
        movd    edx, xmm0
        add     edx, esi
        adc     ecx, 0
        movd    xmm2, ecx
        movd    xmm3, edx
        pshufd  xmm4, xmm1, 231         ## xmm4 = xmm1[3,1,2,3]
        pand    xmm1, xmmword ptr [eax + LCPI1_0-L1$pb]
        shufps  xmm3, xmm2, 65          ## xmm3 = xmm3[1,0],xmm2[0,1]
        psrlq   xmm0, 32
        paddq   xmm0, xmm1
        paddq   xmm0, xmm3
        movd    eax, xmm4
        pshufd  xmm1, xmm0, 78          ## xmm1 = xmm0[2,3,0,1]
        movd    ecx, xmm1
        pshufd  xmm1, xmm0, 231         ## xmm1 = xmm0[3,1,2,3]
        movd    esi, xmm1
        add     esi, eax
        pshufd  xmm1, xmm0, 229         ## xmm1 = xmm0[1,1,2,3]
        movd    edx, xmm1
        movd    eax, xmm0
        add     eax, ecx
        adc     edx, esi
        pop     esi
        ret

Alle 6 Kommentare

Ja, Sie haben recht @easyaspi314 , und dies ist ein bekanntes Problem.
Ich plane, einen dedizierten Code hinzuzufügen, um dies für 32-Bit-Plattformen (und Nicht-Gcc-Plattformen) zu emulieren.
Es sind bereits mehrere Implementierungen verfügbar, also sollte ich in der Lage sein, eine zu greifen und anzuschließen.

Eigentlich sieht der Skalar gar nicht so schlecht aus.

GCC hasst den Code immer noch und besteht darauf, den Stack zu verwenden (oder eine hässliche partielle Vektorisierung für den ersten), aber Clang gibt sauberen Code für alle aus. Es gibt zwar den besten Code für ARMv7 aus, aber das liegt hauptsächlich an den akkumulierten Anweisungen.

umaal ist eine komplette Bestie einer Anweisung, die in ARMv6 hinzugefügt wurde:

void umaal(uint32_t *RdLo, uint32_t *RdHi, uint32_t Rn, uint32_t Rm)
{
    uint64_t prod = (uint64_t) Rn * (uint64_t) Rm;
    prod += (uint64_t) *RdLo;
    prod += (uint64_t) *RdHi;
    *RdLo = prod & 0xFFFFFFFF;
    *RdHi = prod >> 32;
}

Hier ist ein Vergleich einiger Implementierungen, die ich online gefunden habe.
https://gcc.godbolt.org/z/pWh9No

Das zweite könnte besser sein, weil GCC es nicht vektorisiert und es auf ARM nicht schrecklich ist (es fügt nur eine zusätzliche Anweisung hinzu). Bei MSVC möchten wir dies intrinsisch statt umwandeln, da MSVC dumm ist und versuchen wird, eine vollständige 64-Bit-Multiplikation durchzuführen.

Ich habe den xxh3 Zweig mit einer neuen Version von xxh3.h hochgeladen
die ein Update für die mul128 Funktion auf Nicht-x64-Plattformen enthält.
Ich könnte überprüfen, ob es im 32-Bit-Modus kompiliert und ordnungsgemäß ausgeführt wird.

Es enthält auch einen dedizierten Pfad für ARM aarch , obwohl ich glaube, dass das vorhandene gcc eine höhere Priorität hat und wahrscheinlich das gleiche Ergebnis liefern wird (die generierte Assembly wurde noch nicht überprüft). Vielleicht können Sie diesen Teil besser verstehen.

https://gcc.godbolt.org/z/PRtMJy

Boom. Geld.

37-48 Anweisungen auf x86, 8-9 Anweisungen auf ARMv6+, und dank __attribute__((__target__("no-sse2"))) vektorisiert GCC es nicht teilweise, wenn es für Core 2 optimiert wird.

Ich musste Inline-Assembly für ARM verwenden, weil ich Clang oder GCC nicht dazu bringen konnte, meine Anfrage für umaal herauszufinden. Leider, ja, dieses Durcheinander von #if Aussagen ist irgendwie scheiße.

        umull   r12, lr, r0, r2  @ {r12, lr} = (U64)r0 * (U64)r2
        mov     r5, #0           @ r5 = 0
        mov     r4, #0           @ r4 = 0
        umaal   r5, lr, r1, r2   @ {r5, lr} = ((U64)r1 * (U64)r2) + r5 + lr
        umaal   r5, r4, r0, r3   @ {r5, r4} = ((U64)r0 * (U64)r3) + r5 + r4
        umaal   lr, r4, r1, r3   @ {lr, r4} = ((U64)r1 * (U64)r3) + lr + r4
        adds    r0, lr, r12      @ <-.
                                 @    {r0, r1} = (U64){lr, r4} + (U64){r12, r5}
        adc     r1, r4, r5       @ <-'

Ich glaube nicht, dass ich es mehr als das optimieren kann. Das sind nur 8 Anweisungen.

Ich habe XXH_mult32to64 hinzugefügt, das auf MSVC zu __emulu erweitert wird, wodurch es viel weniger wahrscheinlich ist, dass MSVC einen __allmul Aufruf ausgibt (obwohl dies fairerweise nicht der Fall ist) es in diesem Fall).

Ich denke immer noch, dass x86 weiter optimiert werden kann, obwohl es möglicherweise nur die festen Ausgaberegister sind, die all das Mischen verursachen. Es läuft eigentlich recht schnell:

    uint32_t val32[4];
    uint64_t *val = val32;
    uint64_t sum = 0;
    srand(0);
    double start = (double)clock();
    for (int i = 0; i < 10000000; i++) {
        val32[0] = rand();
        val32[1] = rand();
        val32[2] = rand();
        val32[3] = rand();
        sum += XXH_mul128AndFold_32(val[0], val[1]);
    }
    double end = (double)clock();
    printf("%lld %lfs\n", sum, (end - start) / CLOCKS_PER_SEC);

```
7652620537862933594 0.454625s

For comparison, this is in 64-bit mode with `__uint128_t`:

7652620537862933594 0.336406

Only 35% slower considering how much more work it does.

Side note: Clang optimizes the `__uint128_t` version to this on aarch64, so that is probably best to use it. Fused multiply and add is always preferred. 
```asm
        umulh   x8, x1, x0     // x8 = ((__uint128_t)x1 * x0) >> 64
        madd    x0, x1, x0, x8 // x0 = (x1 * x0) + x8;

Sieht großartig aus !

Auf ARM ist es trotz der deutlich weniger Anweisungen definitiv langsamer, aber das liegt hauptsächlich daran, dass der rand()-Aufruf auf Bionic ziemlich ausgefeilt ist.

967456348838854209 5.572055s

Ersetzen Sie es durch nicht initialisierte Daten, zwingen Sie es in ein Register und verwenden Sie -fno-inline , um zu verhindern, dass es betrügt, und es ist sehr schnell:

5764888493227865371 0.119198s

(Ohne die rand Aufrufe für x86 ist es 0.076690s meine Implementierung vs. 0.031167 natives x86_64, was mehr Sinn macht. Trotzdem ziemlich schnell.)

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

make-github-pseudonymous-again picture make-github-pseudonymous-again  ·  3Kommentare

easyaspi314 picture easyaspi314  ·  7Kommentare

yassinm picture yassinm  ·  5Kommentare

vinniefalco picture vinniefalco  ·  4Kommentare

devnoname120 picture devnoname120  ·  8Kommentare