Tại sao các trình biên dịch C ++ không tối ưu hóa phép gán boolean có điều kiện này như một phép gán vô điều kiện?


117

Hãy xem xét chức năng sau:

void func(bool& flag)
{
    if(!flag) flag=true;
}

Đối với tôi, có vẻ như nếu cờ có giá trị boolean hợp lệ, điều này sẽ tương đương với việc đặt nó thành true , như thế này:

void func(bool& flag)
{
    flag=true;
}

Tuy nhiên, cả gcc và clang đều không tối ưu hóa nó theo cách này - cả hai đều tạo ra những điều sau đây tại -O3 cấp độ tối ưu hóa:

_Z4funcRb:
.LFB0:
    .cfi_startproc
    cmp BYTE PTR [rdi], 0
    jne .L1
    mov BYTE PTR [rdi], 1
.L1:
    rep ret

Câu hỏi của tôi là: có phải mã quá đặc biệt để tối ưu hóa không, hay có bất kỳ lý do chính đáng nào khiến việc tối ưu hóa như vậy không mong muốn, vì điều đó flagkhông phải là tham chiếu đến volatile? Có vẻ như lý do duy nhất mà có thể là flagbằng cách nào đó có thể có một phi true-hoặc- falsegiá trị mà không cần xác định hành vi tại thời điểm đọc nó, nhưng tôi không chắc liệu này là có thể.


8
Bạn có bằng chứng nào cho thấy đó là một "tối ưu hóa" không?
David Schwartz

1
@ 200_success Tôi không nghĩ rằng đặt một dòng mã có đánh dấu không hoạt động làm tiêu đề là một điều tốt. Nếu bạn muốn một tiêu đề cụ thể hơn, tốt nhưng hãy chọn một câu tiếng Anh , và cố gắng tránh mã trong đó (ví dụ: tại sao trình biên dịch không tối ưu hóa các viết có điều kiện thành viết không điều kiện khi họ có thể chứng minh chúng tương đương hoặc tương tự). Ngoài ra, vì backticks không được hiển thị nên không sử dụng chúng trong tiêu đề ngay cả khi bạn sử dụng mã.
Bakuriu

2
@Ruslan, mặc dù nó dường như không thực hiện việc tối ưu hóa này cho chính hàm, khi nó có thể nội dòng mã, nó dường như làm như vậy đối với phiên bản nội tuyến. Thường chỉ dẫn đến một hằng số thời gian biên dịch 1được sử dụng. godbolt.org/g/swe0tc
Evan Teran

Câu trả lời:


102

Điều này có thể tác động tiêu cực đến hiệu suất của chương trình do cân nhắc tính nhất quán của bộ nhớ cache . Việc ghi vào flagmỗi lần func()được gọi sẽ làm bẩn dòng bộ đệm chứa. Điều này sẽ xảy ra bất kể thực tế là giá trị được ghi khớp chính xác với các bit được tìm thấy tại địa chỉ đích trước khi ghi.


BIÊN TẬP

hvd đã cung cấp một lý do chính đáng khác ngăn cản việc tối ưu hóa như vậy. Đó là một lập luận thuyết phục hơn chống lại tối ưu hóa được đề xuất, vì nó có thể dẫn đến hành vi không xác định, trong khi câu trả lời (ban đầu) của tôi chỉ giải quyết các khía cạnh hiệu suất.

Sau khi suy nghĩ kỹ hơn một chút, tôi có thể đề xuất thêm một ví dụ tại sao nên cấm mạnh mẽ các trình biên dịch - trừ khi họ có thể chứng minh rằng việc chuyển đổi là an toàn cho một ngữ cảnh cụ thể - từ việc giới thiệu văn bản vô điều kiện. Hãy xem xét mã này:

const bool foo = true;

int main()
{
    func(const_cast<bool&>(foo));
}

Với việc ghi vô điều kiện trong func()điều này chắc chắn sẽ kích hoạt hành vi không xác định (ghi vào bộ nhớ chỉ đọc sẽ kết thúc chương trình, ngay cả khi tác động của việc ghi nếu không sẽ là cấm).


7
Nó cũng có thể tác động tích cực đến hiệu suất kể từ khi bạn loại bỏ một nhánh. Vì vậy, tôi không nghĩ rằng trường hợp cụ thể này có ý nghĩa để thảo luận mà không có một hệ thống cụ thể.
Lundin

3
Tính năng xác định hành vi @Yakk không bị ảnh hưởng bởi nền tảng đích. Nói rằng nó sẽ chấm dứt chương trình là không chính xác, nhưng chính UB có thể gây ra những hậu quả sâu rộng, bao gồm cả quỷ mũi.
John Dvorak

16
@Yakk Điều đó phụ thuộc vào ý nghĩa của "bộ nhớ chỉ đọc". Không, nó không nằm trong chip ROM, nhưng nó thường nằm trong một phần được tải đến một trang không được bật quyền ghi và bạn sẽ nhận được ví dụ: tín hiệu SIGSEGV hoặc ngoại lệ STATUS_ACCESS_VIOLATION khi bạn cố gắng ghi vào nó.
Ngẫu nhiên832

5
"điều này chắc chắn gây ra hành vi không xác định". Không. Hành vi không xác định là một thuộc tính của máy trừu tượng. Chính những gì mã nói sẽ xác định xem UB có hiện diện hay không. Trình biên dịch không thể gây ra nó (mặc dù nếu lỗi trình biên dịch có thể khiến chương trình hoạt động không chính xác).
Eric M Schmidt

