Jawaban singkat:
Spesialisasi pow(x, n)
ke mana n
bilangan asli sering berguna untuk kinerja waktu . Tetapi generik pustaka standar pow()
masih berfungsi dengan baik ( mengejutkan! ) Untuk tujuan ini dan sangat penting untuk menyertakan sesedikit mungkin dalam pustaka C standar sehingga dapat dibuat portabel dan semudah mungkin diimplementasikan. Di sisi lain, itu tidak menghentikannya sama sekali dari berada di pustaka standar C ++ atau STL, yang saya cukup yakin tidak ada yang berencana menggunakannya dalam beberapa jenis platform tertanam.
Sekarang, untuk jawaban panjangnya.
pow(x, n)
dalam banyak kasus dapat dibuat lebih cepat dengan mengkhususkan diri n
pada bilangan asli. Saya harus menggunakan implementasi saya sendiri dari fungsi ini untuk hampir setiap program yang saya tulis (tetapi saya menulis banyak program matematika dalam C). Operasi khusus dapat dilakukan O(log(n))
tepat waktu, tetapi bila n
kecil, versi linier yang lebih sederhana dapat lebih cepat. Berikut implementasi keduanya:
// 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;
}
(Saya pergi x
dan nilai kembaliannya berlipat ganda karena hasil dari pow(double x, unsigned n)
akan masuk ganda sesering pow(double, double)
mungkin.)
(Ya, pown
itu rekursif, tetapi memecahkan tumpukan sama sekali tidak mungkin karena ukuran tumpukan maksimum kira-kira akan sama log_2(n)
dan n
merupakan bilangan bulat. Jika n
adalah bilangan bulat 64-bit, itu memberi Anda ukuran tumpukan maksimum sekitar 64. Tidak ada perangkat keras yang sedemikian ekstrim keterbatasan memori, kecuali untuk beberapa PIC cerdik dengan tumpukan perangkat keras yang hanya melakukan panggilan fungsi 3 hingga 8 dalam.)
Mengenai kinerja, Anda akan terkejut dengan kemampuan berbagai taman pow(double, double)
. Saya menguji seratus juta iterasi pada IBM Thinkpad saya yang berusia 5 tahun dengan x
angka iterasi yang n
sama dan sama dengan 10. Dalam skenario ini, pown_l
menang. glibc pow()
membutuhkan 12,0 detik pengguna, pown
7,4 detik pengguna, dan pown_l
hanya 6,5 detik pengguna. Jadi itu tidak terlalu mengejutkan. Kami kurang lebih mengharapkan ini.
Kemudian, saya biarkan x
konstan (saya setel menjadi 2,5), dan saya mengulang n
dari 0 hingga 19 seratus juta kali. Kali ini, secara tidak terduga, glibc pow
menang, dan dengan telak! Hanya butuh 2.0 detik pengguna. Saya pown
mengambil 9,6 detik, dan pown_l
mengambil 12,2 detik. Apa yang terjadi disini? Saya melakukan tes lain untuk mencari tahu.
Saya melakukan hal yang sama seperti di atas hanya dengan x
setara dengan satu juta. Kali ini, pown
menang di 9,6s. pown_l
mengambil 12.2s dan glibc pow mengambil 16.3s. Sekarang, sudah jelas! glibc pow
berkinerja lebih baik daripada ketiganya saat x
rendah, tetapi terburuk saat x
tinggi. Saat x
tinggi, pown_l
berkinerja terbaik saat n
rendah, dan pown
berkinerja terbaik saat x
tinggi.
Jadi, inilah tiga algoritme berbeda, masing-masing mampu berkinerja lebih baik daripada yang lain dalam situasi yang tepat. Jadi, pada akhirnya, mana yang akan digunakan kemungkinan besar tergantung pada bagaimana Anda berencana menggunakan pow
, tetapi menggunakan versi yang tepat itu sepadan, dan memiliki semua versi itu bagus. Bahkan, Anda bahkan dapat mengotomatiskan pilihan algoritme dengan fungsi seperti ini:
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);
}
Selama x_expected
dan n_expected
merupakan konstanta yang diputuskan pada waktu kompilasi, bersama dengan kemungkinan beberapa peringatan lainnya, kompiler pengoptimalan yang bernilai garamnya akan secara otomatis menghapus seluruh pown_auto
pemanggilan fungsi dan menggantinya dengan pilihan yang sesuai dari ketiga algoritme. (Sekarang, jika Anda benar-benar akan mencoba menggunakan ini, Anda mungkin harus sedikit bermain-main dengannya, karena saya tidak benar-benar mencoba menyusun apa yang saya tulis di atas.;))
Di sisi lain, glibc pow
berfungsi dan glibc sudah cukup besar. Standar C seharusnya portabel, termasuk ke berbagai perangkat yang disematkan (pada kenyataannya pengembang yang disematkan di mana-mana umumnya setuju bahwa glibc sudah terlalu besar untuk mereka), dan itu tidak bisa portabel jika untuk setiap fungsi matematika sederhana perlu menyertakan setiap algoritma alternatif yang mungkin bisa digunakan. Jadi, itulah mengapa tidak ada dalam standar C.
catatan kaki: Dalam pengujian kinerja waktu, saya memberikan fungsi saya tanda pengoptimalan yang relatif murah hati ( -s -O2
) yang cenderung sebanding dengan, jika tidak lebih buruk dari, apa yang mungkin digunakan untuk mengkompilasi glibc di sistem saya (archlinux), jadi hasilnya mungkin adil. Untuk tes yang lebih ketat, aku harus mengkompilasi glibc diri saya dan saya reeeally tidak merasa seperti melakukan hal itu. Saya dulu menggunakan Gentoo, jadi saya ingat berapa lama waktu yang dibutuhkan, bahkan ketika tugasnya otomatis . Hasilnya cukup meyakinkan (atau agak tidak meyakinkan) bagi saya. Anda tentu saja dipersilakan untuk melakukannya sendiri.
Putaran bonus: Spesialisasi dari pow(x, n)
semua bilangan bulat sangat penting jika diperlukan keluaran bilangan bulat yang tepat, yang memang terjadi. Pertimbangkan mengalokasikan memori untuk larik berdimensi-N dengan elemen p ^ N. Mendapatkan p ^ N off bahkan satu akan menghasilkan kemungkinan segfault yang terjadi secara acak.