Mức tối ưu hóa -O3 có nguy hiểm trong g ++ không?


232

Tôi đã nghe từ nhiều nguồn khác nhau (mặc dù chủ yếu là từ một đồng nghiệp của tôi), rằng việc biên dịch với mức tối ưu hóa -O3trong g ++ bằng cách nào đó là 'nguy hiểm', và nói chung nên tránh, trừ khi được chứng minh là cần thiết.

Điều này có đúng không, và nếu vậy, tại sao? Tôi có nên chỉ gắn bó -O2?


38
Nó chỉ nguy hiểm nếu bạn dựa vào hành vi không xác định. Và thậm chí sau đó tôi sẽ ngạc nhiên nếu chính mức độ tối ưu hóa đã làm rối tung mọi thứ.
Seth Carnegie

5
Trình biên dịch vẫn bị hạn chế để tạo ra một chương trình hoạt động "như thể" nó đã biên dịch mã của bạn một cách chính xác. Tôi không biết rằng -O3được coi là đặc biệt lỗi? Tôi nghĩ có lẽ nó có thể làm cho hành vi không xác định trở nên "tệ hơn" vì nó có thể làm những điều kỳ lạ và tuyệt vời dựa trên những giả định nhất định, nhưng đó sẽ là lỗi của chính bạn. Vì vậy, nói chung, tôi muốn nói rằng nó ổn.
BoBTFish

5
Đúng là mức độ tối ưu hóa cao hơn dễ bị lỗi trình biên dịch. Tôi đã tự đánh một vài trường hợp, nhưng nói chung chúng vẫn còn khá hiếm.
Bí ẩn

21
-O2bật -fstrict-aliasingvà nếu mã của bạn tồn tại thì có lẽ nó sẽ tồn tại các tối ưu hóa khác, vì đó là mã mà mọi người mắc lỗi nhiều lần. Điều đó nói rằng, -fpredictive-commoningchỉ có trong -O3và cho phép có thể kích hoạt các lỗi trong mã của bạn gây ra bởi các giả định không chính xác về đồng thời. Mã của bạn càng ít sai, tối ưu hóa càng ít nguy hiểm ;-)
Steve Jessop

6
@PlasmaHH, tôi không nghĩ "nghiêm ngặt hơn" là một mô tả hay -Ofast, nó tắt xử lý NaNs tuân thủ theo chuẩn IEEE chẳng hạn
Jonathan Wakely

Câu trả lời:


223

Trong những ngày đầu của gcc (2.8, v.v.) và trong thời đại của egcs, và redhat 2,96 -O3 đôi khi khá lỗi. Nhưng điều này đã hơn một thập kỷ trước và -O3 không khác nhiều so với các mức tối ưu hóa khác (trong lỗi).

Tuy nhiên, nó có xu hướng tiết lộ các trường hợp mọi người dựa vào hành vi không xác định, do phụ thuộc chặt chẽ hơn vào các quy tắc và đặc biệt là các trường hợp góc, của ngôn ngữ.

Như một lưu ý cá nhân, tôi đang chạy phần mềm sản xuất trong lĩnh vực tài chính trong nhiều năm nay với -O3 và chưa gặp phải lỗi nào nếu tôi sử dụng -O2.

Theo nhu cầu phổ biến, đây là một bổ sung:

-O3 và đặc biệt là các cờ bổ sung như -funroll-loop (không được bật bởi -O3) đôi khi có thể dẫn đến nhiều mã máy được tạo. Trong một số trường hợp nhất định (ví dụ: trên một cpu có bộ đệm lệnh L1 đặc biệt nhỏ), điều này có thể gây ra sự chậm lại do tất cả các mã của ví dụ như một số vòng lặp bên trong bây giờ không còn phù hợp với L1I nữa. Nói chung gcc cố gắng khá nhiều để không tạo ra quá nhiều mã, nhưng vì nó thường tối ưu hóa trường hợp chung, điều này có thể xảy ra. Các tùy chọn đặc biệt thiên về điều này (như hủy đăng ký vòng lặp) thường không được bao gồm trong -O3 và được đánh dấu tương ứng trong trang chủ. Vì vậy, nói chung nên sử dụng -O3 để tạo mã nhanh và chỉ quay lại -O2 hoặc -Os (cố gắng tối ưu hóa cho kích thước mã) khi thích hợp (ví dụ: khi trình lược tả chỉ ra L1I bỏ lỡ).

Nếu bạn muốn tối ưu hóa đến mức tối đa, bạn có thể điều chỉnh trong gcc thông qua - thay đổi chi phí liên quan đến tối ưu hóa nhất định. Ngoài ra, lưu ý rằng gcc hiện có khả năng đặt các thuộc tính tại các chức năng kiểm soát cài đặt tối ưu hóa chỉ cho các chức năng này, vì vậy khi bạn thấy bạn gặp vấn đề với -O3 trong một chức năng (hoặc muốn thử các cờ đặc biệt cho chỉ chức năng đó), bạn không cần phải biên dịch toàn bộ tệp hoặc thậm chí toàn bộ dự án với O2.

