अधिक कुशल क्या है? चौरस करने के लिए वर्ग का उपयोग करना या इसे अपने आप से गुणा करना?


119

C में कौन सी दो विधियाँ अधिक कुशल हैं? और इसके बारे में:

pow(x,3)

बनाम

x*x*x // etc?

9
है xअभिन्न या चल बिन्दु?
मैथ्यू फ्लैशेन

6
आप एक प्रोग्राम लिखने की कोशिश कर सकते हैं जो उपरोक्त दो ऑपरेशन करता है, और समय एक प्रोफाइलिंग लाइब्रेरी के साथ निष्पादन में कितना समय लगता है। यह आपको निष्पादन समय के संदर्भ में एक अच्छा जवाब देगा।
जे। पोलफर

3
जब आप कुशल कहते हैं, तो क्या आप समय, या स्थान (यानी, स्मृति उपयोग) की बात कर रहे हैं?
जे। पोलफर

4
@sheepsimulator: +1 मुझे (फिर से) के लिए आवश्यक समय बचाने के लिए इंगित करता है कि एक त्वरित परीक्षण लिखने से आपको एक निश्चित उत्तर तेजी से मिलेगा, ताकि आपको एसओ से एक संभावित अस्पष्ट या गलत उत्तर मिल जाएगा।
बस मेरा सही समय

5
@kirill_igum यदि वे फ़्लोटिंग पॉइंट मान हैं जो बग नहीं है, तो फ़्लोटिंग पॉइंट अंकगणित सहयोगी नहीं है।
15

जवाबों:


82

मैंने इस कोड का उपयोग करके छोटे 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;
}

1
इसका मतलब यह है कि "नाम स्थान std का उपयोग कर" C विकल्प चुनता है और यह रनटाइम के लिए हानिकारक होगा?
एंड्रियास

आपके दोनों समय के छोरों में, पॉव गणना संभवतः केवल एक बार होती है। gcc -O2 को लूप-इनवेरिएंट एक्सप्रेशन को लूप से बाहर फैंकने में कोई समस्या नहीं होनी चाहिए। तो आप बस परीक्षण कर रहे हैं कि ऐड-कंटेंट लूप को एक गुणा में बदलने पर कंपाइलर कितनी अच्छी तरह से काम करता है, या सिर्फ एड-कंटेंट लूप का अनुकूलन करता है। एक कारण यह है कि आपके छोरों में घातांक = 1 बनाम घातांक = 5 के साथ समान गति है, यहां तक ​​कि लिखित संस्करण के लिए भी।
पीटर कॉर्ड्स

2
मैंने इसे गॉडबोल्ट पर आज़माया (समय के साथ टिप्पणी की, क्योंकि गॉडबोल्ट में बूस्ट स्थापित नहीं है)। यह आश्चर्यजनक रूप से वास्तव में std::pow8 * लूप बार (घातांक> 2 के लिए) कहता है , जब तक कि आप उपयोग नहीं करते हैं -fno-math-errno। फिर यह पाओ कॉल को लूप से बाहर खींच सकता है, जैसा कि मैंने सोचा था कि यह होगा। मुझे लगता है कि इरानो एक वैश्विक है, थ्रेड सेफ्टी के लिए यह आवश्यक है कि वह कई बार इरेटो को सेट करने के लिए पॉव को कॉल करे ... एक्सप = 1 और एक्सप = 2 फास्ट है क्योंकि पॉव कॉल लूप से सिर्फ -O3(..) के साथ फहराया जाता है । ffast- गणित , यह लूप के बाहर भी 8 का योग करता है।)
पीटर कॉर्ड्स

इससे पहले कि मैं महसूस करता था कि मेरे पास गॉडबॉल्ट सत्र में -फास्ट-गणित है जो मैं उपयोग कर रहा था। उसके बिना भी, testpow <1> और testpow <2> टूट जाते हैं, क्योंकि वे powकॉल इनलाइन लूप से बाहर फहराए जाते हैं, इसलिए वहां एक बड़ा दोष है। इसके अलावा, ऐसा लगता है कि आप ज्यादातर एफपी जोड़ की विलंबता का परीक्षण कर रहे हैं, क्योंकि सभी परीक्षण समान मात्रा में चलते हैं। आप test5की तुलना में धीमी होने की उम्मीद है test1, लेकिन यह नहीं है। कई संचयकों का उपयोग निर्भरता श्रृंखला को विभाजित करेगा और विलंबता को छिपाएगा।
पीटर कॉर्ड्स

