Explorarea instrucțiunilor SIMD în Rust

  • 15 minute de citit
  • 17 exemple de cod
  • 2 imagini

Disponibil și în English

Pe această pagină

Salut 👋,

Am început să explorez Rust acum o săptămână, deoarece la locul de muncă tot mai multe servicii web sunt scrise în Rust, iar până acum îmi place limbajul: este simplu, rapid și frumos.

Activitățile mele zilnice de programare sunt în Python, Go și Java. Am folosit și C# pentru câteva proiecte personale. Învățarea Rust nu este chiar ușoară pentru mine, deoarece tot ce scriu sunt servicii web sau programe Java care nu conțin logică „de nivel jos”.

În acest articol voi vorbi despre drumul meu cu Rust și despre experiența mea cu std::simd. Mă fascinează lucrurile cu care nu am prea multă experiență. Am încercat și SIMD cu Go, dar este peste nivelul meu de pricepere, nu am reușit să-l fac să funcționeze.

Ce este SIMD?

SIMD înseamnă Single Instruction/Multiple Data (o instrucțiune, mai multe date). Să presupunem un vector de 128 de elemente.

[1,2,3, …, 128]

Când adunăm elementele, parcurgem fiecare element în buclă. Un cod tipic va arăta astfel:

int sum(int * arr, int size) {    int sum = 0;    for (int i = 0; i < size; i++) {        sum = sum + *(arr + i);    }    return 0;}

Assembly-ul generat va arăta cam așa:

Codul cu ROȘU este partea de buclă, după cum vezi avem o mulțime de instrucțiuni. Uneori, când size este cunoscut, compilatorul poate alege să facă loop unrolling, adică în loc să folosească instrucțiuni JMP și să facă buclă, compilatorul va face ceva de genul acesta, de exemplu dacă size este 3:

int sum(int * arr, int size) {    int sum = 0;    sum = sum + *(arr + 0);    sum = sum + *(arr + 1);    sum = sum + *(arr + 2);    return 0;}

Cu instrucțiuni SIMD, procesorul tău are suport pentru instrucțiuni assembly care pot lucra cu vectori într-un mod eficient. Poți citi mai multe despre SIMD și despre cum funcționează în acest articol excelent Crunching Numbers with AVX and AVX2.

În esență, dacă ai doi vectori a și b, îi spui compilatorului să efectueze o operație matematică asupra lor, iar aceasta va fi eficientă. Imaginează-ți că, în loc să parcurgi fiecare element și să execuți operația +, toate elementele sunt adunate deodată:

Acum că avem o înțelegere de bază a modului în care funcționează SIMD, putem trece la Rust.

Program Rust pentru calculul distanței Hamming

Notă:

dacă vrei să urmărești sau să rulezi codul din acest articol, va trebui să instalezi versiunea nightly de Rust și să o setezi ca implicită:

rustup install nightlyrustup default nightlycând ai terminat:rustup default stable

Problema pe care vrem să o rezolvăm cu instrucțiuni SIMD este calculul distanței Hamming pentru doi vectori de aceeași dimensiune. Distanța Hamming dintre [1, 2, 3] și [1, 3, 3] este egală cu 1, deoarece 2 != 3, iar restul elementelor sunt egale.

Funcția Rust non-SIMD arată astfel:

#[inline(never)]fn hamming_distance_classic(a: &mut Vec<i32>, b: &mut Vec<i32>) -> u64 {    let mut distance = 0;    let length = a.len();    for i in 0..length {        if a[i] != b[i] {            distance += 1        }    }    return distance;}

Pentru a previzualiza assembly-ul, un coleg mi-a recomandat crate-ul cargo-show-asm. Îl poți instala și rula următoarea comandă pentru a afișa codul assembly al funcției date

cargo asm --bin rust_simd "rust_simd::hamming_distance_classic"

Assembly-ul generat arată ca o buclă simplă:

.LBB12_4:        cmp r8, rcx        jae .LBB12_5        mov r10d, dword ptr [rdi + 4*r9]        xor r11d, r11d        cmp r10d, dword ptr [rdx + 4*r9]        lea r10, [r9 + 1]        setne r11b        add rax, r11        mov r9, r10        cmp rsi, r10        jne .LBB12_4        pop rcx        .cfi_def_cfa_offset 8        ret

În continuare, îmi voi arăta implementarea care folosește instrucțiuni SIMD:

