Tôi đang tìm kiếm một ví dụ trong đó một thuật toán rõ ràng đang thay đổi lớp phức tạp của nó do các chiến lược tối ưu hóa trình biên dịch và / hoặc bộ xử lý.
int main(void) { exit(0); };
Tôi đang tìm kiếm một ví dụ trong đó một thuật toán rõ ràng đang thay đổi lớp phức tạp của nó do các chiến lược tối ưu hóa trình biên dịch và / hoặc bộ xử lý.
int main(void) { exit(0); };
Câu trả lời:
Hãy thực hiện một chương trình đơn giản in hình vuông của một số được nhập trên dòng lệnh.
#include <stdio.h>
int main(int argc, char **argv) {
int num = atoi(argv[1]);
printf("%d\n",num);
int i = 0;
int total = 0;
for(i = 0; i < num; i++) {
total += num;
}
printf("%d\n",total);
return 0;
}
Như bạn có thể thấy, đây là phép tính O (n), lặp đi lặp lại nhiều lần.
Biên dịch cái này với gcc -S
một phân đoạn là:
LBB1_1:
movl -36(%rbp), %eax
movl -28(%rbp), %ecx
addl %ecx, %eax
movl %eax, -36(%rbp)
movl -32(%rbp), %eax
addl $1, %eax
movl %eax, -32(%rbp)
LBB1_2:
movl -32(%rbp), %eax
movl -28(%rbp), %ecx
cmpl %ecx, %eax
jl LBB1_1
Trong phần này, bạn có thể thấy phần bổ sung được thực hiện, so sánh và nhảy lại cho vòng lặp.
Thực hiện biên dịch với gcc -S -O3
để tối ưu hóa phân đoạn giữa các lệnh gọi tới printf:
callq _printf
testl %ebx, %ebx
jg LBB1_2
xorl %ebx, %ebx
jmp LBB1_3
LBB1_2:
imull %ebx, %ebx
LBB1_3:
movl %ebx, %esi
leaq L_.str(%rip), %rdi
xorb %al, %al
callq _printf
Bây giờ người ta có thể thấy thay vì nó không có vòng lặp và hơn nữa, không có thêm. Thay vào đó, có một cuộc gọi imull
nhân số đó với chính nó.
Trình biên dịch đã nhận ra một vòng lặp và toán tử toán học bên trong và thay thế nó bằng phép tính thích hợp.
Lưu ý rằng điều này bao gồm một cuộc gọi atoi
để lấy số. Khi số đã tồn tại trong mã, trình biên dịch sẽ tính toán trước giá trị thay vì thực hiện các cuộc gọi thực tế như được so sánh giữa hiệu suất của sqrt trong C # và C trong đó sqrt(2)
(một hằng số) được tính tổng trên một vòng lặp 1.000.000 lần.
Tối ưu hóa cuộc gọi đuôi có thể làm giảm độ phức tạp không gian. Ví dụ, không có TCO, việc thực hiện đệ quy while
vòng lặp này có độ phức tạp không gian trong trường hợp xấu nhất Ο(#iterations)
, trong khi với TCO, nó có độ phức tạp không gian trong trường hợp xấu nhất là Ο(1)
:
// This is Scala, but it works the same way in every other language.
def loop(cond: => Boolean)(body: => Unit): Unit = if (cond) { body; loop(cond)(body) }
var i = 0
loop { i < 3 } { i += 1; println(i) }
// 1
// 2
// 3
// E.g. ECMAScript:
function loop(cond, body) {
if (cond()) { body(); loop(cond, body); };
};
var i = 0;
loop(function { return i < 3; }, function { i++; print(i); });
Điều này thậm chí không cần TCO chung, nó chỉ cần một trường hợp đặc biệt rất hẹp, cụ thể là loại bỏ đệ quy đuôi trực tiếp.
Tuy nhiên, điều sẽ rất thú vị là khi tối ưu hóa trình biên dịch không chỉ thay đổi lớp phức tạp mà thực sự thay đổi hoàn toàn thuật toán.
Trình biên dịch Haskell của Glorious đôi khi thực hiện điều này, nhưng đó không thực sự là điều tôi đang nói, nó giống như gian lận hơn. GHC có Ngôn ngữ đối sánh mẫu đơn giản cho phép nhà phát triển thư viện phát hiện một số mẫu mã đơn giản và thay thế chúng bằng các mã khác nhau. Và việc triển khai GHC của thư viện chuẩn Haskell có chứa một số chú thích đó, do đó, việc sử dụng cụ thể các chức năng cụ thể được biết là không hiệu quả được viết lại thành các phiên bản hiệu quả hơn.
Tuy nhiên, những bản dịch này được viết bởi con người, và chúng được viết cho các trường hợp cụ thể, đó là lý do tại sao tôi coi đó là gian lận.
Một siêu trình biên dịch có thể có thể thay đổi thuật toán mà không cần đầu vào của con người, nhưng AFAIK không có siêu trình biên dịch cấp sản xuất nào được chế tạo.
Một trình biên dịch nhận thức được rằng ngôn ngữ đang sử dụng big-num làm giảm sức mạnh (thay thế các phép nhân bằng chỉ số của một vòng lặp bằng một phép cộng) sẽ thay đổi độ phức tạp của phép nhân đó từ O (n log n) tốt nhất thành O (n) .