Tại sao trình biên dịch Rust không tối ưu hóa mã giả định rằng hai tham chiếu có thể thay đổi không thể bí danh?


301

Theo như tôi biết, bí danh tham chiếu / con trỏ có thể cản trở khả năng tạo mã được tối ưu hóa của trình biên dịch, vì chúng phải đảm bảo nhị phân được tạo hoạt động chính xác trong trường hợp hai tham chiếu / con trỏ thực sự là bí danh. Ví dụ, trong mã C sau đây,

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

khi biên soạn bởi clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)với -O3cờ, nó phát ra

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

Ở đây mã lưu trữ trở lại (%rdi)hai lần trong trường hợp int *aint *bbí danh.

Khi chúng tôi nói rõ với trình biên dịch rằng hai con trỏ này không thể bí danh với restricttừ khóa:

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

Sau đó, Clang sẽ phát ra một phiên bản tối ưu hơn của mã nhị phân:

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

Vì Rust đảm bảo (ngoại trừ mã không an toàn) rằng hai tham chiếu có thể thay đổi không thể bí danh, tôi sẽ nghĩ rằng trình biên dịch sẽ có thể phát ra phiên bản mã được tối ưu hóa hơn.

Khi tôi kiểm tra mã bên dưới và biên dịch nó rustc 1.35.0với -C opt-level=3 --emit obj,

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

nó tạo ra:

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

Điều này không tận dụng sự đảm bảo rằng abkhông thể bí danh.

Đây có phải là do trình biên dịch Rust hiện tại vẫn đang được phát triển và chưa kết hợp phân tích bí danh để thực hiện tối ưu hóa?

Đây có phải là vì vẫn còn cơ hội abcó thể bí danh, ngay cả trong Rust an toàn?



76
Nhận xét bên lề: " Vì Rust đảm bảo (ngoại trừ mã không an toàn) rằng hai tham chiếu có thể thay đổi không thể bí danh " - điều đáng nói là ngay cả trong unsafemã, các tham chiếu có thể thay đổi không được phép và dẫn đến hành vi không xác định. Bạn có thể có các con trỏ thô có răng cưa, nhưng unsafemã không thực sự cho phép bạn bỏ qua các quy tắc chuẩn của Rust. Đó chỉ là một quan niệm sai lầm phổ biến và do đó đáng để chỉ ra.
Lukas Kalbertodt

6
Tôi phải mất một thời gian để tìm hiểu xem ví dụ này là gì, bởi vì tôi không giỏi đọc asm, vì vậy trong trường hợp nó giúp được bất kỳ ai khác: nó sẽ hiểu rõ liệu hai +=thao tác trong cơ addsthể có thể được diễn giải lại như thế nào *a = *a + *b + *b. Nếu con trỏ không bí danh, chúng có thể, bạn thậm chí có thể thấy số tiền b* + *btrong danh sách mã asm thứ hai : 2: 01 c0 add %eax,%eax. Nhưng nếu họ làm bí danh, họ không thể, bởi vì khi bạn thêm *blần thứ hai, nó sẽ chứa một giá trị khác với lần đầu tiên (lần bạn lưu trữ trên 4:danh sách asm đầu tiên).
dlukes

Câu trả lời:


364

Rust ban đầu đã kích hoạt noaliasthuộc tính của LLVM , nhưng điều này gây ra mã sai . Khi tất cả các phiên bản LLVM được hỗ trợ không còn biên dịch mã, nó sẽ được bật lại .

Nếu bạn thêm -Zmutable-noalias=yesvào các tùy chọn trình biên dịch, bạn sẽ nhận được hội đồng dự kiến:

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

Nói một cách đơn giản, Rust đặt restricttừ khóa C tương đương ở mọi nơi , phổ biến hơn nhiều so với bất kỳ chương trình C thông thường nào. Điều này đã thực hiện các trường hợp góc của LLVM nhiều hơn khả năng xử lý chính xác. Hóa ra các lập trình viên C và C ++ đơn giản là không sử dụng restrictthường xuyên như &mutđược sử dụng trong Rust.

Điều này đã xảy ra nhiều lần .

  • Rust 1.0 đến 1.7 - noaliasđược bật
  • Rust 1.8 đến 1.27 - noaliasbị vô hiệu hóa
  • Rust 1.28 đến 1.29 - noaliasđược bật
  • Rỉ 1.30 qua ??? - noaliastàn tật

Các vấn đề liên quan đến Rust


12
Điều này không đáng ngạc nhiên. Mặc dù có nhiều tuyên bố về sự thân thiện đa ngôn ngữ, LLVM được thiết kế đặc biệt như một phụ trợ C ++ và nó luôn có xu hướng mạnh mẽ để bóp nghẹt những thứ không đủ như C ++.
Mason Wheeler

47
@MasonWheeler nếu bạn nhấp vào một số vấn đề, bạn có thể tìm thấy các ví dụ mã C sử dụng restrictvà sảy thai trên cả Clang và GCC. Nó không giới hạn ở các ngôn ngữ không đủ C ++, trừ khi bạn đếm chính C ++ trong nhóm đó .
Người quản lý

6
@MasonWheeler: Tôi không nghĩ LLVM thực sự được thiết kế xoay quanh các quy tắc của C hoặc C ++, mà là xung quanh các quy tắc của LLVM. Nó đưa ra các giả định thường đúng với mã C hoặc C ++, nhưng từ những gì tôi có thể nói thiết kế được đưa ra dựa trên mô hình phụ thuộc dữ liệu tĩnh không thể xử lý các trường hợp góc khó. Sẽ không sao nếu nó phụ thuộc dữ liệu một cách bi quan không thể bị bác bỏ, nhưng thay vào đó, nó xử lý như các hành động không hoạt động sẽ ghi lưu trữ với cùng một mẫu bit và có các phụ thuộc dữ liệu tiềm năng nhưng không thể chứng minh được đọc và viết
supercat

8
@supercat Tôi đã đọc bình luận của bạn một vài lần, nhưng tôi thừa nhận tôi đã bối rối - Tôi không biết họ phải làm gì với câu hỏi hoặc câu trả lời này. Hành vi không xác định không xuất hiện ở đây, đây là "chỉ" một trường hợp nhiều lượt tối ưu hóa tương tác kém với nhau.
Người quản lý

2
@avl_sweden nhắc lại, đó chỉ là một lỗi . Bước tối ưu hóa không kiểm soát vòng lặp đã làm (không?) Không hoàn toàn đưa noaliascon trỏ vào tài khoản khi thực hiện. Nó tạo ra các con trỏ mới dựa trên các con trỏ đầu vào, sao chép không đúng noaliasthuộc tính mặc dù các con trỏ mới đã làm bí danh.
Người quản lý
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.