#[inline(never)]fn hamming_distance_simd(a: &mut Vec, b: &mut Vec) -> u32 {    let mut distance: u32 = 0;    let lanes = 16;    let chunks = a.len() / lanes;    for i in 0..chunks {        // creează vectori SIMD        let a: i32x16 = Simd::from_slice(&a[i*lanes..i*lanes+lanes]);        let b: i32x16 = Simd::from_slice(&b[i*lanes..i*lanes+lanes]);        // XOR SIMD        let z = a.simd_ne(b);        distance += z.to_bitmask().count_ones();    }    return distance;}

Codul assembly pentru această funcție arată astfel:

.LBB11_2:        cmp r8, rsi        ja .LBB11_6        cmp r8, rcx        ja .LBB11_7        movdqu xmm1, xmmword ptr [rdi + 4*r8 - 64]        movdqu xmm2, xmmword ptr [rdi + 4*r8 - 48]        movdqu xmm3, xmmword ptr [rdi + 4*r8 - 32]        movdqu xmm4, xmmword ptr [rdi + 4*r8 - 16]        movdqu xmm5, xmmword ptr [rdx + 4*r8 - 64]        pcmpeqd xmm5, xmm1        movdqu xmm1, xmmword ptr [rdx + 4*r8 - 48]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [rdx + 4*r8 - 32]        pcmpeqd xmm2, xmm3        movdqu xmm3, xmmword ptr [rdx + 4*r8 - 16]        pcmpeqd xmm3, xmm4        pxor xmm1, xmm0        pxor xmm5, xmm0        packssdw xmm5, xmm1        pxor xmm2, xmm0        pxor xmm3, xmm0        packssdw xmm2, xmm3        packsswb xmm5, xmm2        pmovmskb r10d, xmm5        mov r11d, r10d        shr r11d        and r11d, 21845        sub r10d, r11d        mov r11d, r10d        and r11d, 13107        shr r10d, 2        and r10d, 13107        add r10d, r11d        mov r11d, r10d        shr r11d, 4        add r11d, r10d        and r11d, 3855        mov r10d, r11d        shr r10d, 8        add r10d, r11d        movzx r10d, r10b        add eax, r10d        add r8, 16        dec r9        jne .LBB11_2

Vedem că registrele XMM sunt folosite.

Benchmarking-ul codului

Pentru a face benchmark codului, am folosit un bloc de cod pe care l-am copiat de pe o pagină despre SIMD în Rust[1] și pe care l-am modificat puțin.

