Vlákno názorů k článku Použití instrukcí SSE a AVX pro zrychlení bitových operací od kvr kvr - Není tam chyba? int idx...

  • Článek je starý, nové názory již nelze přidávat.
  • 23. 11. 2022 17:00

    kvr kvr

    Není tam chyba?


    int idx = i/2 + imod - 3;
    //vs:
    outbuf2[i2 + 1] = (float)raw3;

    IMHO v tom prvním má být imod-2 . Nezkoušel jsem, jestli to nějak zlepší výsledek autovektorizace.

    Ohledně poznámky výše a SSE vs aarch64 - na Apple M1 netřeba, stačí zkusit Graviton3 v AWS - podporuje Neon i SVE. S těmi 2+2 konverzemi si nejsem jistý, jestli to zvládne efektivně. Ještě by mělo smysl zkusit AVX-512, které je taky už nějakou dobu standard, aspoň u Intel.

    PS: Pěkný článek a hezké ne zcela běžné využití SIMD.

  • 23. 11. 2022 17:08

    Ladis

    Jen doplním, že M1 má "plnotučná" jádra, která si poradí i s neoptimálním kódem (podobně jako x86 jádra od Intel a AMD). Zatímco vše ostatní jsou jádra Cortex, která tak chytrá (ale taky velká) nejsou. Příkladem jsou optimalizace, které nemají na M1 vliv (neoptimální kód tam běží stejně rychle jako optimální). Samozřejmě pro Amazon a cloud obecně je lepší více menších hloupějších jader a optimalizovat software na míru (variable vs fixed costs).

  • 23. 11. 2022 19:06

    kvr kvr

    Je pravda, že u násobení matic mi přišly třeba Graviton na Neon pomalejší než M1, se SVE ale byly rychlejší.

    Nicméně, zkusil jsem pro zajímavost M1 a Graviton3:

    #include <stdint.h>
    #include <stdlib.h>
    
    #ifdef __aarch64__
    #include <arm_neon.h>
    #endif
    
    
    void radar_ref(float *outbuf1, float *outbuf2, int16_t *inbuf, int SAMPLES)
    {
        for (size_t i = 0; i<4*SAMPLES; i++) {
    
            // fix sign-extend
            int16_t raw = inbuf[i];
            raw = (raw & 0xEFFF) | ((raw & 0xE000)>>1);
    
            // make float from int16
            float raw_f = (float)raw;
    
            // decide where to put it - horizontal or vertical buffer
            int imod = i % 4;
            if(imod < 2) {
                int idx = i/2 + imod;
                outbuf1[idx] = raw_f;
            } else {
                int idx = i/2 + imod - 3;
                outbuf2[idx] = raw_f;
            }
        }
    }
    
    void radar_Neon(float *outbuf1, float *outbuf2, int16_t *inbuf, int SAMPLES)
    {
        int16x4_t four16_EFFF = vdup_n_s16(0xEFFF);
        int16x4_t four16_E000 = vdup_n_s16(0xE000);
    
        for (size_t b = 0; b < SAMPLES; b += 1) {
    
            // fix sign-extend
            int16x4_t raw4 = ((int16x4_t *)inbuf)[b];
            int32x4_t adjusted = vmovl_s16(vorr_s16(vand_s16(raw4, four16_EFFF), vshr_n_s16(vand_s16(raw4, four16_E000), 1)));
    
            // make float from int16
            float32x4_t flt = vcvtq_f32_s32(adjusted);
    
            vst1_f32(outbuf1+b*2, vget_low_f32(flt));
            vst1_f32(outbuf2+b*2, vget_high_f32(flt));
        }
    }
    
    // main.c :
    // (too smart compiler may completely remove code doing nothing)
    #include <stdint.h>
    #include <stdlib.h>
    
    extern void radar_ref(float *outbuf1, float *outbuf2, int16_t *inbuf, int SAMPLES);
    extern void radar_Neon(float *outbuf1, float *outbuf2, int16_t *inbuf, int SAMPLES);
    
    #define SAMPLES 30000
    #define ITERATIONS 1000000
    
    
    int main(void)
    {
        float outbuf1[SAMPLES*2], outbuf2[SAMPLES*2];
        int16_t inbuf[SAMPLES*4];
    
        for (int i = 0; i < ITERATIONS; ++i) {
            radar_ref(outbuf1, outbuf2, inbuf, SAMPLES);
        }
        return 0;
    }

    Nejspíš by šlo vylepšit zpracováním osmi prvků najednou, podobně jako v původním AVX. Případně šestnácti prvků a využít load and store pair.
    Neznám tak dobře shuffle instrukce na Neon, abych to dal z hlavy.

    Apple M1 Pro (clang 14.0.0) :
    Letmým pohled, clang byl schopen autovektorizovat aspoň operace po dvou prvcích.
    ref: 44 us
    Neon: 20 us

    Graviton3 c7g.medium (gcc 12.1.0) :
    Přímý kód, žádná autovektorizace.
    ref: 183 us
    Neon: 41 us