Dlaczego kompilator Rusta nie optymalizuje kodu przy założeniu, że dwa zmienne odniesienia nie mogą być aliasem?

Z tego, co wiem, aliasing referencji/wskaźników może utrudnić kompilatorowi generowanie zoptymalizowanego kodu, ponieważ musi zapewnić, że wygenerowany plik binarny zachowuje się poprawnie w przypadku, gdy dwa referencje/wskaźniki rzeczywiście alias. Na przykład w następującym kodzie C

void adds(int  *a, int *b) {
    *a += *b;
    *a += *b;
}

Po skompilowaniu przez clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final) z flagą -O3, emituje

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)  # The first time
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)  # The second time
   a:    c3                       retq

Tutaj Kod przechowuje się z powrotem do (%rdi) dwa razy w przypadku int *a i int *b aliasu.

Kiedy wyraźnie powiemy kompilator, którego te dwa wskaźniki nie mogą nazwać słowem kluczowym restrict:

void adds(int * restrict a, int * restrict b) {
    *a += *b;
    *a += *b;
}

Wtedy Clang będzie emitował bardziej zoptymalizowaną wersję kodu binarnego:

0000000000000000 <adds>:
   0:    8b 06                    mov    (%rsi),%eax
   2:    01 c0                    add    %eax,%eax
   4:    01 07                    add    %eax,(%rdi)
   6:    c3                       retq

Ponieważ Rust upewnia się (z wyjątkiem kodu unsafe), że dwa zmienne odniesienia nie mogą być aliasem, myślę, że kompilator powinien być w stanie emitować bardziej zoptymalizowaną wersję kodu.

Kiedy testuję z poniższym kodem i kompiluję go z rustc 1.35.0 z -C opt-level=3 --emit obj,

#![crate_type = "staticlib"]
#[no_mangle]
fn adds(a: &mut i32, b: &mut i32) {
    *a += *b;
    *a += *b;
}

It generuje:

0000000000000000 <adds>:
   0:    8b 07                    mov    (%rdi),%eax
   2:    03 06                    add    (%rsi),%eax
   4:    89 07                    mov    %eax,(%rdi)
   6:    03 06                    add    (%rsi),%eax
   8:    89 07                    mov    %eax,(%rdi)
   a:    c3                       retq

To nie korzysta z gwarancji, że a i b nie mogą być używane.

Czy to dlatego, że obecny kompilator Rusta jest nadal w fazie rozwoju i nie włączył jeszcze analizy aliasów do optymalizacji?

Czy to dlatego, że jest jeszcze szansa, że a i b mogą się nazywać, nawet w safe Rust?

Author: Peter Mortensen, 2019-07-29

1 answers

Rust pierwotnie włączył atrybut LLVM noalias, ale to spowodowało błędną kompilację kodu . Gdy wszystkie obsługiwane wersje LLVM nie będą już źle kompilować kodu, zostanie on ponownie włączony.

Jeśli dodasz -Zmutable-noalias=yes do opcji kompilatora, otrzymasz oczekiwany skład:

adds:
        mov     eax, dword ptr [rsi]
        add     eax, eax
        add     dword ptr [rdi], eax
        ret

Mówiąc najprościej, Rust umieścił odpowiednik słowa kluczowego C restrict wszędzie , znacznie bardziej rozpowszechniony niż jakikolwiek zwykły program C. To ćwiczyło narożne przypadki LLVM więcej niż to był w stanie obsłużyć poprawnie. Okazuje się, że programiści C i c++ po prostu nie używają restrict tak często, jak &mut jest używany w Rust.

To się zdarzyło wiele razy .

  • Rust 1.0 do 1.7 - noalias włączone
  • Rust 1.8 do 1.27 - noalias wyłączone
  • Rust 1.28 do 1.29 - noalias włączone
  • Rust 1.30 do ??? - noalias wyłączone

Powiązane problemy z rdzą

 391
Author: Shepmaster,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-08-01 17:28:50