7
Đó là việc truyền đi constđể truyền vào một hàm có thể sửa đổi dữ liệu là nguồn gốc của hành vi không xác định, không phải là ghi vô điều kiện. Bác sĩ, một điều đau khổ khi tôi làm điều này ....
Spencer

48

Ngoài câu trả lời của Leon về hiệu suất:

Giả sử flagtrue. Giả sử hai luồng liên tục gọi func(flag). Hàm như đã viết, trong trường hợp đó, không lưu trữ bất cứ thứ gì vào flag, vì vậy điều này phải an toàn theo luồng. Hai luồng truy cập vào cùng một bộ nhớ, nhưng chỉ để đọc nó. Vô điều kiện thiết flagđể truephương tiện hai chủ đề khác nhau sẽ được viết với cùng bộ nhớ. Điều này không an toàn, điều này không an toàn ngay cả khi dữ liệu được ghi giống hệt với dữ liệu đã có.


9
Tôi nghĩ đây là một kết quả của việc áp dụng [intro.races]/21.
Griwes

10
Rất thú vị. Vì vậy, tôi đọc điều này là: Trình biên dịch không bao giờ được phép "tối ưu hóa" trong một hoạt động ghi mà máy trừu tượng sẽ không có.
Martin Ba

3
@MartinBa Chủ yếu là như vậy. Nhưng nếu trình biên dịch có thể chứng minh rằng điều đó không quan trọng, chẳng hạn vì nó có thể chứng minh rằng không có luồng nào khác có thể có quyền truy cập vào biến cụ thể đó, thì nó có thể không sao.

13
Điều này chỉ không an toàn nếu hệ thống mà trình biên dịch đang nhắm mục tiêu làm cho nó không an toàn . Tôi chưa bao giờ phát triển trên một hệ thống mà việc ghi 0x01vào một byte đã 0x01gây ra hành vi "không an toàn". Trên hệ thống có truy cập bộ nhớ từ hoặc từ khóa, nó sẽ; nhưng trình tối ưu hóa nên biết điều này. Trên PC hoặc hệ điều hành điện thoại hiện đại, không có sự cố nào xảy ra. Vì vậy, đây không phải là một lý do hợp lệ.
Yakk - Adam Nevraumont.

4
@Yakk Thực ra, suy nghĩ kỹ hơn nữa, tôi nghĩ rằng điều này là đúng, ngay cả đối với các bộ vi xử lý thông thường. Tôi nghĩ bạn đúng khi CPU có thể ghi trực tiếp vào bộ nhớ, nhưng giả sử flaglà trong một trang copy-on-write. Bây giờ, ở cấp độ CPU, hành vi có thể được xác định (lỗi trang, hãy để hệ điều hành xử lý), nhưng ở cấp độ hệ điều hành, nó có thể vẫn chưa được xác định, phải không?

13

Tôi không chắc chắn về hành vi của C ++ ở đây, nhưng trong C bộ nhớ có thể thay đổi bởi vì nếu bộ nhớ chứa giá trị khác 0 khác 1, nó sẽ không thay đổi với dấu kiểm, nhưng được thay đổi thành 1 với dấu kiểm.

Nhưng vì tôi không thông thạo lắm về C ++ nên tôi không biết liệu tình huống này có thể xảy ra hay không.


Điều này có còn đúng _Boolkhông?
Ruslan

5
Trong C, nếu bộ nhớ chứa một giá trị mà ABI không cho biết là hợp lệ cho kiểu của nó, thì đó là biểu diễn bẫy và việc đọc biểu diễn bẫy là hành vi không xác định. Trong C ++, điều này chỉ có thể xảy ra khi đọc một đối tượng chưa được khởi tạo và nó đang đọc một đối tượng chưa được khởi tạo đó là UB. Nhưng nếu bạn có thể tìm thấy một ABI cho biết bất kỳ giá trị nào khác 0 đều hợp lệ cho loại bool/ _Boolvà phương tiện true, thì trong ABI cụ thể đó, có lẽ bạn đã đúng.

1
@Ruslan Với các trình biên dịch sử dụng Itanium ABI và trên các bộ xử lý ARM, C _Boolvà C ++ boollà cùng một loại hoặc các loại tương thích tuân theo các quy tắc giống nhau. Với MSVC, chúng có cùng kích thước và sự liên kết, nhưng không có tuyên bố chính thức nào về việc chúng có sử dụng các quy tắc giống nhau hay không.
Thời gian Justin - Phục hồi Monica

1
@JustinTime: C <stdbool.h>bao gồm một typedef _Bool bool; Và có, trên x86 (ít nhất là trong Hệ thống V ABI), bool/ _Boolđược yêu cầu là 0 hoặc 1, với các bit trên của byte bị xóa. Tôi không nghĩ lời giải thích này là hợp lý.
Peter Cordes

1
@JustinTime: Đúng vậy, tôi nên chỉ ra rằng nó chắc chắn có cùng ngữ nghĩa trong tất cả các phiên bản x86 của System V ABI, đó là câu hỏi này. (Tôi có thể nói vì đối số đầu tiên funcđược chuyển trong RDI, trong khi Windows sẽ sử dụng RDX).
Peter Cordes
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.