Vì vậy, tôi đã có một cái nhìn tại một số ma thuật đó là O3
trong GCC (thực sự tôi đang biên dịch bằng cách sử dụng Clang nhưng nó giống với GCC và tôi đoán một phần lớn của trình tối ưu hóa đã được kéo từ GCC đến Clang).Trình biên dịch tối ưu hóa chức năng giai thừa này như thế nào?
xem xét chương trình C này:
int foo(int n) {
if (n == 0) return 1;
return n * foo(n-1);
}
int main() {
return foo(10);
}
Điều đầu tiên tôi đã khá WOW-ed tại (mà cũng là WOW-ed ở trong câu hỏi này - https://stackoverflow.com/a/414774/1068248) là cách int foo(int)
(một chức năng thừa cơ bản) biên dịch vào một vòng lặp chặt chẽ. Đây là hội đồng ARM cho nó:
.globl _foo
.align 2
.code 16
.thumb_func _foo
_foo:
mov r1, r0
movs r0, #1
cbz r1, LBB0_2
LBB0_1:
muls r0, r1, r0
subs r1, #1
bne LBB0_1
LBB0_2:
bx lr
Blimey tôi nghĩ. Điều đó khá thú vị! Vòng lặp hoàn toàn chặt chẽ để làm giai thừa. WOW. Nó không phải là một tối ưu hóa cuộc gọi đuôi kể từ khi, tốt, nó không phải là một cuộc gọi đuôi. Nhưng nó dường như đã thực hiện một tối ưu hóa tương tự.
Bây giờ nhìn vào main
:
.globl _main
.align 2
.code 16
.thumb_func _main
_main:
movw r0, #24320
movt r0, #55
bx lr
Đó chỉ thổi tâm trí của tôi phải trung thực. Nó hoàn toàn bỏ qua foo
và trả lại 3628800
là 10!
.
Điều này làm cho tôi thực sự nhận ra cách trình biên dịch của bạn thường có thể thực hiện công việc tốt hơn nhiều so với việc tối ưu hóa mã của bạn. Nhưng nó đặt ra câu hỏi, làm thế nào để nó quản lý để làm một công việc tốt như vậy? Vì vậy, bất kỳ ai cũng có thể giải thích (có thể bằng cách liên kết với mã có liên quan) cách tối ưu hóa sau hoạt động:
Tối ưu hóa ban đầu là một vòng lặp chặt chẽ.
Tối ưu hóa nơi
main
chỉ cần gửi và trả lại kết quả trực tiếp thay vì thực sự thực hiệnfoo
.
Một hiệu ứng phụ thú vị khác của câu hỏi này sẽ cho thấy một số tối ưu thú vị hơn mà GCC/Clang có thể thực hiện.
Câu trả lời hay! Và có tối ưu hóa 'main' thực sự có thể được thay thế bằng các hằng số nhân với nhau để dễ dàng. Tôi thích bước đi của bạn thông qua tối ưu hóa 'foo' :-). – mattjgalloway