Câu trả lời ngắn:
Sự chuyên biệt về pow(x, n)
vị trí n
là một số tự nhiên thường hữu ích cho hiệu suất thời gian . Nhưng thư viện tiêu chuẩn chung của thư viện pow()
vẫn hoạt động khá tốt ( đáng ngạc nhiên! ) Cho mục đích này và điều tối quan trọng là phải đưa càng ít càng tốt vào thư viện C tiêu chuẩn để nó có thể di động và dễ thực hiện nhất có thể. Mặt khác, điều đó hoàn toàn không ngăn cản nó nằm trong thư viện chuẩn C ++ hoặc STL, mà tôi khá chắc rằng không ai có ý định sử dụng trong một số loại nền tảng nhúng.
Bây giờ, cho câu trả lời dài.
pow(x, n)
có thể được thực hiện nhanh hơn nhiều trong nhiều trường hợp bằng cách chuyên về n
một số tự nhiên. Tôi đã phải sử dụng việc triển khai hàm này của riêng mình cho hầu hết mọi chương trình tôi viết (nhưng tôi viết rất nhiều chương trình toán học bằng C). Hoạt động chuyên biệt có thể được thực hiện O(log(n))
kịp thời, nhưng khi quy n
mô nhỏ, phiên bản tuyến tính đơn giản hơn có thể nhanh hơn. Dưới đây là cách triển khai của cả hai:
// Computes x^n, where n is a natural number.
double pown(double x, unsigned n)
{
double y = 1;
// n = 2*d + r. x^n = (x^2)^d * x^r.
unsigned d = n >> 1;
unsigned r = n & 1;
double x_2_d = d == 0? 1 : pown(x*x, d);
double x_r = r == 0? 1 : x;
return x_2_d*x_r;
}
// The linear implementation.
double pown_l(double x, unsigned n)
{
double y = 1;
for (unsigned i = 0; i < n; i++)
y *= x;
return y;
}
(Tôi đã rời đi x
và giá trị trả về tăng gấp đôi bởi vì kết quả của pow(double x, unsigned n)
sẽ khớp với mức nhân đôi thường xuyên pow(double, double)
.)
(Đúng, pown
là đệ quy, nhưng việc phá vỡ ngăn xếp là hoàn toàn không thể vì kích thước ngăn xếp tối đa sẽ gần bằng log_2(n)
và n
là một số nguyên. Nếu n
là số nguyên 64 bit, điều đó cung cấp cho bạn kích thước ngăn xếp tối đa khoảng 64. Không có phần cứng nào có kích thước cực đại như vậy giới hạn bộ nhớ, ngoại trừ một số PIC tinh ranh có ngăn xếp phần cứng chỉ có 3 đến 8 lệnh gọi hàm.)
Về hiệu suất, bạn sẽ ngạc nhiên bởi những gì một giống vườn có thể làm pow(double, double)
được. Tôi đã thử nghiệm một trăm triệu lần lặp lại trên chiếc IBM Thinkpad 5 tuổi của mình với x
số lần lặp n
bằng và bằng 10. Trong trường hợp này, tôi pown_l
đã thắng. glibc pow()
mất 12,0 giây của người dùng, pown
mất 7,4 giây của người dùng và pown_l
chỉ mất 6,5 giây của người dùng. Vì vậy, điều đó không quá ngạc nhiên. Chúng tôi đã ít nhiều mong đợi điều này.
Sau đó, tôi để x
là hằng số (tôi đặt nó thành 2,5), và tôi lặp lại n
từ 0 đến 19 một trăm triệu lần. Lần này, khá bất ngờ, glibc pow
đã giành chiến thắng, và một trận đấu long trời lở đất! Người dùng chỉ mất 2,0 giây. My pown
mất 9,6 giây và pown_l
mất 12,2 giây. Điều gì đã xảy ra ở đây? Tôi đã làm một bài kiểm tra khác để tìm hiểu.
Tôi đã làm điều tương tự như trên chỉ x
bằng một triệu. Lần này, pown
giành chiến thắng ở 9,6 giây. pown_l
mất 12,2 giây và glibc pow mất 16,3 giây. Bây giờ, nó rõ ràng! glibc hoạt động pow
tốt hơn ba khi x
ở mức thấp, nhưng kém nhất khi x
ở mức cao. Khi x
cao, hoạt động pown_l
tốt nhất khi n
thấp và hoạt động pown
tốt nhất khi x
cao.
Vì vậy, đây là ba thuật toán khác nhau, mỗi thuật toán có khả năng hoạt động tốt hơn các thuật toán khác trong những trường hợp thích hợp. Vì vậy, cuối cùng, việc sử dụng cái nào nhiều khả năng phụ thuộc vào cách bạn định sử dụng pow
, nhưng sử dụng đúng phiên bản là điều đáng giá và có tất cả các phiên bản đều tốt. Trên thực tế, bạn thậm chí có thể tự động hóa việc lựa chọn thuật toán với một chức năng như sau:
double pown_auto(double x, unsigned n, double x_expected, unsigned n_expected) {
if (x_expected < x_threshold)
return pow(x, n);
if (n_expected < n_threshold)
return pown_l(x, n);
return pown(x, n);
}
Miễn là x_expected
và n_expected
là các hằng số được quyết định tại thời điểm biên dịch, cùng với một số lưu ý khác, trình biên dịch tối ưu hóa giá trị của nó sẽ tự động loại bỏ toàn bộ pown_auto
lệnh gọi hàm và thay thế nó bằng lựa chọn thích hợp trong ba thuật toán. (Bây giờ, nếu bạn thực sự định sử dụng cái này, có lẽ bạn sẽ phải đùa giỡn với nó một chút, vì tôi đã không chính xác thử biên dịch những gì tôi đã viết ở trên.;))
Mặt khác, glibc pow
hoạt động và glibc đã đủ lớn. Tiêu chuẩn C được cho là có thể di động, bao gồm các thiết bị nhúng khác nhau (trên thực tế, các nhà phát triển nhúng ở khắp mọi nơi thường đồng ý rằng glibc đã quá lớn đối với họ) và nó không thể di động nếu đối với mọi hàm toán học đơn giản, nó cần bao gồm mọi thuật toán thay thế có thể được sử dụng. Vì vậy, đó là lý do tại sao nó không nằm trong tiêu chuẩn C.
chú thích: Trong kiểm tra hiệu suất thời gian, tôi đã đưa ra các cờ tối ưu hóa tương đối hào phóng cho các chức năng của mình ( -s -O2
) có khả năng tương đương với, nếu không tệ hơn, những gì có khả năng được sử dụng để biên dịch glibc trên hệ thống của tôi (Archlinux), vì vậy kết quả có thể là hội chợ. Đối với một bài kiểm tra nghiêm ngặt hơn, tôi sẽ phải biên dịch glibc bản thân mình và tôi reeeally không cảm thấy như làm điều đó. Tôi đã từng sử dụng Gentoo, vì vậy tôi nhớ nó mất bao lâu, ngay cả khi tác vụ được tự động hóa . Kết quả là đủ cho tôi (hay đúng hơn là không thể kết luận). Tất nhiên, bạn có thể tự làm việc này.
Vòng thưởng: Chuyên môn hóa pow(x, n)
tất cả các số nguyên là công cụ nếu yêu cầu đầu ra số nguyên chính xác, điều này sẽ xảy ra. Xem xét cấp phát bộ nhớ cho một mảng N chiều có p ^ N phần tử. Việc tắt p ^ N dù chỉ một sẽ dẫn đến một segfault có thể xảy ra ngẫu nhiên.