otoh có vẻ như phải cẩn thận khi sử dụng -Ofast, trong đó nêu rõ:

-Ofast cho phép tối ưu hóa tất cả -O3. Nó cũng cho phép tối ưu hóa không hợp lệ cho tất cả các chương trình tuân thủ tiêu chuẩn.

điều này khiến tôi kết luận rằng -O3 được dự định là tuân thủ đầy đủ các tiêu chuẩn.


2
Tôi chỉ sử dụng một cái gì đó như ngược lại. Tôi luôn sử dụng -Os hoặc -O2 (đôi khi O2 tạo ra một tệp thực thi nhỏ hơn) .. sau khi định hình tôi sử dụng O3 trên các phần của mã cần nhiều thời gian thực hiện hơn và một mình nó có thể tăng tốc độ lên tới 20%.
CoffeDeveloper

3
Tôi làm điều đó cho tốc độ. O3 hầu hết các lần làm cho mọi thứ chậm hơn. Không biết chính xác tại sao, tôi nghi ngờ nó gây ô nhiễm Hướng dẫn bộ đệm.
CoffeDeveloper

4
@DarioOO Tôi cảm thấy như nài nỉ "code bloat" là một việc phổ biến, nhưng tôi hầu như không bao giờ thấy nó được hỗ trợ với điểm chuẩn. Nó phụ thuộc rất nhiều vào kiến ​​trúc, nhưng mỗi lần tôi thấy các điểm chuẩn được công bố (ví dụ: phoronix.com/ ), nó cho thấy O3 nhanh hơn trong phần lớn các trường hợp. Tôi đã thấy phân tích hồ sơ và cẩn thận cần thiết để chứng minh rằng sự phình mã thực sự là một vấn đề và nó thường chỉ xảy ra đối với những người nắm lấy các mẫu theo một cách cực đoan.
Nir Friedman

1
@NirFriedman: Nó có xu hướng gặp sự cố khi mô hình chi phí nội tuyến của trình biên dịch có lỗi hoặc khi bạn tối ưu hóa cho một mục tiêu hoàn toàn khác so với khi bạn chạy. Điều này vô tình áp dụng cho tất cả các mức tối ưu hóa ...
PlasmaHH

1
@PlasmaHH: vấn đề sử dụng-cmov sẽ khó khắc phục cho trường hợp chung. Thông thường, bạn đã không chỉ sắp xếp dữ liệu của bạn, vì vậy khi gcc đang cố gắng quyết định xem một chi nhánh là có thể dự đoán hay không, phân tích tĩnh tìm kiếm các cuộc gọi đến std::sortchức năng này là khó có khả năng giúp đỡ. Sử dụng một cái gì đó như stackoverflow.com/questions/109710/ sẽ giúp hoặc có thể viết nguồn để tận dụng tính năng được sắp xếp: quét cho đến khi bạn thấy> = 128, sau đó bắt đầu tính tổng. Đối với mã cồng kềnh, vâng tôi dự định đi xung quanh để báo cáo nó. : P
Peter Cordes

42

Theo kinh nghiệm hơi bị kiểm tra của tôi, việc áp dụng -O3cho toàn bộ chương trình hầu như luôn làm cho nó chậm hơn (so với -O2), vì nó bật vòng lặp tích cực không kiểm soát và nội tuyến khiến chương trình không còn phù hợp với bộ đệm lệnh. Đối với các chương trình lớn hơn, điều này cũng có thể đúng với -O2tương đối -Os!

Mẫu sử dụng dự định -O3là, sau khi định hình chương trình của bạn, bạn áp dụng thủ công nó cho một số ít tệp chứa các vòng lặp quan trọng bên trong thực sự có lợi từ sự đánh đổi không gian tốc độ mạnh mẽ này. Các phiên bản mới hơn của GCC có chế độ tối ưu hóa theo hướng dẫn cấu hình có thể (IIUC) áp dụng có chọn lọc các -O3tối ưu hóa cho các chức năng nóng - tự động hóa hiệu quả quá trình này.


10
"gần như luôn luôn"? Làm cho nó "50-50", và chúng tôi sẽ có một thỏa thuận ;-).
No-Bugs Hare

12

Tùy chọn -O3 bật tối ưu hóa đắt tiền hơn, chẳng hạn như nội tuyến, ngoài tất cả các tối ưu hóa ở mức thấp hơn '-O2' và '-O1'. Mức tối ưu hóa '-O3' có thể tăng tốc độ thực thi kết quả, nhưng cũng có thể tăng kích thước của nó. Trong một số trường hợp khi các tối ưu hóa này không thuận lợi, tùy chọn này thực sự có thể làm cho chương trình chậm hơn.


3
Tôi hiểu rằng một số "tối ưu hóa rõ ràng" có thể làm cho chương trình chậm hơn, nhưng bạn có nguồn nào cho rằng GCC -O3 đã làm cho chương trình chậm hơn không?
Vịt Mooing

