Saya sedang mengembangkan beberapa simulasi teknik. Ini melibatkan penerapan beberapa persamaan panjang seperti persamaan ini untuk menghitung tegangan pada bahan seperti karet:
T = (
mu * (
pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
* (
pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
- l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l1
- pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l1 / 0.3e1
- pow(l3 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l1 / 0.3e1
) / a
+ K * (l1 * l2 * l3 - 0.1e1) * l2 * l3
) * N1 / l2 / l3
+ (
mu * (
- pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l2 / 0.3e1
+ pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
* (
pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
- l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l2
- pow(l3 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l2 / 0.3e1
) / a
+ K * (l1 * l2 * l3 - 0.1e1) * l1 * l3
) * N2 / l1 / l3
+ (
mu * (
- pow(l1 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l3 / 0.3e1
- pow(l2 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a / l3 / 0.3e1
+ pow(l3 * pow(l1 * l2 * l3, -0.1e1 / 0.3e1), a) * a
* (
pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
- l1 * l2 * l3 * pow(l1 * l2 * l3, -0.4e1 / 0.3e1) / 0.3e1
) * pow(l1 * l2 * l3, 0.1e1 / 0.3e1) / l3
) / a
+ K * (l1 * l2 * l3 - 0.1e1) * l1 * l2
) * N3 / l1 / l2;
Saya menggunakan Maple untuk menghasilkan kode C ++ untuk menghindari kesalahan (dan menghemat waktu dengan aljabar yang membosankan). Karena kode ini dieksekusi ribuan (jika tidak jutaan) kali, kinerja menjadi perhatian. Sayangnya sejauh ini matematika hanya menyederhanakan; persamaan panjang tidak bisa dihindari.
Pendekatan apa yang dapat saya ambil untuk mengoptimalkan penerapan ini? Saya mencari strategi tingkat tinggi yang harus saya terapkan saat menerapkan persamaan seperti itu, belum tentu pengoptimalan khusus untuk contoh yang ditunjukkan di atas.
Saya mengompilasi menggunakan g ++ dengan --enable-optimize=-O3
.
Memperbarui:
Saya tahu ada banyak ekspresi berulang, saya menggunakan asumsi bahwa kompilator akan menangani ini; pengujian saya sejauh ini menunjukkan bahwa memang demikian.
l1, l2, l3, mu, a, K
adalah semua bilangan real positif (bukan nol).
Saya telah diganti l1*l2*l3
dengan variabel setara: J
. Ini memang membantu meningkatkan kinerja.
Mengganti pow(x, 0.1e1/0.3e1)
dengan cbrt(x)
adalah saran yang bagus.
Ini akan dijalankan di CPU, Dalam waktu dekat ini kemungkinan akan berjalan lebih baik di GPU, tetapi untuk saat ini opsi itu tidak tersedia.
pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
dengan variabel ... Anda perlu melakukan benchmark kode Anda untuk memastikan apakah itu berjalan cepat atau lambat.Jawaban:
Edit ringkasan
pow(x, 0.1e1/0.3e1)
sama seperticbrt(x)
.mencoret) hasil edit tersebut dan mendorongnya ke bagian bawah revisi saat ini dari jawaban ini. Namun, saya tidak menghapusnya. Saya manusia. Mudah bagi kami untuk membuat kesalahan.l1
,l2
, danl3
adalah bilangan real positif dan jikaa
adalah nomor non-nol nyata. (Kami belum mendengar dari OP mengenai sifat spesifik dari koefisien ini. Mengingat sifat masalahnya, ini adalah asumsi yang masuk akal.)Hal pertama yang pertama
Maple dan Mathematica terkadang melewatkan yang sudah jelas. Yang lebih penting lagi, pengguna Maple dan Mathematica terkadang melakukan kesalahan. Mengganti "sering kali", atau mungkin bahkan "hampir selalu", sebagai pengganti "terkadang mungkin lebih dekat ke sasaran.
Anda bisa membantu Maple menyederhanakan ekspresi itu dengan menceritakan tentang parameter yang dimaksud. Pada contoh di tangan, saya menduga bahwa
l1
,l2
, danl3
adalah bilangan real positif dan bahwaa
adalah angka non-nol nyata. Jika itu masalahnya, katakan itu. Program matematika simbolis tersebut biasanya mengasumsikan besaran yang ada di tangan itu kompleks. Membatasi domain memungkinkan program membuat asumsi yang tidak valid dalam bilangan kompleks.Bagaimana menyederhanakan kekacauan besar itu dari program matematika simbolis (edit ini)
Program matematika simbolik biasanya menyediakan kemampuan untuk memberikan informasi tentang berbagai parameter. Gunakan kemampuan itu, terutama jika masalah Anda melibatkan pembagian atau eksponensial. Pada contoh di tangan, Anda bisa membantu Maple menyederhanakan ekspresi itu dengan mengatakan bahwa
l1
,l2
, danl3
adalah bilangan real positif dan bahwaa
adalah angka non-nol nyata. Jika itu masalahnya, katakan itu. Program matematika simbolis tersebut biasanya mengasumsikan jumlah yang ada di tangan itu kompleks. Membatasi domain memungkinkan program membuat asumsi seperti a x b x = (ab) x . Ini hanya jikaa
danb
adalah bilangan real positif dan jikax
nyata. Ini tidak valid dalam bilangan kompleks.Pada akhirnya, program matematika simbolis tersebut mengikuti algoritma. Bantu itu. Cobalah bermain dengan memperluas, mengumpulkan, dan menyederhanakan sebelum Anda menghasilkan kode. Dalam kasus ini, Anda bisa mengumpulkan suku-suku yang melibatkan faktor dari
mu
dan yang melibatkan faktorK
. Mereduksi ekspresi menjadi "bentuk paling sederhana" tetap merupakan seni.Ketika Anda mendapatkan kode yang dihasilkan berantakan, jangan menerimanya sebagai kebenaran yang tidak boleh Anda sentuh. Coba sederhanakan sendiri. Lihatlah apa yang dimiliki program matematika simbolik sebelum ia menghasilkan kode. Lihatlah bagaimana saya mengurangi ekspresi Anda menjadi sesuatu yang jauh lebih sederhana dan lebih cepat, dan bagaimana jawaban Walter membawa saya beberapa langkah lebih jauh. Tidak ada resep ajaib. Jika ada resep ajaib, Maple akan menerapkannya dan memberikan jawaban yang diberikan Walter.
Tentang pertanyaan spesifik
Anda melakukan banyak penambahan dan pengurangan dalam perhitungan itu. Anda bisa mendapat masalah besar jika Anda memiliki persyaratan yang hampir membatalkan satu sama lain. Anda membuang banyak CPU jika Anda memiliki satu istilah yang mendominasi yang lain.
Selanjutnya, Anda membuang banyak CPU dengan melakukan penghitungan berulang. Kecuali Anda telah mengaktifkannya
-ffast-math
, yang memungkinkan kompilator melanggar beberapa aturan titik mengambang IEEE, kompilator tidak akan (pada kenyataannya, tidak boleh) menyederhanakan ekspresi itu untuk Anda. Ia malah akan melakukan persis seperti yang Anda perintahkan. Minimal, Anda harus menghitungl1 * l2 * l3
sebelum menghitung kekacauan itu.Terakhir, Anda melakukan banyak panggilan ke
pow
, yang sangat lambat. Perhatikan bahwa beberapa dari panggilan tersebut dalam bentuk (l1 * l2 * l3) (1/3) . Banyak dari panggilan kepow
tersebut dapat dilakukan dengan satu panggilan kestd::cbrt
:Dengan ini,
X * pow(l1 * l2 * l3, 0.1e1 / 0.3e1)
menjadiX * l123_pow_1_3
.X * pow(l1 * l2 * l3, -0.1e1 / 0.3e1)
menjadiX / l123_pow_1_3
.X * pow(l1 * l2 * l3, 0.4e1 / 0.3e1)
menjadiX * l123_pow_4_3
.X * pow(l1 * l2 * l3, -0.4e1 / 0.3e1)
menjadiX / l123_pow_4_3
.Maple memang melewatkan yang sudah jelas.
Misalnya, ada cara yang lebih mudah untuk menulis
Dengan asumsi bahwa
l1
,,l2
danl3
adalah bilangan real daripada bilangan kompleks, dan bahwa akar pangkat tiga nyata (bukan akar kompleks utama) akan diekstraksi, di atas tereduksi menjadiatau
Menggunakan
cbrt_l123
alih-alihl123_pow_1_3
, ekspresi buruk dalam pertanyaan tersebut dikurangi menjadiSelalu periksa ulang, tetapi selalu sederhanakan juga.
Berikut adalah beberapa langkah saya untuk sampai di atas:
Jawaban yang salah, sengaja disimpan untuk kerendahan hati
Perhatikan bahwa ini terserang. Itu salah.
MemperbaruiMaple memang melewatkan yang sudah jelas. Misalnya, ada cara yang lebih mudah untuk menulis
Dengan asumsi bahwa
l1
,l2
, danl3
adalah bilangan real daripada bilangan kompleks, dan bahwa akar pangkat tiga yang sebenarnya (bukan akar kompleks utama) akan diekstraksi, di atas tereduksi menjadi nol. Perhitungan nol ini diulang berkali-kali.Pembaruan kedua
Jika saya telah menyelesaikan matematika dengan benar (tidak ada jaminan bahwa saya telah menyelesaikan matematika dengan benar), ekspresi buruk dalam pertanyaan tersebut berkurang menjadi
Di atas mengasumsikan bahwal1
,l2
, danl3
adalah bilangan real positif.sumber
-ffast-math
dengan gcc atau clang), kompilator tidak dapat mengandalkanpow(x,-1.0/3.0)
persamaan denganx*pow(x,-4.0/3.0)
. Yang terakhir mungkin melimpah sedangkan yang pertama mungkin tidak. Agar sesuai dengan standar floating point, compiler tidak harus mengoptimalkan perhitungan itu ke nol.-fno-math-errno
identik g ++ ke CSEpow
. (Kecuali mungkin dapat membuktikan bahwa pow tidak perlu disetel errno?)N1
,,N2
danN3
non-negatif, salah satu2*N_i-(N_j+N_k)
akan negatif, yang satu akan positif, dan yang lainnya akan berada di antara keduanya. Ini dapat dengan mudah menyebabkan masalah pembatalan numerik.Hal pertama yang perlu diperhatikan adalah itu
pow
sangat mahal, jadi Anda harus menyingkirkan ini sebanyak mungkin. Memindai melalui ekspresi saya melihat banyak pengulanganpow(l1 * l2 * l3, -0.1e1 / 0.3e1)
danpow(l1 * l2 * l3, -0.4e1 / 0.3e1)
. Jadi saya mengharapkan keuntungan besar dari pra-komputasi yang:di mana saya menggunakan fungsi boost pow .
Selanjutnya, Anda memiliki lebih banyak
pow
dengan eksponena
. Jikaa
Integer dan dikenal pada waktu kompilator, Anda juga dapat menggantinya denganboost::math::pow<a>(...)
untuk mendapatkan performa lebih lanjut. Saya juga menyarankan untuk mengganti suku-suku sepertia / l1 / 0.3e1
dengana / (l1 * 0.3e1)
perkalian lebih cepat dari pembagian.Terakhir, jika Anda menggunakan g ++, Anda dapat menggunakan
-ffast-math
tanda yang memungkinkan pengoptimal menjadi lebih agresif dalam mengubah persamaan. Baca tentang apa sebenarnya fungsi bendera ini , karena memiliki efek samping.sumber
-ffast-math
lead kode menjadi tidak stabil atau memberikan jawaban yang salah. Kami memiliki masalah serupa dengan kompiler Intel dan harus menggunakan-fp-model precise
opsi, jika tidak kode akan meledak atau memberikan jawaban yang salah. Jadi-ffast-math
bisa mempercepatnya, tetapi saya akan merekomendasikan untuk melanjutkan dengan sangat hati-hati dengan opsi itu, selain efek samping yang tercantum dalam pertanyaan terkait Anda.-fno-math-errno
g ++ untuk dapat memanggil panggilan yang identikpow
keluar dari satu lingkaran. Itu adalah bagian paling "berbahaya" dari matematika-cepat, untuk kebanyakan kode.pow
menjadi sangat lambat dan akhirnya menggunakandlsym
peretasan yang disebutkan dalam komentar untuk mendapatkan peningkatan kinerja yang cukup besar ketika kami benar-benar dapat melakukannya dengan sedikit kurang presisi.pow
adalah tidak fungsi murni, sesuai dengan standar, karena itu seharusnya seterrno
dalam beberapa keadaan. Menyetel flag seperti-fno-math-errno
menyebabkannya tidak disetelerrno
(dengan demikian melanggar standar), tetapi kemudian itu adalah fungsi murni dan dapat dioptimalkan seperti itu.Woah, ekspresi yang luar biasa. Menciptakan ekspresi dengan Maple sebenarnya adalah pilihan yang kurang optimal di sini. Hasilnya tidak terbaca.
Secara teoritis kompilator harus dapat melakukan semua itu untuk Anda, tetapi terkadang tidak bisa - misalnya ketika loop bersarang menyebar ke beberapa fungsi dalam unit kompilasi yang berbeda. Bagaimanapun, itu akan memberi Anda kode yang lebih mudah dibaca, dimengerti, dan dipelihara.
sumber
x
dan bukan variabel huruf tunggaly
yang tidak berarti, mereka adalah kata - kata utuh dengan definisi yang tepat dan makna yang dipahami dengan baik dan luas.Jawaban David Hammen memang bagus, tapi masih jauh dari optimal. Mari lanjutkan dengan ekspresi terakhirnya (pada saat penulisan ini)
yang dapat dioptimalkan lebih lanjut. Secara khusus, kita dapat menghindari panggilan ke
cbrt()
dan salah satu panggilan kepow()
jika mengeksploitasi beberapa identitas matematika. Mari lakukan ini lagi selangkah demi selangkah.Perhatikan bahwa saya juga telah mengoptimalkan
2.0*N1
keN1+N1
dll. Selanjutnya, kita dapat melakukan hanya dengan dua panggilan kepow()
.Karena panggilan ke
pow()
sejauh ini merupakan operasi yang paling mahal di sini, ada baiknya untuk menguranginya sejauh mungkin (operasi mahal berikutnya adalah panggilan kecbrt()
, yang telah kami hilangkan).Jika kebetulan
a
adalah integer, panggilan kepow
dapat dioptimalkan untuk panggilan kecbrt
(ditambah kekuatan integer), atau jikaathird
setengah-integer, kita dapat menggunakansqrt
(ditambah kekuatan integer). Selain itu, jika kebetulanl1==l2
ataul1==l3
ataul2==l3
salah satu atau kedua panggilan kepow
dapat dihilangkan. Jadi, perlu dipertimbangkan ini sebagai kasus khusus jika peluang seperti itu ada secara realistis.sumber
Saya telah mencoba penyederhanaan manual dari rumus itu, ingin tahu apakah itu menghemat?
[TAMBAH] Saya telah mengerjakan lebih banyak lagi rumus tiga baris terakhir dan menjelaskannya pada keindahan ini:
Izinkan saya menunjukkan pekerjaan saya, selangkah demi selangkah:
sumber
std::pow()
, yang masih Anda miliki 6, 3 kali lebih banyak dari yang diperlukan. Dengan kata lain, kode Anda 3 kali lebih lambat dari mungkin.Ini mungkin sedikit singkat, tetapi saya sebenarnya telah menemukan percepatan yang baik untuk polinomial (interpolasi fungsi energi) dengan menggunakan Formulir Horner, yang pada dasarnya menulis ulang
ax^3 + bx^2 + cx + d
sebagaid + x(c + x(b + x(a)))
. Ini akan menghindari banyak panggilan berulang-ulang kepow()
dan menghentikan Anda melakukan hal-hal konyol seperti meneleponpow(x,6)
danpow(x,7)
bukan hanya melakukanx*pow(x,6)
.Ini tidak berlaku langsung untuk masalah Anda saat ini, tetapi jika Anda memiliki polinomial orde tinggi dengan pangkat integer, ini dapat membantu. Anda mungkin harus berhati-hati terhadap stabilitas numerik dan masalah luapan karena urutan operasi penting untuk itu (walaupun secara umum saya benar-benar berpikir Formulir Horner membantu untuk ini, karena
x^20
danx
biasanya banyak urutan besarnya terpisah).Juga sebagai tip praktis, jika Anda belum melakukannya, coba sederhanakan ekspresi di maple terlebih dahulu. Anda mungkin bisa membuatnya melakukan sebagian besar eliminasi subekspresi umum untuk Anda. Saya tidak tahu seberapa besar pengaruhnya terhadap generator kode dalam program itu secara khusus, tetapi saya tahu di Mathematica melakukan FullSimplify sebelum membuat kode dapat menghasilkan perbedaan besar.
sumber
Sepertinya Anda mengalami banyak operasi berulang.
Anda dapat menghitung sebelumnya sehingga Anda tidak berulang kali memanggil
pow
fungsi yang bisa mahal.Anda juga bisa melakukan pra-calutate
saat Anda menggunakan istilah itu berulang kali.
sumber
-ffast-math
diaktifkan, dan seperti disebutkan dalam komentar oleh @ tpg2114, pengoptimalan tersebut dapat membuat hasil yang sangat tidak stabil.Jika Anda memiliki kartu grafis Nvidia CUDA, Anda dapat mempertimbangkan untuk memindahkan kalkulasi ke kartu grafis - yang dengan sendirinya lebih cocok untuk kalkulasi yang rumit secara komputasi.
https://developer.nvidia.com/how-to-cuda-c-cpp
Jika tidak, Anda mungkin ingin mempertimbangkan beberapa utas untuk perhitungan.
sumber
Kebetulan, dapatkah Anda memberikan perhitungan secara simbolis. Jika ada operasi vektor, Anda mungkin ingin menyelidiki menggunakan blas atau lapack yang dalam beberapa kasus dapat menjalankan operasi secara paralel.
Bisa dibayangkan (dengan risiko di luar topik?) Bahwa Anda mungkin bisa menggunakan python dengan numpy dan / atau scipy. Sejauh itu memungkinkan, perhitungan Anda mungkin lebih mudah dibaca.
sumber
Saat Anda secara eksplisit bertanya tentang pengoptimalan tingkat tinggi, mungkin ada baiknya Anda mencoba berbagai kompiler C ++. Saat ini, kompiler adalah binatang pengoptimalan yang sangat kompleks dan vendor CPU mungkin menerapkan pengoptimalan yang sangat kuat dan spesifik. Namun perlu diketahui, beberapa di antaranya tidak gratis (tetapi mungkin ada program akademik gratis).
Saya telah melihat potongan kode berbeda dalam kecepatan eksekusi dengan faktor 2, hanya dengan mengubah kompiler (tentu saja dengan pengoptimalan penuh). Namun berhati-hatilah dalam memeriksa identitas keluaran. Pengoptimalan yang agresif dapat menghasilkan keluaran yang berbeda, yang tentunya ingin Anda hindari.
Semoga berhasil!
sumber