C में कौन सी दो विधियाँ अधिक कुशल हैं? और इसके बारे में:
pow(x,3)
बनाम
x*x*x // etc?
C में कौन सी दो विधियाँ अधिक कुशल हैं? और इसके बारे में:
pow(x,3)
बनाम
x*x*x // etc?
जवाबों:
मैंने इस कोड का उपयोग करके छोटे x*x*...
बनाम pow(x,i)
के बीच प्रदर्शन अंतर का परीक्षण किया i
:
#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";
}
परिणाम हैं:
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
ध्यान दें कि मैं यह सुनिश्चित करने के लिए कि संकलक इसे दूर का अनुकूलन नहीं करने के लिए हर पॉव गणना का परिणाम जमा करता है।
यदि मैं std::pow(double, double)
संस्करण का उपयोग करता हूं , और loops = 1000000l
, मुझे मिलता है:
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
यह उबंटू 9.10 64 बिट पर चलने वाले एक इंटेल कोर डुओ पर है। -O2 अनुकूलन के साथ gcc 4.4.1 का उपयोग करके संकलित।
इसलिए C में, हाँ x*x*x
से तेज होगा pow(x, 3)
, क्योंकि कोई pow(double, int)
अधिभार नहीं है । C ++ में, यह लगभग एक ही होगा। (मेरे परीक्षण में कार्यप्रणाली को सही मान लेना।)
यह एक मार्क द्वारा की गई टिप्पणी के जवाब में है:
यहां तक कि अगर एक using namespace std
निर्देश जारी किया गया था, अगर दूसरा पैरामीटर के लिए pow
एक है int
, तो std::pow(double, int)
से अधिभार <cmath>
के बजाय बुलाया जाएगा ::pow(double, double)
से <math.h>
।
यह परीक्षण कोड उस व्यवहार की पुष्टि करता है:
#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
8 * लूप बार (घातांक> 2 के लिए) कहता है , जब तक कि आप उपयोग नहीं करते हैं -fno-math-errno
। फिर यह पाओ कॉल को लूप से बाहर खींच सकता है, जैसा कि मैंने सोचा था कि यह होगा। मुझे लगता है कि इरानो एक वैश्विक है, थ्रेड सेफ्टी के लिए यह आवश्यक है कि वह कई बार इरेटो को सेट करने के लिए पॉव को कॉल करे ... एक्सप = 1 और एक्सप = 2 फास्ट है क्योंकि पॉव कॉल लूप से सिर्फ -O3
(..) के साथ फहराया जाता है । ffast- गणित , यह लूप के बाहर भी 8 का योग करता है।)
pow
कॉल इनलाइन लूप से बाहर फहराए जाते हैं, इसलिए वहां एक बड़ा दोष है। इसके अलावा, ऐसा लगता है कि आप ज्यादातर एफपी जोड़ की विलंबता का परीक्षण कर रहे हैं, क्योंकि सभी परीक्षण समान मात्रा में चलते हैं। आप test5
की तुलना में धीमी होने की उम्मीद है test1
, लेकिन यह नहीं है। कई संचयकों का उपयोग निर्भरता श्रृंखला को विभाजित करेगा और विलंबता को छिपाएगा।
pow
को कभी भी बदलते मूल्य पर लागू करने का प्रयास करूँगा (दोहराया स्वर को बाहर निकालने से रोकने के लिए)।
यह गलत तरह का सवाल है। सही सवाल यह होगा: "मेरे कोड के मानव पाठकों के लिए कौन सा समझना आसान है?"
यदि गति (बाद में) मायने रखती है, तो न पूछें, लेकिन मापें। (और इससे पहले, मापें कि क्या यह वास्तव में अनुकूलन से कोई ध्यान देने योग्य अंतर होगा।) तब तक, कोड लिखें, ताकि यह पढ़ना आसान हो।
संपादित
बस बनाने के लिए यह स्पष्ट है (हालांकि यह पहले से ही किया जाना चाहिए था): सफलता speedups आमतौर पर तरह बातें से आते हैं बेहतर एल्गोरिथ्म का उपयोग , डेटा के इलाके में सुधार , गतिशील स्मृति के उपयोग को कम करने , पूर्व की गणना के परिणाम , आदि वे शायद ही कभी कभी से आते हैं एकल-अनुकूलन एकल फ़ंक्शन कॉल , और जहां वे करते हैं, वे बहुत कम जगहों पर ऐसा करते हैं , जो केवल सावधान (और समय लेने वाली) प्रोफाइलिंग द्वारा पाया जाएगा, अधिक बार कभी भी वे बहुत गैर-सहज ज्ञान युक्त करने से बच सकते हैं चीजें (डालने की तरह)noop
बयान), और एक मंच के लिए एक अनुकूलन क्या है कभी-कभी दूसरे के लिए एक निराशाकरण होता है (यही कारण है कि आपको पूछने के बजाय मापने की आवश्यकता है, क्योंकि हम आपके पर्यावरण को पूरी तरह से नहीं जानते / जानते हैं)।
मुझे इसे फिर से रेखांकित करें: यहां तक कि कुछ अनुप्रयोगों में जहां इस तरह की चीजें मायने रखती हैं, वे ज्यादातर उन जगहों पर मायने नहीं रखते हैं, जिनका उपयोग किया जाता है, और यह बहुत कम संभावना है कि आप उन स्थानों को ढूंढ पाएंगे जहां वे कोड को देखते हुए मायने रखते हैं। आपको वास्तव में पहले हॉट स्पॉट की पहचान करने की आवश्यकता है , क्योंकि अन्यथा अनुकूलन कोड केवल समय की बर्बादी है ।
यहां तक कि अगर एक एकल ऑपरेशन (जैसे कुछ मूल्य के वर्ग की गणना) अनुप्रयोग के निष्पादन के समय का 10% लेता है (जो आईएमई काफी दुर्लभ है), और यहां तक कि अगर यह अनुकूलन उस ऑपरेशन के लिए आवश्यक समय का 50% बचाता है (जो आईएमई है) इससे भी अधिक, बहुत दुर्लभ), आपने अभी भी आवेदन को केवल 5% कम समय लिया है ।
आपके उपयोगकर्ताओं को यह देखने के लिए स्टॉपवॉच की भी आवश्यकता होगी। (मैं ज्यादातर मामलों 20 कुछ भी के तहत% speedup अधिकांश उपयोगकर्ताओं के लिए किसी का ध्यान नहीं जाता है में लगता है। और कि चार ऐसे धब्बे आप को खोजने की जरूरत है।)
x*x
या x*x*x
तेजी से हो जाएगा pow
, के बाद से pow
सामान्य स्थिति के साथ जरूरी बात नहीं है, जबकि x*x
विशिष्ट है। इसके अलावा, आप फंक्शन कॉल और इस तरह के फोन को खत्म कर सकते हैं।
हालांकि, यदि आप अपने आप को इस तरह से माइक्रो-ऑप्टिमाइज़िंग पाते हैं, तो आपको एक प्रोफाइलर प्राप्त करने और कुछ गंभीर प्रोफाइलिंग करने की आवश्यकता है। अत्यधिक संभावना यह है कि आप कभी भी दोनों के बीच कोई अंतर नहीं देखेंगे।
x*x*x
बनाम डबल का परीक्षण किया std::pow(double base, int exponent)
और एक सांख्यिकीय रूप से सार्थक प्रदर्शन अंतर नहीं देख सकता।
मैं प्रदर्शन के मुद्दे के बारे में भी सोच रहा था, और उम्मीद कर रहा था कि यह @EmileCormier के उत्तर के आधार पर कंपाइलर द्वारा अनुकूलित किया जाएगा। हालाँकि, मुझे चिंता थी कि जो परीक्षण कोड उन्होंने दिखाया था, वह कंपाइलर को std :: pow () कॉल को ऑप्टिमाइज़ करने की अनुमति देगा, क्योंकि हर बार कॉल में समान मानों का उपयोग किया गया था, जो कंपाइलर को परिणामों को संग्रहीत करने की अनुमति देगा और लूप में इसे फिर से उपयोग करें - यह सभी मामलों के लिए लगभग समान रन-टाइम की व्याख्या करेगा। इसलिए मैंने इस पर भी गौर किया।
यहां मैंने जो कोड इस्तेमाल किया है (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;
}
यह प्रयोग करके संकलित किया गया था:
g++ -std=c++11 [-O2] test_pow.cpp -o test_pow
मूल रूप से, अंतर std :: pow () लूप काउंटर है। जैसा कि मुझे डर था, प्रदर्शन में अंतर स्पष्ट है। -O2 ध्वज के बिना, मेरे सिस्टम पर परिणाम (आर्क लिनक्स 64-बिट, जी ++ 4.9.1, इंटेल i7-492):
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)
अनुकूलन के साथ, परिणाम समान रूप से हड़ताली थे:
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)
तो ऐसा लगता है कि कंपाइलर कम से कम std :: pow (x, 2) केस को ऑप्टिमाइज़ करने की कोशिश करता है, लेकिन std नहीं: pow (x, 3) केस (इसे std की तुलना में ~ 40 गुना अधिक समय लगता है :: pow (x, 2) मामला)। सभी मामलों में, मैन्युअल विस्तार ने बेहतर प्रदर्शन किया - लेकिन विशेष रूप से पावर 3 केस (60 गुना तेज) के लिए। यह निश्चित रूप से ध्यान देने योग्य है अगर std :: pow () पूर्णांक शक्तियों के साथ एक तंग पाश में 2 से अधिक हो ...
सबसे कुशल तरीका गुणन के घातीय वृद्धि पर विचार करना है। इस कोड को p ^ q के लिए जांचें:
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;
}
यदि प्रतिपादक स्थिर और छोटा है, तो इसे विस्तार करें, गुणा की संख्या कम से कम करें। (उदाहरण के लिए, x^4
नहीं बेहतर है x*x*x*x
, लेकिन y*y
जहां y=x*x
और। x^5
है y*y*x
जहां y=x*x
लगातार पूर्णांक घातांक के लिए, बस पहले से ही अनुकूलित फ़ॉर्म को लिखने और इतने पर।।); छोटे घातांक के साथ, यह एक मानक अनुकूलन है जिसे निष्पादित किया जाना चाहिए कि क्या कोड को प्रोफाइल किया गया है या नहीं। अनुकूलित रूप इतने बड़े मामलों में त्वरित होगा कि यह मूल रूप से हमेशा करने योग्य है।
(यदि आप विज़ुअल C ++ का उपयोग करते हैं, std::pow(float,int)
तो ऑप्टिमाइज़ेशन I allude to करता है, जिससे ऑपरेशन का क्रम एक्सपोनेंट के बिट पैटर्न से संबंधित है। मैं इस बात की कोई गारंटी नहीं देता कि कंपाइलर आपके लिए लूप को अनियंत्रित कर देगा, हालाँकि, यह अभी भी करने लायक है। यह हाथ से।)
[संपादित करें] बीटीडब्लू pow
में एक (संयुक्त राष्ट्र) के प्रोफाइलर परिणामों पर फसल लगाने की आश्चर्यजनक प्रवृत्ति है। यदि आपको इसकी बिल्कुल आवश्यकता नहीं है (यानी, घातांक बड़ा है या स्थिर नहीं है), और आप प्रदर्शन के बारे में चिंतित हैं, तो सबसे अच्छा है कि इष्टतम कोड लिखें और प्रोफाइलर को आपको यह बताने के लिए प्रतीक्षा करें (आश्चर्यजनक रूप से) ) आगे सोचने से पहले समय बर्बाद करना। (विकल्प कॉल करने के लिए है pow
और प्रोफाइलर आपको बता रहा है कि यह (आश्चर्यजनक रूप से) समय बर्बाद कर रहा है - आप समझदारी से इस कदम को काट रहे हैं।)
मैं इसी तरह की समस्या में व्यस्त हूं, और मैं परिणामों से काफी हैरान हूं। मैं एक एन-बॉडी स्थिति में न्यूटनियन गुरुत्वाकर्षण के लिए x was / ⁻³ की गणना कर रहा था (एक दूरी वेक्टर d पर स्थित द्रव्यमान M के दूसरे शरीर से त्वरण की गति): a = M G d*(d²)⁻³/²
(जहाँ d² अपने आप में d का डॉट (स्केलर) उत्पाद है), और मुझे लगा कि गणना M*G*pow(d2, -1.5)
करना इससे सरल होगाM*G/d2/sqrt(d2)
चाल यह है कि यह छोटे सिस्टम के लिए सही है, लेकिन जैसे-जैसे सिस्टम आकार में बढ़ता है, M*G/d2/sqrt(d2)
अधिक कुशल हो जाता है और मुझे समझ में नहीं आता है कि सिस्टम का आकार इस परिणाम को क्यों प्रभावित करता है, क्योंकि विभिन्न डेटा पर ऑपरेशन को दोहराता नहीं है। यह ऐसा है जैसे कि सिस्टम बढ़ने के साथ संभावित अनुकूलन थे, लेकिन जो संभव नहीं हैंpow
x
अभिन्न या चल बिन्दु?