@PeterCordes, आप 5 साल पहले कहां थे? :-) मैं अपने मानदंड powको कभी भी बदलते मूल्य पर लागू करने का प्रयास करूँगा (दोहराया स्वर को बाहर निकालने से रोकने के लिए)।
एमिल कॉर्मियर

30

यह गलत तरह का सवाल है। सही सवाल यह होगा: "मेरे कोड के मानव पाठकों के लिए कौन सा समझना आसान है?"

यदि गति (बाद में) मायने रखती है, तो न पूछें, लेकिन मापें। (और इससे पहले, मापें कि क्या यह वास्तव में अनुकूलन से कोई ध्यान देने योग्य अंतर होगा।) तब तक, कोड लिखें, ताकि यह पढ़ना आसान हो।

संपादित
बस बनाने के लिए यह स्पष्ट है (हालांकि यह पहले से ही किया जाना चाहिए था): सफलता speedups आमतौर पर तरह बातें से आते हैं बेहतर एल्गोरिथ्म का उपयोग , डेटा के इलाके में सुधार , गतिशील स्मृति के उपयोग को कम करने , पूर्व की गणना के परिणाम , आदि वे शायद ही कभी कभी से आते हैं एकल-अनुकूलन एकल फ़ंक्शन कॉल , और जहां वे करते हैं, वे बहुत कम जगहों पर ऐसा करते हैं , जो केवल सावधान (और समय लेने वाली) प्रोफाइलिंग द्वारा पाया जाएगा, अधिक बार कभी भी वे बहुत गैर-सहज ज्ञान युक्त करने से बच सकते हैं चीजें (डालने की तरह)noop बयान), और एक मंच के लिए एक अनुकूलन क्या है कभी-कभी दूसरे के लिए एक निराशाकरण होता है (यही कारण है कि आपको पूछने के बजाय मापने की आवश्यकता है, क्योंकि हम आपके पर्यावरण को पूरी तरह से नहीं जानते / जानते हैं)।

मुझे इसे फिर से रेखांकित करें: यहां तक ​​कि कुछ अनुप्रयोगों में जहां इस तरह की चीजें मायने रखती हैं, वे ज्यादातर उन जगहों पर मायने नहीं रखते हैं, जिनका उपयोग किया जाता है, और यह बहुत कम संभावना है कि आप उन स्थानों को ढूंढ पाएंगे जहां वे कोड को देखते हुए मायने रखते हैं। आपको वास्तव में पहले हॉट स्पॉट की पहचान करने की आवश्यकता है , क्योंकि अन्यथा अनुकूलन कोड केवल समय की बर्बादी है

यहां तक ​​कि अगर एक एकल ऑपरेशन (जैसे कुछ मूल्य के वर्ग की गणना) अनुप्रयोग के निष्पादन के समय का 10% लेता है (जो आईएमई काफी दुर्लभ है), और यहां तक ​​कि अगर यह अनुकूलन उस ऑपरेशन के लिए आवश्यक समय का 50% बचाता है (जो आईएमई है) इससे भी अधिक, बहुत दुर्लभ), आपने अभी भी आवेदन को केवल 5% कम समय लिया है
आपके उपयोगकर्ताओं को यह देखने के लिए स्टॉपवॉच की भी आवश्यकता होगी। (मैं ज्यादातर मामलों 20 कुछ भी के तहत% speedup अधिकांश उपयोगकर्ताओं के लिए किसी का ध्यान नहीं जाता है में लगता है। और कि चार ऐसे धब्बे आप को खोजने की जरूरत है।)


43
यह सही तरह का सवाल हो सकता है। शायद वह अपनी खुद की व्यावहारिक परियोजना के बारे में नहीं सोच रहा है, लेकिन केवल इस बात में दिलचस्पी रखता है कि
लैंगगेज

137
स्टैकओवरफ़्लो में एक बटन होना चाहिए जो एक मानक अस्वीकरण सम्मिलित करता है: "मुझे पहले से ही पता है कि समय से पहले अनुकूलन बुराई है, लेकिन मैं अकादमिक उद्देश्यों के लिए यह अनुकूलन प्रश्न पूछ रहा हूं या मैंने पहले ही उस लाइन / कोड को एक अड़चन के रूप में पहचान लिया है"।
एमिल कॉर्मियर

