Theo như kiến thức tối ưu (giới hạn) của tôi về tối ưu hóa, tôi luôn nghĩ rằng tối ưu hóa trình biên dịch không quan trọng là số nhiều. Ý tôi là, chúng ta chắc chắn có thể đạt được một vài phần trăm (có lẽ là 0 ... 100% như là một suy nghĩ sơ bộ rất thô sơ) về thời gian bằng cách sử dụng thanh ghi thay vì bộ nhớ và bỏ vòng, nhưng yếu tố cơ bản làm hạn chế hiệu suất của mã thực sự là sự lựa chọn của các thuật toán.Mã chạy nhanh hơn khi kích hoạt tối ưu hóa. Tui bỏ lỡ điều gì vậy?
Gần đây tôi đã bắt đầu một dự án thú cưng - một ngôn ngữ kịch bản nhỏ được giải thích. Nó biên dịch sang bytecode và thực hiện nó bằng cách sử dụng một máy ảo. Để dễ dàng gỡ lỗi, lược tả và thử nghiệm, trước tiên tôi đã tự nhiên biên dịch mã bằng các cờ -O0 -g
của gcc
(và clang
), sau đó với -O2
. Tôi đã sau đó time
'da chương trình nhỏ chạy trên máy ảo, mà về cơ bản thực hiện điều này (pseudo-code, tôi không hiển thị cho bạn các cú pháp thực tế cho đến khi dự án đi công cộng):
i = 1
sum = 0
while (i < 10000000) {
sum = (sum + i * i) mod 1000000
i++
}
print sum
này dịch đại khái để lắp ráp giả sau đây:
load r0, 0 # sum = 0
load r1, 1 # i = 1
load r2, 1000000
load r3, 10000000
loop:
mul r4, r1, r1 # i * i
add r0, r0, r4 # sum += i * i
mod r0, r0, r2 # sum %= 1000000
inc r1
gt r5, r1, r3 # if i > 10000000
jz r5, loop # then don't goto loop
ret r0
Về cơ bản, đây là vòng lặp chặt chẽ với 10000.000 lần lặp. time
báo cáo rằng nó chạy 0,47 ... 0,52 giây khi được biên dịch với -O2
, trong 1,51 ... 1,74 giây khi được biên dịch với -O0 -g
và trong 3,16 ... 3,47 giây khi lược tả (-pg
) cũng được bật.
Như bạn có thể thấy, có sự khác biệt 7 lần giữa thời gian thực hiện nhanh nhất và chậm nhất.
Điều này tự nó không phải là đáng ngạc nhiên, bởi vì tôi biết rằng thông tin gỡ lỗi bổ sung và thiếu tối ưu hóa nhỏ thực sự làm cho mã chạy chậm hơn, nhưng bây giờ là phần thú vị. Để có một nhận thức thực tế hơn về những gì thực sự xảy ra, tôi đã chơi cùng một trò chơi với Lua 5.2. Quan trọng xung quanh với Makefile
, tôi đã tìm thấy rằng chương trình Lua rất giống nhau:
local sum = 0
for i = 1, 10000000 do
sum = (sum + i * i) % 1000000
end
print(sum)
chạy trong khoảng 0,8 ... 0,87 giây khi Lua được biên soạn với -O0 -g -pg
, và trong 0,39 ... 0,43 giây khi chỉ -O2
được bật. Vì vậy, mã của tôi dường như có tốc độ tăng gấp 7 lần khi trình tối ưu hóa thực hiện các sửa lỗi phức tạp, trong khi việc triển khai tham chiếu Lua dường như được hưởng lợi từ những cải tiến này ít hơn nhiều.
Bây giờ câu hỏi của tôi là:
Bất cứ ý tưởng tại sao điều này xảy ra? Tôi nghi ngờ rằng lý do chính là những người sáng tạo của Lua thông minh hơn tôi và biết rõ hơn về trình biên dịch và máy ảo của họ đang làm gì.
Tôi cũng bắt gặp suy nghĩ của mình "điều này phải được tối ưu hóa sớm, hãy để tôi chỉ chuyển nó cho trình tối ưu hóa, nó sẽ thực hiện công việc" vài lần. Chúng bao gồm các hàm tĩnh một dòng được gọi trong việc thực hiện hầu hết các lệnh VM (tôi nghĩ rằng chúng sẽ được inlined khi cần), sử dụng các biểu thức phức tạp khác nhau (đôi khi với các hiệu ứng phụ không dễ dự đoán) có thể được đơn giản hóa, mặc dù.Thái độ này của tôi có được tính không? (Và đó có phải là thái độ đúng đắn không?)
Và dù sao, tôi có nên lo lắng về hiện tượng này không? Mã của tôi chạy chậm hơn 1,5 lần so với Lua, sau khi tất cả - và đó là khá tốt (ít nhất là nó đủ tốt cho tôi). Tôi có nên cố gắng cải thiện hiệu suất của việc xây dựng gỡ lỗi chỉ vì không làm như vậy chỉ ra rằng tôi thiếu kiến thức thân mật của mã của riêng tôi? Hoặc có thể tôi chỉ quên nó hoàn toàn xa như việc phát hành (tối ưu) xây dựng là đủ nhanh?
(Nếu câu hỏi này sẽ là một sự phù hợp tốt hơn cho Prog.SE hoặc CodeReview, cho tôi biết và tôi sẽ di chuyển nó.)
7x không phải là thứ tự cường độ nhanh hơn trong thế giới tập trung thập phân. –
Bạn đã xem mã lắp ráp cho vòng lặp của mình với cờ -O0 và -O2 chưa? Nó sẽ giải thích rất nhiều (tôi nghĩ rằng gcc thậm chí không sử dụng sổ đăng ký tích cực với -O0). Đối với tôi, tôi sẽ không lo lắng nhiều về tốc độ của mã được biên dịch mà không tối ưu hóa. Về nội tuyến: nó phụ thuộc vào trình biên dịch, và tôi đồng ý với bạn: suy nghĩ của nó là tối ưu hóa sớm, hồ sơ mã cuối cùng của bạn và chơi với nội tuyến sau đó. – Inspired
Thật khó để nói mà không có nhiều chi tiết hơn. Tôi cũng nghi ngờ điểm số 1 của bạn có liên quan. Trình biên dịch sẽ làm những việc như di chuyển vòng lặp bất biến ra khỏi vòng lặp, loại bỏ mã chết, sắp xếp lại các câu lệnh để kết hợp bộ nhớ cache tốt hơn và thời gian bộ nhớ, v.v.Có thể (thậm chí có khả năng) rằng những người sáng tạo của Lua rất ý thức về những hiệu ứng này và ghi nhớ chúng bằng máy ảo của riêng chúng. –