GCC sử dụng các thủ thuật nền tảng cụ thể để tránh các hoạt động nguyên tử hoàn toàn trên đường dẫn nhanh, tận dụng thực tế là nó có thể phân tích static
tốt hơn so với call_once hoặc kiểm tra kép.
Vì kiểm tra kép sử dụng nguyên tử làm phương pháp tránh trường hợp đua, nên phải trả giá mỗi lần mua. Nó không phải là một mức giá cao, nhưng đó là một mức giá.
Nó phải trả tiền này bởi vì nguyên tử phải duy trì nguyên tử trong mọi trường hợp, thậm chí các hoạt động khó như trao đổi so sánh. Điều này làm cho nó rất khó để tối ưu hóa. Nói chung, trình biên dịch phải để nó trong, chỉ trong trường hợp bạn sử dụng biến cho nhiều hơn chỉ là một khóa đôi. Nó không có cách nào dễ dàng chứng minh rằng bạn không bao giờ sử dụng một trong những hoạt động phức tạp hơn trên nguyên tử của bạn.
Mặt khác, static
là chuyên môn cao và là một phần của ngôn ngữ. Nó được thiết kế, ngay từ đầu, rất dễ khởi tạo. Theo đó, trình biên dịch có thể thực hiện các lối tắt không có sẵn cho phiên bản chung hơn. The compiler actually emits đoạn mã sau cho một tĩnh:
một chức năng đơn giản:
void foo() {
static X x;
}
được viết lại bên trong GCC để:
void foo() {
static X x;
static guard x_is_initialized;
if (__cxa_guard_acquire(x_is_initialized)) {
X::X();
x_is_initialized = true;
__cxa_guard_release(x_is_initialized);
}
}
nào trông rất giống một khóa kiểm tra lại. Tuy nhiên, trình biên dịch được ăn gian một chút ở đây. Nó biết người dùng không bao giờ có thể viết trực tiếp bằng cách sử dụng trực tiếp cxa_guard
. Nó biết rằng nó chỉ được sử dụng trong các trường hợp đặc biệt mà trình biên dịch chọn sử dụng nó. Do đó, với thông tin bổ sung đó, nó có thể tiết kiệm thời gian. Các thông số kỹ thuật bảo vệ CXA, như được phân phối như chúng, tất cả chia sẻ common rule: __cxa_guard_acquire
sẽ không bao giờ sửa đổi byte đầu tiên của bộ bảo vệ và __cxa_guard__release
sẽ đặt nó thành khác 0.
Điều này có nghĩa là mỗi người bảo vệ phải đơn điệu, và nó chỉ định chính xác những hoạt động sẽ làm như thế.Theo đó nó có thể tận dụng lợi thế của bảo vệ trường hợp chủng tộc hiện có trong nền tảng máy chủ. Ví dụ, trên x86, bảo vệ LL/SS được bảo đảm bởi các CPU được đồng bộ hóa mạnh trở thành đủ để thực hiện mẫu lấy/giải phóng này, vì vậy nó có thể thực hiện raw đọc byte đầu tiên đó khi nó khóa kép, thay vì đọc. Điều này chỉ có thể bởi vì GCC không sử dụng API nguyên tử C++ để thực hiện khóa kép của nó - nó đang sử dụng một platform specific approach.
GCC không thể tối ưu hóa nguyên tử trong trường hợp chung. Trên các kiến trúc được thiết kế để được đồng bộ hóa ít hơn (chẳng hạn như được thiết kế cho 1024 lõi), GCC không dựa vào kiến trúc để làm LL/SS cho nó. Vì vậy GCC buộc phải thực sự phát ra nguyên tử. Tuy nhiên, trên các nền tảng phổ biến như x86 và x64, nó có thể nhanh hơn.
call_once
có thể có hiệu quả thống kê của GCC, vì nó giới hạn tương tự số lượng hoạt động có thể được thực hiện cho once_flag
đến một phần của các hàm có thể được áp dụng cho nguyên tử. Sự cân bằng là các statics thuận tiện hơn nhiều khi sử dụng, khi chúng được áp dụng, nhưng call_once
hoạt động trong nhiều trường hợp không có đủ số liệu thống kê (chẳng hạn như once_flag
thuộc sở hữu của một đối tượng được tạo động).
Có sự khác biệt nhỏ về hiệu suất giữa tĩnh và call_once
trên các nền tảng cao hơn này. Nhiều người trong số các nền tảng này, trong khi không cung cấp LL/SS, ít nhất sẽ cung cấp đọc không rách của một số nguyên. Các nền tảng này có thể sử dụng điều này và một con trỏ cụ thể theo chuỗi, để thực hiện per-thread epoch counting to avoid atomics. Điều này là đủ cho tĩnh hoặc call_once
, nhưng phụ thuộc vào quầy không lăn qua. Nếu bạn không có số nguyên 64 bit không bị rách, call_once
phải lo lắng về việc di chuột qua. Việc thực hiện có thể hoặc không thể lo lắng về điều này. Nếu nó bỏ qua vấn đề này, nó có thể nhanh như statics. Nếu nó chú ý đến vấn đề đó, nó phải chậm như nguyên tử. Tĩnh biết tại thời gian biên dịch bao nhiêu biến tĩnh/khối có, do đó, nó có thể chứng minh không có rollover tại thời gian biên dịch (hoặc ít nhất là darn tự tin!)
Hãy cảnh báo rằng VC++ đang chạy phía sau về chức năng an toàn thread địa phương thống kê. Họ không có trong VS2013. Nhưng được báo cáo là trong VS2014: http://blogs.msdn.com/b/vcblog/archive/2014/06/11/c-11-14-feature-tables-for-visual-studio-14-ctp1. aspx –
Mặt khác, GCC có thể làm cho statics địa phương nhanh hơn call_once hoặc kiểm tra kép bởi vì nó có thể sử dụng các thủ thuật nền tảng cụ thể để tránh bất kỳ hoạt động nguyên tử nào cả. –
@CortAmmon Nếu bạn đăng câu trả lời đó bằng một số bằng chứng tôi chấp nhận. – Praxeolitic