39
मुझे नहीं लगता कि पठनीयता यहां कोई मुद्दा है। लेखन x * x बनाम pow (x, 2) दोनों काफी स्पष्ट हैं।
किलियनडीएस

41
आंखों पर आसान नहीं, बोल्ड और इटैलिक का अधिक उपयोग।
स्टैगस

24
मैं इस जवाब से पूरी तरह सहमत नहीं हूं। प्रदर्शन के बारे में पूछना एक वैध प्रश्न है। आपके द्वारा प्राप्त किया जा सकता सबसे अच्छा प्रदर्शन कभी-कभी एक वैध आवश्यकता है, और अक्सर इसका कारण किसी अन्य भाषा के बजाय c ++ का उपयोग किया जाता है। और माप हमेशा एक अच्छा विचार नहीं है। मैं बबल सॉर्ट और एस्कॉर्ट को माप सकता हूं और अपने 10 आइटम के साथ तेजी से बुलबुले खोज सकता हूं क्योंकि मेरे पास यह जानने के लिए पृष्ठभूमि नहीं थी कि आइटमों की संख्या बेहद मायने रखती है और बाद में मेरे 1,000,000 आइटमों के साथ मिलें यह बहुत बुरा विकल्प था।
jcoder

17

x*xया x*x*xतेजी से हो जाएगा pow, के बाद से powसामान्य स्थिति के साथ जरूरी बात नहीं है, जबकि x*xविशिष्ट है। इसके अलावा, आप फंक्शन कॉल और इस तरह के फोन को खत्म कर सकते हैं।

हालांकि, यदि आप अपने आप को इस तरह से माइक्रो-ऑप्टिमाइज़िंग पाते हैं, तो आपको एक प्रोफाइलर प्राप्त करने और कुछ गंभीर प्रोफाइलिंग करने की आवश्यकता है। अत्यधिक संभावना यह है कि आप कभी भी दोनों के बीच कोई अंतर नहीं देखेंगे।


7
मैं एक ही बात सोच रहा था जब तक मैंने इसे परखने का फैसला नहीं किया। मैंने समयबद्ध लूप में बस x*x*xबनाम डबल का परीक्षण किया std::pow(double base, int exponent)और एक सांख्यिकीय रूप से सार्थक प्रदर्शन अंतर नहीं देख सकता।
एमिल कॉर्मियर

2
सुनिश्चित करें कि यह संकलक द्वारा दूर अनुकूलित नहीं हो रहा है।
पोंकाडूडल

1
@ ईमाइल: कंपाइलर द्वारा उत्पन्न कोड की जाँच करें। ऑप्टिमाइज़र कभी-कभी कुछ मुश्किल (और असभ्य) चीजें करते हैं। विभिन्न अनुकूलन स्तरों पर प्रदर्शन की भी जाँच करें: -O0, -O1, -O2 और -O3 उदाहरण के लिए।
बस मेरा सही समय

2
आप यह नहीं मान सकते हैं कि सामान्यीकृत कार्य धीमा हैं। कभी-कभी विपरीत भी सही होता है क्योंकि कंपाइलर का अनुकूलन करने के लिए सरल कोड आसान होता है।
२०:१

5

मैं प्रदर्शन के मुद्दे के बारे में भी सोच रहा था, और उम्मीद कर रहा था कि यह @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 से अधिक हो ...


4

सबसे कुशल तरीका गुणन के घातीय वृद्धि पर विचार करना है। इस कोड को 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;
}

2

यदि प्रतिपादक स्थिर और छोटा है, तो इसे विस्तार करें, गुणा की संख्या कम से कम करें। (उदाहरण के लिए, 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और प्रोफाइलर आपको बता रहा है कि यह (आश्चर्यजनक रूप से) समय बर्बाद कर रहा है - आप समझदारी से इस कदम को काट रहे हैं।)


0

मैं इसी तरह की समस्या में व्यस्त हूं, और मैं परिणामों से काफी हैरान हूं। मैं एक एन-बॉडी स्थिति में न्यूटनियन गुरुत्वाकर्षण के लिए 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

यहां छवि विवरण दर्ज करें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.