tl; dr doNotOptimizeAway
tạo "sử dụng" nhân tạo.
Một chút thuật ngữ ở đây: "def" ("định nghĩa") là một câu lệnh gán giá trị cho biến; "use" là một câu lệnh, sử dụng giá trị của một biến để thực hiện một số thao tác.
Nếu từ điểm ngay sau khi thoát, tất cả các đường dẫn đến thoát chương trình sẽ không gặp phải việc sử dụng biến số, lỗi đó được gọi là dead
và vé loại bỏ mã chết (DCE) sẽ xóa nó. Mà lần lượt có thể gây ra các defs khác để trở thành chết (nếu def đó là một sử dụng bởi đức hạnh của có operands biến), vv
Hãy tưởng tượng chương trình sau khi thay thế Scalar tổng hợp (SRA), biến địa phương std::vector
trong hai biến số len
và ptr
. Tại một số điểm chương trình gán một giá trị cho ptr
; tuyên bố đó là một def.
Hiện tại, chương trình gốc không làm bất kỳ điều gì với vectơ; nói cách khác không có bất kỳ việc sử dụng nào là len
hoặc ptr
. Do đó, tất cả các defs của họ đã chết và DCE có thể loại bỏ chúng, có hiệu quả loại bỏ tất cả các mã và làm cho các điểm chuẩn vô giá trị.
Thêm doNotOptimizeAway(ptr)
tạo sử dụng nhân tạo, ngăn DCE xóa các lỗi. (Là một lưu ý phụ, tôi thấy không có điểm trong "+", "g" nên đã đủ).
Một dòng tương tự của lý luận có thể được theo sau với tải bộ nhớ và cửa hàng: một cửa hàng (def) là chết iff không có đường dẫn đến cuối chương trình, có chứa tải (một sử dụng) từ vị trí cửa hàng đó. Khi theo dõi các vị trí bộ nhớ tùy ý khó hơn việc theo dõi các biến đăng ký giả riêng lẻ, lý do trình biên dịch bảo thủ - một cửa hàng bị chết nếu không có đường dẫn đến cuối chương trình, có thể có thể là gặp phải việc sử dụng cửa hàng đó.
Một trường hợp như vậy, là một cửa hàng đến một vùng bộ nhớ, được đảm bảo không được đặt bí danh - sau khi bộ nhớ được deallocated, có thể không có thể được sử dụng của cửa hàng đó, mà không kích hoạt hành vi không xác định. IOW, không có sử dụng như vậy.
Do đó trình biên dịch có thể loại bỏ v.push_back(42)
. Nhưng có escape
- nó làm cho v.data()
được coi là bí danh tùy ý, như @Leon được mô tả ở trên.
Mục đích của clobber()
trong ví dụ này là tạo ra một sử dụng nhân tạo của tất cả bộ nhớ bí danh. Chúng tôi có một cửa hàng (từ push_back(42)
), cửa hàng là một địa điểm được đặt biệt hiệu toàn cầu (do escape(v.data())
), do đó clobber()
có khả năng chứa việc sử dụng cửa hàng đó (IOW, hiệu ứng bên cửa hàng sẽ được quan sát), do đó trình biên dịch không được phép xóa cửa hàng.
Một vài ví dụ đơn giản:
Ví dụ I:
void f() {
int v[1];
v[0] = 42;
}
này không tạo ra bất kỳ mã.
Ví dụ II:
extern void g();
void f() {
int v[1];
v[0] = 42;
g();
}
này tạo ra chỉ là một cuộc gọi đến g()
, không có bộ nhớ lưu trữ. Hàm g
không thể truy cập v
vì v
không được đặt bí danh.
Ví dụ III:
void clobber() {
__asm__ __volatile__ ("" : : : "memory");
}
void f() {
int v[1];
v[0] = 42;
clobber();
}
Giống như trong ví dụ trước, không có cửa hàng tạo vì v
không aliased và cuộc gọi đến clobber
được inlined không có gì.
Ví dụ IV:
template<typename T>
void use(T &&t) {
__asm__ __volatile__ ("" :: "g" (t));
}
void f() {
int v[1];
use(v);
v[0] = 42;
}
Lần này v
thoát (ví dụ: có thể có khả năng truy cập từ khung kích hoạt khác). Tuy nhiên, cửa hàng vẫn bị xóa, vì sau khi nó không có khả năng sử dụng bộ nhớ đó (không có UB).
Ví dụ V:
template<typename T>
void use(T &&t) {
__asm__ __volatile__ ("" :: "g" (t));
}
extern void g();
void f() {
int v[1];
use(v);
v[0] = 42;
g(); // same with clobber()
}
Và cuối cùng chúng tôi nhận được cửa hàng, bởi vì v
thoát và trình biên dịch phải thận trọng cho rằng cuộc gọi đến g
có thể truy cập các giá trị được lưu trữ.
(đối với thử nghiệm https://godbolt.org/g/rFviMI)
Thực ra, "+ r" có nghĩa là đoạn mã sẽ đọc và ghi dữ liệu. Vì trình biên dịch (gcc) không thể biết làm thế nào datum có thể được sử dụng/sửa đổi bởi asm, nó không thể tối ưu hóa nó đi trong bất kỳ trường hợp nào. –
Chức năng đó được tích hợp sẵn vào google benchmark ngay bây giờ. điểm chuẩn :: DoNotOptimize như được thấy ở đây: github.com/google/benchmark – xaxxon