1
@MooingDuck: Mặc dù tôi không thể trích dẫn nguồn, tôi nhớ đã gặp phải trường hợp như vậy với một số bộ xử lý AMD cũ hơn có bộ đệm L1I khá nhỏ (~ 10k hướng dẫn). Tôi chắc chắn google có nhiều thứ cho người quan tâm, nhưng đặc biệt là các tùy chọn như hủy đăng ký vòng lặp không phải là một phần của O3 và những kích thước này tăng lên rất nhiều. -Os là một trong những khi bạn muốn làm cho thực thi nhỏ nhất. Thậm chí -O2 có thể tăng kích thước mã. Một công cụ tuyệt vời để chơi với kết quả của các mức tối ưu hóa khác nhau là gcc explorer.
PlasmaHH

@PlasmaHH: Trên thực tế, kích thước bộ đệm nhỏ xíu là thứ mà trình biên dịch có thể làm hỏng, điểm tốt. Đó là một ví dụ thực sự tốt. Hãy đặt nó trong câu trả lời.
Vịt Mooing

1
@PlasmaHH Pentium III có bộ đệm mã 16KB. K6 của AMD trở lên thực sự có bộ đệm lệnh 32KB. P4 bắt đầu với giá trị khoảng 96KB. Core I7 thực sự có bộ đệm mã LK 32KB. Bộ giải mã hướng dẫn rất mạnh hiện nay, vì vậy L3 của bạn đủ tốt để quay trở lại cho hầu hết mọi vòng lặp.
doug65536

1
Bạn sẽ thấy hiệu suất tăng lên bất cứ khi nào có một hàm được gọi trong một vòng lặp và nó có thể loại bỏ đáng kể sự loại bỏ phổ biến đáng kể và nâng các phép tính toán không cần thiết ra khỏi hàm trước vòng lặp.
doug65536

8

Vâng, O3 là buggier. Tôi là nhà phát triển trình biên dịch và tôi đã xác định các lỗi gcc rõ ràng và rõ ràng do O3 tạo ra các hướng dẫn lắp ráp SIMD có lỗi khi xây dựng phần mềm của riêng tôi. Từ những gì tôi đã thấy, hầu hết các phần mềm sản xuất đều có O2, điều đó có nghĩa là O3 sẽ ít được kiểm tra và sửa lỗi.

Hãy nghĩ về nó theo cách này: O3 thêm nhiều biến đổi trên đỉnh của O2, trong đó thêm nhiều biến đổi trên đầu O1. Nói theo thống kê, nhiều biến đổi có nghĩa là nhiều lỗi hơn. Điều đó đúng với bất kỳ trình biên dịch nào.


3

Gần đây tôi gặp một vấn đề sử dụng tối ưu hóa với g++. Vấn đề liên quan đến thẻ PCI, trong đó các thanh ghi (cho lệnh và dữ liệu) đã được gửi lại bởi một địa chỉ bộ nhớ. Trình điều khiển của tôi đã ánh xạ địa chỉ vật lý tới một con trỏ trong ứng dụng và đưa nó vào quy trình được gọi, hoạt động với nó như thế này:

unsigned int * pciMemory;
askDriverForMapping( & pciMemory );
...
pciMemory[ 0 ] = someCommandIdx;
pciMemory[ 0 ] = someCommandLength;
for ( int i = 0; i < sizeof( someCommand ); i++ )
    pciMemory[ 0 ] = someCommand[ i ];

Thẻ không hoạt động như mong đợi. Khi tôi nhìn thấy hội đồng tôi hiểu rằng trình biên dịch chỉ ghi someCommand[ the last ]vào pciMemory, bỏ qua tất cả các ghi trước đó.

Tóm lại: hãy chính xác và chú ý với việc tối ưu hóa.


38
Nhưng vấn đề ở đây là chương trình của bạn đơn giản là có hành vi không xác định; Trình tối ưu hóa không làm gì sai. Đặc biệt bạn cần khai báo pciMemoryvolatile.
Konrad Rudolph

11
Đó thực sự không phải là UB nhưng trình biên dịch nằm trong quyền bỏ qua tất cả ngoại trừ lần ghi cuối cùng pciMemorybởi vì tất cả các ghi khác có thể chứng minh là không có hiệu lực. Đối với trình tối ưu hóa đó là tuyệt vời bởi vì nó có thể loại bỏ nhiều hướng dẫn vô ích và tốn thời gian.
Konrad Rudolph

4
Tôi đã tìm thấy điều này trong tiêu chuẩn (sau hơn 10 năm))) - Một tuyên bố dễ bay hơi có thể được sử dụng để mô tả một đối tượng tương ứng với cổng đầu vào / đầu ra được ánh xạ bộ nhớ hoặc một đối tượng được truy cập bởi chức năng ngắt không đồng bộ. Các hành động trên các đối tượng được khai báo sẽ không được '' tối ưu hóa '' bởi việc triển khai hoặc sắp xếp lại trừ khi được cho phép bởi các quy tắc để đánh giá biểu thức.
borvdn

2
@borvdn Hơi lạc đề nhưng làm sao bạn biết rằng thiết bị của bạn đã nhận lệnh trước khi gửi lệnh mới?
dùng877329

3
@ user877329 Tôi đã thấy nó bởi hành vi của thiết bị, nhưng đó là một nhiệm vụ tuyệt vời
borboln
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.