mod bench {    extern crate test;    use self::test::Bencher;    use std::iter;    static BENCH_SIZE: usize = 128;    macro_rules! bench {        ($name:ident, $func:ident) => {            #[bench]            fn $name(b: &mut Bencher) {                let mut x: Vec<_> = iter::repeat(1i32)                                        .take(BENCH_SIZE)                                        .collect();                let mut y: Vec<_> = iter::repeat(1i32)                                        .take(BENCH_SIZE)                                        .collect();                b.iter(|| {                    super::$func(&mut x, &mut y);                })            }        }    }    bench!(simd, hamming_distance_simd);    bench!(vanilla, hamming_distance_classic);}

Rezultatele sunt următoarele. Codul scris cu SIMD câștigă:

/home/denis/.cargo/bin/cargo bench --color=always --package rust_simd --bin rust_simd bench    Finished bench [optimized] target(s) in 0.00s     Running unittests src/main.rs (target/release/deps/rust_simd-2477d04109c1c861)running 2 teststest bench::simd    ... bench:          14 ns/iter (+/- 0)test bench::vanilla ... bench:          20 ns/iter (+/- 0)

Inlining-ul funcțiilor

Dacă eliminăm #[inline(never)], atunci se întâmplă ceva interesant. Compilatorul observă că ambele funcții sunt folosite o singură dată și decide să le facă inline.

Dacă rulăm cargo asm –bin rust_simd, funcțiile nu mai apar în rezultat:

Try one of those by name or a sequence number 0 "alloc::alloc::box_free" [15] 1 "alloc::raw_vec::RawVec::reserve_for_push" [136] 2 "alloc::raw_vec::finish_grow" [110] 3 "core::ops::function::FnOnce::call_once" [19] 4 "core::ptr::drop_in_place>" [31] 5 "core::ptr::drop_in_place" [27] 6 "core::ptr::drop_in_place::>" [7] 7 "main" [18] 8 "rand::rngs::adapter::reseeding::ReseedingCore::reseed_and_generate" [157] 9 "rust_simd::main" [787]10 "std::rt::lang_start" [28]11 "std::rt::lang_start::" [18]12 "std::sys_common::backtrace::__rust_begin_short_backtrace" [22]

Rulând din nou benchmark-ul, obținem următorul rezultat:

/home/denis/.cargo/bin/cargo bench --color=always --package rust_simd --bin rust_simd bench    Finished bench [optimized] target(s) in 0.00s     Running unittests src/main.rs (target/release/deps/rust_simd-2477d04109c1c861)running 2 teststest bench::simd    ... bench:          20 ns/iter (+/- 0)test bench::vanilla ... bench:           0 ns/iter (+/- 0)

Funcția vanilla (distanța Hamming clasică) este mult mai rapidă, iar dacă ne uităm la codul assembly, vedem că și compilatorul Rust este inteligent și a vectorizat automat codul pentru a folosi instrucțiuni SIMD. Spre deosebire de mine, compilatorul este mai deștept și codul lui este mai bun:

.LBB11_56:        movq xmm3, qword ptr [r15 + 4*rdx]        movq xmm4, qword ptr [r15 + 4*rdx + 8]        movq xmm5, qword ptr [rbp + 4*rdx]        pcmpeqd xmm5, xmm3        movq xmm3, qword ptr [rbp + 4*rdx + 8]        pcmpeqd xmm3, xmm4        pshufd xmm4, xmm5, 212        pandn xmm4, xmm2        paddq xmm4, xmm0        pshufd xmm3, xmm3, 212        pandn xmm3, xmm2        paddq xmm3, xmm1        movq xmm0, qword ptr [r15 + 4*rdx + 16]        movq xmm1, qword ptr [r15 + 4*rdx + 24]        movq xmm5, qword ptr [rbp + 4*rdx + 16]        pcmpeqd xmm5, xmm0        movq xmm6, qword ptr [rbp + 4*rdx + 24]        pcmpeqd xmm6, xmm1        pshufd xmm0, xmm5, 212        pandn xmm0, xmm2        paddq xmm0, xmm4        pshufd xmm1, xmm6, 212        pandn xmm1, xmm2        paddq xmm1, xmm3        add rdx, 8        add rsi, -2        jne .LBB11_56        test cl, 1        je .LBB11_59.LBB11_58:        movq xmm2, qword ptr [r15 + 4*rdx]        movq xmm3, qword ptr [r15 + 4*rdx + 8]        movq xmm4, qword ptr [rbp + 4*rdx]        pcmpeqd xmm4, xmm2        movq xmm2, qword ptr [rbp + 4*rdx + 8]        pcmpeqd xmm2, xmm3        pshufd xmm3, xmm4, 212        movdqa xmm4, xmmword ptr [rip + .LCPI11_0]        pandn xmm3, xmm4        paddq xmm0, xmm3        pshufd xmm2, xmm2, 212        pandn xmm2, xmm4        paddq xmm1, xmm2.LBB11_59:        paddq xmm0, xmm1        pshufd xmm1, xmm0, 238        paddq xmm1, xmm0        movq rcx, xmm1        cmp rdi, rax        je .LBB11_61

Assembly-ul pentru funcția hamming_distance_simd arată astfel:

.LBB11_40:        cmp rdi, rax        ja .LBB11_41        cmp rdi, r12        ja .LBB11_44        movdqu xmm1, xmmword ptr [r15 + 4*rdi - 64]        movdqu xmm2, xmmword ptr [r15 + 4*rdi - 48]        movdqu xmm3, xmmword ptr [r15 + 4*rdi - 32]        movdqu xmm4, xmmword ptr [r15 + 4*rdi - 16]        movdqu xmm5, xmmword ptr [r10 + 4*rdi - 64]        pcmpeqd xmm5, xmm1        movdqu xmm1, xmmword ptr [r10 + 4*rdi - 48]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [r10 + 4*rdi - 32]        pcmpeqd xmm2, xmm3        movdqu xmm3, xmmword ptr [r10 + 4*rdi - 16]        pcmpeqd xmm3, xmm4        pxor xmm3, xmm0        pxor xmm2, xmm0        packssdw xmm2, xmm3        pxor xmm1, xmm0        pxor xmm5, xmm0        packssdw xmm5, xmm1        packsswb xmm5, xmm2        pmovmskb ebp, xmm5        mov esi, ebp        shr esi        and esi, 21845        sub ebp, esi        mov esi, ebp        and esi, 13107        shr ebp, 2        and ebp, 13107        add ebp, esi        mov eax, ebp        shr eax, 4        add eax, ebp        and eax, 3855        mov esi, eax        shr esi, 8        add esi, eax        movzx eax, sil        add r9d, eax        add rdi, 16        dec rcx        mov rax, qword ptr [rsp + 8]        jne .LBB11_40

Putem forța și loop unrolling dacă fixăm dimensiunea blocurilor în hamming_distance_simd:

fn hamming_distance_simd(a: &mut Vec, b: &mut Vec) -> u32 {    let mut distance: u32 = 0;    let lanes = 16;    // let chunks = a.len() / lanes;    for i in 0..8 {        // creează vectori SIMD        let a: i32x16 = Simd::from_slice(&a[i*lanes..i*lanes+lanes]);        let b: i32x16 = Simd::from_slice(&b[i*lanes..i*lanes+lanes]);        // XOR SIMD        let z = a.simd_ne(b);        distance += z.to_bitmask().count_ones();    }    return distance;}

Atunci assembly-ul va deveni:

        movdqu xmm0, xmmword ptr [r15]        movdqu xmm1, xmmword ptr [r15 + 16]        movdqu xmm2, xmmword ptr [r15 + 32]        movdqu xmm3, xmmword ptr [r15 + 48]        movdqu xmm4, xmmword ptr [r14]        pcmpeqd xmm4, xmm0        movdqu xmm0, xmmword ptr [r14 + 16]        pcmpeqd xmm0, xmm1        movdqu xmm1, xmmword ptr [r14 + 32]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [r14 + 48]        pcmpeqd xmm2, xmm3        pcmpeqd xmm3, xmm3        pxor xmm2, xmm3        pxor xmm1, xmm3        packssdw xmm1, xmm2        pxor xmm0, xmm3        pxor xmm4, xmm3        packssdw xmm4, xmm0        packsswb xmm4, xmm1        pmovmskb eax, xmm4        mov ecx, eax        shr ecx        and ecx, 21845        sub eax, ecx        mov ecx, eax        and ecx, 13107        shr eax, 2        and eax, 13107        add eax, ecx        mov ecx, eax        shr ecx, 4        add ecx, eax        and ecx, 3855        mov eax, ecx        shr eax, 8        add eax, ecx        movzx eax, al        movdqu xmm0, xmmword ptr [r15 + 64]        movdqu xmm1, xmmword ptr [r15 + 80]        movdqu xmm2, xmmword ptr [r15 + 96]        movdqu xmm3, xmmword ptr [r15 + 112]        movdqu xmm4, xmmword ptr [r14 + 64]        pcmpeqd xmm4, xmm0        movdqu xmm0, xmmword ptr [r14 + 80]        pcmpeqd xmm0, xmm1        movdqu xmm1, xmmword ptr [r14 + 96]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [r14 + 112]        pcmpeqd xmm2, xmm3        pcmpeqd xmm3, xmm3        pxor xmm2, xmm3        pxor xmm1, xmm3        packssdw xmm1, xmm2        pxor xmm0, xmm3        pxor xmm4, xmm3        packssdw xmm4, xmm0        packsswb xmm4, xmm1        pmovmskb ecx, xmm4        mov edx, ecx        shr edx        and edx, 21845        sub ecx, edx        mov edx, ecx        and edx, 13107        shr ecx, 2        and ecx, 13107        add ecx, edx        mov edx, ecx        shr edx, 4        add edx, ecx        and edx, 3855        mov ecx, edx        shr ecx, 8        add ecx, edx        movzx ecx, cl        movzx eax, ax        add eax, ecx        movdqu xmm0, xmmword ptr [r15 + 128]        movdqu xmm1, xmmword ptr [r15 + 144]        movdqu xmm2, xmmword ptr [r15 + 160]        movdqu xmm3, xmmword ptr [r15 + 176]        movdqu xmm4, xmmword ptr [r14 + 128]        pcmpeqd xmm4, xmm0        movdqu xmm0, xmmword ptr [r14 + 144]        pcmpeqd xmm0, xmm1        movdqu xmm1, xmmword ptr [r14 + 160]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [r14 + 176]        pcmpeqd xmm2, xmm3        pcmpeqd xmm3, xmm3        pxor xmm2, xmm3        pxor xmm1, xmm3        packssdw xmm1, xmm2        pxor xmm0, xmm3        pxor xmm4, xmm3        packssdw xmm4, xmm0        packsswb xmm4, xmm1        pmovmskb ecx, xmm4        mov edx, ecx        shr edx        and edx, 21845        sub ecx, edx        mov edx, ecx        and edx, 13107        shr ecx, 2        and ecx, 13107        add ecx, edx        mov edx, ecx        shr edx, 4        add edx, ecx        and edx, 3855        mov ecx, edx        shr ecx, 8        add ecx, edx        movzx ecx, cl        movzx eax, ax        add eax, ecx        movdqu xmm0, xmmword ptr [r15 + 192]        movdqu xmm1, xmmword ptr [r15 + 208]        movdqu xmm2, xmmword ptr [r15 + 224]        movdqu xmm3, xmmword ptr [r15 + 240]        movdqu xmm4, xmmword ptr [r14 + 192]        pcmpeqd xmm4, xmm0        movdqu xmm0, xmmword ptr [r14 + 208]        pcmpeqd xmm0, xmm1        movdqu xmm1, xmmword ptr [r14 + 224]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [r14 + 240]        pcmpeqd xmm2, xmm3        pcmpeqd xmm3, xmm3        pxor xmm2, xmm3        pxor xmm1, xmm3        packssdw xmm1, xmm2        pxor xmm0, xmm3        pxor xmm4, xmm3        packssdw xmm4, xmm0        packsswb xmm4, xmm1        pmovmskb ecx, xmm4        mov edx, ecx        shr edx        and edx, 21845        sub ecx, edx        mov edx, ecx        and edx, 13107        shr ecx, 2        and ecx, 13107        add ecx, edx        mov edx, ecx        shr edx, 4        add edx, ecx        and edx, 3855        mov ecx, edx        shr ecx, 8        add ecx, edx        movzx ecx, cl        movzx eax, ax        add eax, ecx        movdqu xmm0, xmmword ptr [r15 + 256]        movdqu xmm1, xmmword ptr [r15 + 272]        movdqu xmm2, xmmword ptr [r15 + 288]        movdqu xmm3, xmmword ptr [r15 + 304]        movdqu xmm4, xmmword ptr [r14 + 256]        pcmpeqd xmm4, xmm0        movdqu xmm0, xmmword ptr [r14 + 272]        pcmpeqd xmm0, xmm1        movdqu xmm1, xmmword ptr [r14 + 288]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [r14 + 304]        pcmpeqd xmm2, xmm3        pcmpeqd xmm3, xmm3        pxor xmm2, xmm3        pxor xmm1, xmm3        packssdw xmm1, xmm2        pxor xmm0, xmm3        pxor xmm4, xmm3        packssdw xmm4, xmm0        packsswb xmm4, xmm1        pmovmskb ecx, xmm4        mov edx, ecx        shr edx        and edx, 21845        sub ecx, edx        mov edx, ecx        and edx, 13107        shr ecx, 2        and ecx, 13107        add ecx, edx        mov edx, ecx        shr edx, 4        add edx, ecx        and edx, 3855        mov ecx, edx        shr ecx, 8        add ecx, edx        movzx ecx, cl        movzx eax, ax        add eax, ecx        movdqu xmm0, xmmword ptr [r15 + 320]        movdqu xmm1, xmmword ptr [r15 + 336]        movdqu xmm2, xmmword ptr [r15 + 352]        movdqu xmm3, xmmword ptr [r15 + 368]        movdqu xmm4, xmmword ptr [r14 + 320]        pcmpeqd xmm4, xmm0        movdqu xmm0, xmmword ptr [r14 + 336]        pcmpeqd xmm0, xmm1        movdqu xmm1, xmmword ptr [r14 + 352]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [r14 + 368]        pcmpeqd xmm2, xmm3        pcmpeqd xmm3, xmm3        pxor xmm2, xmm3        pxor xmm1, xmm3        packssdw xmm1, xmm2        pxor xmm0, xmm3        pxor xmm4, xmm3        packssdw xmm4, xmm0        packsswb xmm4, xmm1        pmovmskb ecx, xmm4        mov edx, ecx        shr edx        and edx, 21845        sub ecx, edx        mov edx, ecx        and edx, 13107        shr ecx, 2        and ecx, 13107        add ecx, edx        mov edx, ecx        shr edx, 4        add edx, ecx        and edx, 3855        mov ecx, edx        shr ecx, 8        add ecx, edx        movzx ecx, cl        movzx eax, ax        add eax, ecx        movdqu xmm0, xmmword ptr [r15 + 384]        movdqu xmm1, xmmword ptr [r15 + 400]        movdqu xmm2, xmmword ptr [r15 + 416]        movdqu xmm3, xmmword ptr [r15 + 432]        movdqu xmm4, xmmword ptr [r14 + 384]        pcmpeqd xmm4, xmm0        movdqu xmm0, xmmword ptr [r14 + 400]        pcmpeqd xmm0, xmm1        movdqu xmm1, xmmword ptr [r14 + 416]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [r14 + 432]        pcmpeqd xmm2, xmm3        pcmpeqd xmm3, xmm3        pxor xmm2, xmm3        pxor xmm1, xmm3        packssdw xmm1, xmm2        pxor xmm0, xmm3        pxor xmm4, xmm3        packssdw xmm4, xmm0        packsswb xmm4, xmm1        pmovmskb ecx, xmm4        mov edx, ecx        shr edx        and edx, 21845        sub ecx, edx        mov edx, ecx        and edx, 13107        shr ecx, 2        and ecx, 13107        add ecx, edx        mov edx, ecx        shr edx, 4        add edx, ecx        and edx, 3855        mov ecx, edx        shr ecx, 8        add ecx, edx        movzx ecx, cl        movzx eax, ax        add eax, ecx        movdqu xmm0, xmmword ptr [r15 + 448]        movdqu xmm1, xmmword ptr [r15 + 464]        movdqu xmm2, xmmword ptr [r15 + 480]        movdqu xmm3, xmmword ptr [r15 + 496]        movdqu xmm4, xmmword ptr [r14 + 448]        pcmpeqd xmm4, xmm0        movdqu xmm0, xmmword ptr [r14 + 464]        pcmpeqd xmm0, xmm1        movdqu xmm1, xmmword ptr [r14 + 480]        pcmpeqd xmm1, xmm2        movdqu xmm2, xmmword ptr [r14 + 496]        pcmpeqd xmm2, xmm3        pcmpeqd xmm3, xmm3        pxor xmm2, xmm3        pxor xmm1, xmm3        packssdw xmm1, xmm2        pxor xmm0, xmm3        pxor xmm4, xmm3        packssdw xmm4, xmm0        packsswb xmm4, xmm1        pmovmskb ecx, xmm4        mov edx, ecx        shr edx        and edx, 21845        sub ecx, edx        mov edx, ecx        and edx, 13107        shr ecx, 2        and ecx, 13107        add ecx, edx        mov edx, ecx        shr edx, 4        add edx, ecx        and edx, 3855        mov ecx, edx        shr ecx, 8        add ecx, edx        movzx ecx, cl        add ecx, eax        mov dword ptr [rsp + 108], ecx        lea rax, [rsp + 108]        mov qword ptr [rsp + 120], rax        mov rax, qword ptr [rip + core::fmt::num::imp::::fmt@GOTPCREL]

Se poate observa că prin desfacerea buclei există exact 8 blocuri de cod care folosesc instrucțiuni SIMD. Performanța este puțin mai bună, dar nu cu mult:

/home/denis/.cargo/bin/cargo bench --color=always --package rust_simd --bin rust_simd bench    Finished bench [optimized] target(s) in 0.00s     Running unittests src/main.rs (target/release/deps/rust_simd-2477d04109c1c861)running 2 teststest bench::simd    ... bench:          17 ns/iter (+/- 1)test bench::vanilla ... bench:           0 ns/iter (+/- 0)

Concluzii

Sunt încă un începător în Rust, iar drumul meu abia începe. Am încercat să depășesc compilatorul Rust folosind manual instrucțiuni SIMD, dar nu a funcționat prea bine. Rust a reușit să optimizeze și să vectorizeze codul de buclă care nu fusese scris având în vedere instrucțiunile SIMD, iar în final a oferit performanță mult mai bună.

Dacă ai sugestii despre cum pot îmbunătăți codul, te rog să-mi spui în comentarii. 😀

Mulțumesc că ai citit! Spor la programat!


Cod

https://gist.github.com/dnutiu/8fc529d09d2872595cf2bf23d40e1cd2

Referințe