Bu iki yöntemden hangisi C'de daha etkilidir? Ve nasıl olur:
pow(x,3)
vs.
x*x*x // etc?
Bu iki yöntemden hangisi C'de daha etkilidir? Ve nasıl olur:
pow(x,3)
vs.
x*x*x // etc?
Yanıtlar:
Ben arasındaki performans farkı test x*x*...
vs pow(x,i)
küçük için i
bu kodu kullanarak:
#include <cstdlib>
#include <cmath>
#include <boost/date_time/posix_time/posix_time.hpp>
inline boost::posix_time::ptime now()
{
return boost::posix_time::microsec_clock::local_time();
}
#define TEST(num, expression) \
double test##num(double b, long loops) \
{ \
double x = 0.0; \
\
boost::posix_time::ptime startTime = now(); \
for (long i=0; i<loops; ++i) \
{ \
x += expression; \
x += expression; \
x += expression; \
x += expression; \
x += expression; \
x += expression; \
x += expression; \
x += expression; \
x += expression; \
x += expression; \
} \
boost::posix_time::time_duration elapsed = now() - startTime; \
\
std::cout << elapsed << " "; \
\
return x; \
}
TEST(1, b)
TEST(2, b*b)
TEST(3, b*b*b)
TEST(4, b*b*b*b)
TEST(5, b*b*b*b*b)
template <int exponent>
double testpow(double base, long loops)
{
double x = 0.0;
boost::posix_time::ptime startTime = now();
for (long i=0; i<loops; ++i)
{
x += std::pow(base, exponent);
x += std::pow(base, exponent);
x += std::pow(base, exponent);
x += std::pow(base, exponent);
x += std::pow(base, exponent);
x += std::pow(base, exponent);
x += std::pow(base, exponent);
x += std::pow(base, exponent);
x += std::pow(base, exponent);
x += std::pow(base, exponent);
}
boost::posix_time::time_duration elapsed = now() - startTime;
std::cout << elapsed << " ";
return x;
}
int main()
{
using std::cout;
long loops = 100000000l;
double x = 0.0;
cout << "1 ";
x += testpow<1>(rand(), loops);
x += test1(rand(), loops);
cout << "\n2 ";
x += testpow<2>(rand(), loops);
x += test2(rand(), loops);
cout << "\n3 ";
x += testpow<3>(rand(), loops);
x += test3(rand(), loops);
cout << "\n4 ";
x += testpow<4>(rand(), loops);
x += test4(rand(), loops);
cout << "\n5 ";
x += testpow<5>(rand(), loops);
x += test5(rand(), loops);
cout << "\n" << x << "\n";
}
Sonuçlar:
1 00:00:01.126008 00:00:01.128338
2 00:00:01.125832 00:00:01.127227
3 00:00:01.125563 00:00:01.126590
4 00:00:01.126289 00:00:01.126086
5 00:00:01.126570 00:00:01.125930
2.45829e+54
Derleyicinin onu optimize etmediğinden emin olmak için her güç hesaplamasının sonucunu biriktirdiğime dikkat edin.
std::pow(double, double)
Sürümü kullanırsam ve loops = 1000000l
şunu elde ederim:
1 00:00:00.011339 00:00:00.011262
2 00:00:00.011259 00:00:00.011254
3 00:00:00.975658 00:00:00.011254
4 00:00:00.976427 00:00:00.011254
5 00:00:00.973029 00:00:00.011254
2.45829e+52
Bu, Ubuntu 9.10 64bit çalıştıran bir Intel Core Duo üzerindedir. -O2 optimizasyonlu gcc 4.4.1 kullanılarak derlenmiştir.
Yani C'de evet x*x*x
daha hızlı olacaktır pow(x, 3)
çünkü pow(double, int)
aşırı yük yoktur . C ++ 'da kabaca aynı olacaktır. (Testlerimdeki metodolojinin doğru olduğunu varsayarsak.)
Bu, An Markm tarafından yapılan yoruma cevaben:
Bir Bile using namespace std
direktif verildiği için ikinci parametre ise, pow
bir olduğunu int
, ardından std::pow(double, int)
gelen aşırı yük <cmath>
yerine çağrılacak ::pow(double, double)
gelen <math.h>
.
Bu test kodu şu davranışı doğrular:
#include <iostream>
namespace foo
{
double bar(double x, int i)
{
std::cout << "foo::bar\n";
return x*i;
}
}
double bar(double x, double y)
{
std::cout << "::bar\n";
return x*y;
}
using namespace foo;
int main()
{
double a = bar(1.2, 3); // Prints "foo::bar"
std::cout << a << "\n";
return 0;
}
std::pow
kullanmadığınız sürece , aslında 8 * döngü sürelerini (üs> 2 için) çağırır -fno-math-errno
. Ardından, düşündüğüm gibi pow çağrısını döngüden çıkarabilir. Sanırım errno bir global olduğundan, iş parçacığı güvenliği pow çağrısının errno'yu birden çok kez ayarlamasını gerektirir ... exp = 1 ve exp = 2 hızlıdır çünkü pow çağrı döngüden sadece -O3
.. ( ile - ffast-math , döngünün dışında da 8'in toplamını yapar.)
pow
döngüden çıkarılan çağrı ile aynı hizadalar , bu yüzden orada büyük bir kusur var. Ayrıca, tüm testler aynı sürede çalıştığından, çoğunlukla FP eklemenin gecikmesini test ediyormuşsunuz gibi görünüyor. Daha test5
yavaş olmayı beklerdin test1
, ama öyle değil. Birden fazla akümülatör kullanmak bağımlılık zincirini böler ve gecikmeyi gizler.
pow
Sürekli değişen bir değere uygulayarak kıyaslamamı düzeltmeyi deneyeceğim (tekrarlanan güç ifadesinin kaldırılmasını önlemek için).
Bu yanlış türden bir soru. Doğru soru şudur: "Kodumu okuyanlar için hangisini anlamak daha kolay?"
Hız önemliyse (daha sonra), sormayın, ölçün. (Ve ondan önce, bunu optimize etmenin gerçekten gözle görülür bir fark yaratıp yaratmayacağını ölçün.) O zamana kadar, okunması en kolay olan kodu yazın.
Düzenleme
Sadece yapmak için bu açık (zaten olmalıydı rağmen): Cephe speedups genellikle gibi şeyler geliyor daha iyi algoritmaları kullanarak , verilerin yerellik iyileştirilmesi , dinamik hafıza kullanımını azaltarak , sonuçları önceden hesaplama vb Bunlar nadiren gelen mikro optimizasyonlu tek işlev çağrıları ve yaptıkları yerlerde bunu çok az yerde yaparlar, ki bu yalnızca dikkatli (ve zaman alan) profilleme ile bulunabilir , çoğu zaman hiç sezgisel olmayan yöntemlerle hızlandırılamazlar. şeyler (eklemek gibinoop
ifadeler) ve bir platform için optimizasyon ne ise bazen diğeri için bir karamsarlıktır (bu yüzden sormak yerine ölçmeniz gerekir, çünkü ortamınızı tam olarak bilmiyoruz / sahip değiliz).
Hatta bu tür şeyler önemli birkaç uygulamalarda, onlar alıştığınız çoğu yerde olsun yoktur ve: ver tekrar vurgulayalım öyle çok onlar koduna bakılarak önemli olan yerleri bulacaksınız olası. Gerçekten önce sıcak noktaları belirlemeniz gerekir , çünkü aksi takdirde kodu optimize etmek sadece zaman kaybıdır .
Tek bir işlem (belirli bir değerin karesini hesaplamak gibi) uygulamanın yürütme süresinin% 10'unu (ki bu IME oldukça nadirdir) alsa ve optimize etse bile , bu işlem için gereken sürenin% 50'sinden tasarruf etse bile (ki bu IME hatta çok, çok daha nadir), yine de uygulamanın yalnızca% 5 daha az zaman almasını sağladınız .
Kullanıcılarınızın bunu fark etmesi için bir kronometreye ihtiyacı olacaktır. (Hiçbir şey altında% 20 hızlanma çoğu kullanıcı için gözden kaçmış olur çoğu durumda sanırım. Ve o sen bulmalıyız dört tür lekeler içindir.)
x*x
veya x*x*x
daha hızlı olacaktır pow
, çünkü pow
genel durumla ilgilenmek zorundadır, oysa x*x
spesifiktir. Ayrıca, işlev çağrısı ve benzerlerini de atlayabilirsiniz.
Bununla birlikte, kendinizi bu şekilde mikro optimizasyon bulursanız, bir profil oluşturucu edinmeniz ve ciddi bir profilleme yapmanız gerekir. En büyük olasılık, ikisi arasında hiçbir fark görmemenizdir.
x*x*x
çifte karşı test ettim std::pow(double base, int exponent)
ve istatistiksel olarak anlamlı bir performans farkı göremiyorum.
Performans sorununu da merak ediyordum ve @ EmileCormier'ın cevabına göre derleyici tarafından optimize edilmesini umuyordum. Bununla birlikte, gösterdiği test kodunun derleyicinin std :: pow () çağrısını optimize etmesine izin vereceğinden endişeliydim, çünkü çağrıda her seferinde aynı değerler kullanıldı, bu da derleyicinin sonuçları depolamasına ve döngüde yeniden kullanın - bu, tüm durumlar için neredeyse aynı çalışma sürelerini açıklar. Bu yüzden ben de bir göz attım.
İşte kullandığım kod (test_pow.cpp):
#include <iostream>
#include <cmath>
#include <chrono>
class Timer {
public:
explicit Timer () : from (std::chrono::high_resolution_clock::now()) { }
void start () {
from = std::chrono::high_resolution_clock::now();
}
double elapsed() const {
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - from).count() * 1.0e-6;
}
private:
std::chrono::high_resolution_clock::time_point from;
};
int main (int argc, char* argv[])
{
double total;
Timer timer;
total = 0.0;
timer.start();
for (double i = 0.0; i < 1.0; i += 1e-8)
total += std::pow (i,2);
std::cout << "std::pow(i,2): " << timer.elapsed() << "s (result = " << total << ")\n";
total = 0.0;
timer.start();
for (double i = 0.0; i < 1.0; i += 1e-8)
total += i*i;
std::cout << "i*i: " << timer.elapsed() << "s (result = " << total << ")\n";
std::cout << "\n";
total = 0.0;
timer.start();
for (double i = 0.0; i < 1.0; i += 1e-8)
total += std::pow (i,3);
std::cout << "std::pow(i,3): " << timer.elapsed() << "s (result = " << total << ")\n";
total = 0.0;
timer.start();
for (double i = 0.0; i < 1.0; i += 1e-8)
total += i*i*i;
std::cout << "i*i*i: " << timer.elapsed() << "s (result = " << total << ")\n";
return 0;
}
Bu, şu şekilde derlenmiştir:
g++ -std=c++11 [-O2] test_pow.cpp -o test_pow
Temel olarak, std :: pow () 'nin argümanı döngü sayacıdır. Korktuğum gibi, performanstaki fark belirgindir. -O2 bayrağı olmadan, sistemimdeki sonuçlar (Arch Linux 64-bit, g ++ 4.9.1, Intel i7-4930) şunlardı:
std::pow(i,2): 0.001105s (result = 3.33333e+07)
i*i: 0.000352s (result = 3.33333e+07)
std::pow(i,3): 0.006034s (result = 2.5e+07)
i*i*i: 0.000328s (result = 2.5e+07)
Optimizasyonla elde edilen sonuçlar da aynı derecede çarpıcıydı:
std::pow(i,2): 0.000155s (result = 3.33333e+07)
i*i: 0.000106s (result = 3.33333e+07)
std::pow(i,3): 0.006066s (result = 2.5e+07)
i*i*i: 9.7e-05s (result = 2.5e+07)
Öyleyse, derleyici en azından std :: pow (x, 2) durumunu optimize etmeye çalışıyor gibi görünüyor, ancak std :: pow (x, 3) durumunu değil (std :: pow'den ~ 40 kat daha uzun sürer (x, 2) durum). Her durumda, manuel genişletme daha iyi sonuç verdi - ancak özellikle 3. güç durumunda (60 kat daha hızlı). Sıkı bir döngüde 2'den büyük tamsayı güçleri ile std :: pow () çalıştırıyorsanız, bu kesinlikle akılda tutulmaya değerdir ...
En verimli yol, çarpımların üstel büyümesini dikkate almaktır. P ^ q için bu kodu kontrol edin:
template <typename T>
T expt(T p, unsigned q){
T r =1;
while (q != 0) {
if (q % 2 == 1) { // if q is odd
r *= p;
q--;
}
p *= p;
q /= 2;
}
return r;
}
Üs sabit ve küçükse, çarpma sayısını en aza indirerek genişletin. (Örneğin, x^4
optimal değil x*x*x*x
, ama y*y
nerede y=x*x
Ve. x^5
Olduğu y*y*x
yerde y=x*x
Ve böyle devam eder..) Sabit tamsayı üstlerin için, sadece zaten optimize formu yazma; küçük üslerle bu, kodun profilli olup olmadığına bakılmaksızın gerçekleştirilmesi gereken standart bir optimizasyondur. Optimize edilmiş form, temelde her zaman yapmaya değecek kadar büyük bir yüzdede daha hızlı olacaktır.
(Visual C ++ kullanırsanız, std::pow(float,int)
ima ettiğim optimizasyonu gerçekleştirir, burada işlem sırası üssün bit kalıbı ile ilgilidir. Derleyicinin sizin için döngüyü açacağına dair hiçbir garanti vermiyorum, bu yüzden yine de yapmaya değer elle.)
[değiştir] BTW'nin pow
profil oluşturucu sonuçlarında (hiç) şaşırtıcı olmayan bir eğilimi var. Buna kesinlikle ihtiyacınız yoksa (yani, üs büyükse veya sabit değilse) ve performans konusunda endişeleriniz varsa, en iyisi en iyi kodu yazıp profilcinin size bunu söylemesini bekleyin (şaşırtıcı bir şekilde ) daha fazla düşünmeden önce zaman kaybetmek. (Alternatifi arayıp pow
profilcinin size zaman kaybettiğini söylemesini sağlamaktır (şaşırtıcı bir şekilde) - akıllıca yaparak bu adımı atlıyorsunuz.)
Benzer bir problemle meşguldüm ve sonuçlar beni oldukça şaşırttı. Bir n-cisim durumunda Newton kütlesel çekimi için x⁻³ / ² hesaplıyordum (ivme, d mesafe vektöründe yer alan başka bir M kütlesinden kaynaklanan ivme): a = M G d*(d²)⁻³/²
(burada d², d'nin kendi başına nokta (skaler) çarpımıdır), ve hesaplamanın M*G*pow(d2, -1.5)
daha basit olacağını düşündümM*G/d2/sqrt(d2)
İşin püf noktası, bunun küçük sistemler için doğru olmasıdır, ancak sistemler büyüdükçe M*G/d2/sqrt(d2)
daha verimli hale gelir ve sistemin boyutunun bu sonucu neden etkilediğini anlamıyorum, çünkü işlemi farklı veriler üzerinde tekrarlamak etkilemez. Sanki sistem büyüdükçe olası optimizasyonlar varmış gibi, ancak bunlar mümkün değilpow
x
ayrılmaz veya kayan nokta?