C और C ++ में लगभग समान कोड के बीच निष्पादन समय में बड़ा अंतर (x9)


85

मैं www.spoj.com: FCTRL - Factorial से इस अभ्यास को हल करने की कोशिश कर रहा था

आपको वास्तव में इसे पढ़ने की ज़रूरत नहीं है, अगर आप उत्सुक हैं तो बस इसे करें :)

पहले मैंने इसे C ++ में लागू किया (यहाँ मेरा समाधान है):

#include <iostream>
using namespace std;

int main() {
    unsigned int num_of_inputs;
    unsigned int fact_num;
    unsigned int num_of_trailing_zeros;

    std::ios_base::sync_with_stdio(false); // turn off synchronization with the C library’s stdio buffers (from https://stackoverflow.com/a/22225421/5218277)

    cin >> num_of_inputs;

    while (num_of_inputs--)
    {
        cin >> fact_num;

        num_of_trailing_zeros = 0;

        for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
            num_of_trailing_zeros += fact_num/fives;

        cout << num_of_trailing_zeros << "\n";
    }

    return 0;
}

मैंने इसे g ++ 5.1 के समाधान के रूप में अपलोड किया

परिणाम था: समय 0.18 मेम 3.3M C ++ निष्पादन परिणाम

लेकिन फिर मैंने कुछ टिप्पणियां देखीं जिनमें दावा किया गया था कि उनका समय निष्पादन 0.1 से कम था। चूंकि मैं तेज एल्गोरिथ्म के बारे में नहीं सोच सकता था, इसलिए मैंने सी में समान कोड लागू करने की कोशिश की :

#include <stdio.h>

int main() {
    unsigned int num_of_inputs;
    unsigned int fact_num;
    unsigned int num_of_trailing_zeros;

    scanf("%d", &num_of_inputs);

    while (num_of_inputs--)
    {
        scanf("%d", &fact_num);

        num_of_trailing_zeros = 0;

        for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
            num_of_trailing_zeros += fact_num/fives;

        printf("%d", num_of_trailing_zeros);
        printf("%s","\n");
    }

    return 0;
}

मैंने इसे gcc 5.1 के समाधान के रूप में अपलोड किया

इस बार परिणाम था: समय 0.02 मेम 2.1M C निष्पादन परिणाम

अब कोड लगभग समान है , मैंने std::ios_base::sync_with_stdio(false);C ++ कोड में जोड़ा है जैसा कि सी लाइब्रेरी के stdio बफ़र्स के साथ सिंक्रनाइज़ेशन को बंद करने के लिए यहां सुझाया गया था । मैं भी विभाजित printf("%d\n", num_of_trailing_zeros);करने printf("%d", num_of_trailing_zeros); printf("%s","\n");की दोहरी कॉल के लिए क्षतिपूर्ति करने operator<<में cout << num_of_trailing_zeros << "\n";

लेकिन मैंने अभी भी सी बनाम सी ++ कोड में x9 बेहतर प्रदर्शन और कम मेमोरी उपयोग को देखा ।

ऐसा क्यों है?

संपादित करें

मैंने सी कोड में तय unsigned longकिया unsigned int। यह होना चाहिए था unsigned intऔर जो परिणाम ऊपर दिखाए गए हैं वे नए ( unsigned int) संस्करण से संबंधित हैं ।


31
C ++ स्ट्रीम डिज़ाइन द्वारा बेहद धीमी हैं। क्योंकि धीमी और स्थिर दौड़ जीतती है। : P ( इससे पहले कि मैं
भड़क उठता हूं

7
सुस्ती सुरक्षा या अनुकूलनशीलता से नहीं आती है। यह सभी स्ट्रीम झंडे के साथ अतिदेय है।
कारोली होरवाथ

8
@AlexLop। std::ostringstreamआउटपुट को जमा करने के लिए और अंत std::cout में एक बार में सभी को भेजने का उपयोग करने से समय 0.02 तक कम हो जाता है। std::coutलूप का उपयोग करना उनके वातावरण में बस धीमा है और मुझे नहीं लगता कि इसमें सुधार करने का कोई सरल तरीका है।
ब्लास्टफर्नेस

6
क्या इस तथ्य से कोई और संबंधित नहीं है कि ये समय विचारधारा का उपयोग करके प्राप्त किए गए थे?
इलडाजर्न

6
@ ओलाफ: मुझे डर है कि मैं असहमत हूं, इस तरह का सवाल सभी चुने गए टैग के लिए विषय पर बहुत अधिक है। C और C ++ सामान्य रूप से पर्याप्त करीब हैं कि प्रदर्शन में ऐसा अंतर स्पष्टीकरण के लिए भीख माँगता है। मुझे खुशी है कि हमने इसे पाया। हो सकता है कि GNU libc ++ को परिणाम के रूप में सुधारा जाए।
चकरली

जवाबों:


56

दोनों कार्यक्रम बिल्कुल एक जैसे काम करते हैं। वे एक ही सटीक एल्गोरिदम का उपयोग करते हैं, और इसकी कम जटिलता को देखते हुए, उनका प्रदर्शन ज्यादातर इनपुट और आउटपुट हैंडलिंग की दक्षता के लिए बाध्य है।

scanf("%d", &fact_num);एक तरफ और cin >> fact_num;दूसरी तरफ इनपुट को स्कैन करना बहुत महंगा नहीं लगता है। वास्तव में यह C ++ में कम खर्चीला होना चाहिए क्योंकि रूपांतरण के प्रकार को संकलन समय पर जाना जाता है और सही पार्सर को सीधे C ++ कंपाइलर द्वारा लागू किया जा सकता है। वही आउटपुट के लिए रखती है। आप इसके लिए एक अलग कॉल लिखने की बात भी करते हैं printf("%s","\n");, लेकिन सी कंपाइलर इसे कॉल के रूप में संकलित करने के लिए पर्याप्त है putchar('\n');

इसलिए I / O और अभिकलन दोनों की जटिलता को देखते हुए, C ++ संस्करण C संस्करण से अधिक तेज़ होना चाहिए।

पूरी तरह से अक्षम करने के stdoutलिए C के कार्यान्वयन को धीमा करता है C C ++ संस्करण की तुलना में कुछ धीमा भी। fflush(stdout);अंतिम कड़ी के बाद printfC ++ संस्करण के समान प्रदर्शन के साथ एलेक्सलोप द्वारा एक और परीक्षण । यह बफ़रिंग को पूरी तरह से अक्षम करने के रूप में धीमा नहीं है क्योंकि एक बार में एक बाइट के बजाय छोटे विखंडू में सिस्टम को आउटपुट लिखा जाता है।

यह आपके C ++ लाइब्रेरी में एक विशिष्ट व्यवहार को इंगित करता है: मुझे आपके सिस्टम के कार्यान्वयन पर संदेह है cinऔर इनपुट से अनुरोध किए जाने पर coutआउटपुट को फ्लश करता coutहै cin। कुछ C पुस्तकालयों के रूप में अच्छी तरह से करते हैं, लेकिन आमतौर पर केवल पढ़ने और लिखने के लिए या टर्मिनल से। Www.spoj.com साइट द्वारा की गई बेंचमार्किंग शायद फाइलों से इनपुट और आउटपुट को रीडायरेक्ट करती है।

एलेक्सलॉप ने एक और परीक्षण किया: वेक्टर में एक बार में सभी इनपुटों को पढ़ना और बाद में सभी आउटपुट को कंप्यूटिंग और लिखने से यह समझने में मदद मिलती है कि C ++ संस्करण इतना धीमा क्यों है। यह सी संस्करण के प्रदर्शन को बढ़ाता है, यह मेरी बात साबित करता है और सी ++ प्रारूपण कोड पर संदेह को दूर करता है।

ब्लास्टफर्न द्वारा एक और परीक्षण, सभी आउटपुट को एक में संग्रहीत करता है std::ostringstreamऔर फ्लशिंग करता है कि अंत में एक विस्फोट में, मूल सी संस्करण के C ++ प्रदर्शन में सुधार होता है। QED।

धारा से बफरिंग योजना को पराजित करते हुए इनपुट cinऔर आउटपुट से इंटरलास्टिंग इनपुट coutबहुत ही अक्षम I / O हैंडलिंग का कारण बनता है। 10 के कारक द्वारा प्रदर्शन को कम करना।

पुनश्च: आपका एल्गोरिथ्म गलत है fact_num >= UINT_MAX / 5क्योंकि fives *= 5इससे पहले कि यह ओवरफ्लो और लपेट जाएगा > fact_num। आप बनाकर इस सही कर सकते हैं fivesएक unsigned longया एक unsigned long longअगर इन प्रकारों में से एक से भी बड़ा है unsigned int। प्रारूप के %uरूप में भी उपयोग करें scanf। आप भाग्यशाली हैं कि www.spoj.com के लोग अपने बेंचमार्क में बहुत सख्त नहीं हैं।

संपादित करें: जैसा कि बाद में विटेक्स द्वारा समझाया गया है, यह व्यवहार वास्तव में सी ++ मानक द्वारा अनिवार्य है। डिफ़ॉल्ट रूप cinसे बंधा हुआ है cout। एक इनपुट ऑपरेशन cinजिसके लिए इनपुट बफर को रिफिलिंग की आवश्यकता है, coutलंबित आउटपुट को फ्लश करेगा । ओपी के कार्यान्वयन में, व्यवस्थित रूप cinसे फ्लश लगता है cout, जो थोड़ा ओवरकिल और नेत्रहीन अक्षम है।

इल्या पोपोव ने इसके लिए एक सरल समाधान प्रदान किया : इसके अलावा एक और जादुई मंत्र कास्टिंग करके इससे cinछुटकारा पाया जा सकता coutहै std::ios_base::sync_with_stdio(false);:

cin.tie(nullptr);

यह भी ध्यान दें कि इस तरह के मजबूर फ्लश भी तब होते हैं जब लाइन के अंत का उत्पादन करने के std::endlबजाय उपयोग करते हैं । अधिक सी ++ मुहावरेदार और निर्दोष दिखने के लिए आउटपुट लाइन को बदलना उसी तरह प्रदर्शन को नीचा दिखाएगा।'\n'coutcout << num_of_trailing_zeros << endl;


2
आप शायद स्ट्रीम फ्लशिंग के बारे में सही हैं। आउटपुट को एक में इकट्ठा करना std::ostringstreamऔर इसे एक बार अंत में आउटपुट करना, सी संस्करण के साथ समता के लिए समय नीचे लाता है।
ब्लास्टफर्नेस

2
@ DavidC.Rankin: मैंने एक अनुमान व्यक्त किया (कॉट सिन पढ़ने पर भड़क जाता है), इसे साबित करने का एक तरीका तैयार किया, एलेक्सलॉप ने इसे लागू किया और यह पुख्ता सबूत देता है, लेकिन ब्लास्टफर्नेस ने अपनी बात और अपनी परीक्षा को साबित करने के लिए एक अलग तरीका अपनाया। समान रूप से ठोस सबूत देना। मैं इसे प्रमाण के लिए लेता हूं, लेकिन निश्चित रूप से यह पूरी तरह से औपचारिक प्रमाण नहीं है, सी ++ स्रोत कोड को देख सकता है।
चकरली

2
मैंने ostringstreamआउटपुट के लिए उपयोग करने की कोशिश की और इसने समय 0.02 QED :) दिया। के बारे में fact_num >= UINT_MAX / 5, अच्छा बिंदु!
एलेक्स लोप।

1
सभी इनपुटों को एक में एकत्रित करना vectorऔर फिर गणनाओं को संसाधित करना (बिना ostringstream) एक ही परिणाम देता है! समय 0.02। दोनों को मिलाना vectorऔर ostringstreamइसमें और सुधार नहीं करना। वही समय 0.02
एलेक्स लोप।

2
एक सरल फिक्स जो काम करता है भले ही sizeof(int) == sizeof(long long)यह है: लूप के शरीर में एक परीक्षण जोड़ें num_of_trailing_zeros += fact_num/fives;अगर fivesयह जांचने के बाद कि इसकी अधिकतम तक पहुंच गया है:if (fives > UINT_MAX / 5) break;
chqrlie

44

iostreamजब आप दोनों का उपयोग करते हैं cinऔर coutकॉल करने के लिए तेजी से बनाने के लिए एक और चाल है

cin.tie(nullptr);

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

std::string name;
cout << "Enter your name:";
cin >> name;

इस मामले में आप सुनिश्चित करना चाहते हैं कि इनपुट के लिए प्रतीक्षा शुरू करने से पहले प्रॉम्प्ट वास्तव में दिखाया गया है। ऊपर की रेखा के साथ आप उस टाई को तोड़ देते हैं, cinऔर coutस्वतंत्र हो जाते हैं।

C ++ 11 के बाद से, iostreams के साथ बेहतर प्रदर्शन प्राप्त करने का एक और तरीका इस तरह से std::getlineएक साथ उपयोग करना है std::stoi:

std::string line;
for (int i = 0; i < n && std::getline(std::cin, line); ++i)
{
    int x = std::stoi(line);
}

यह तरीका प्रदर्शन में C- शैली के करीब आ सकता है, या इससे भी आगे निकल सकता है scanf। उपयोग करना getcharऔर विशेष रूप getchar_unlockedसे हस्तलिखित पार्सिंग के साथ मिलकर अभी भी बेहतर प्रदर्शन प्रदान करता है।

पुनश्च। मैंने C ++ में इनपुट नंबरों के कई तरीकों की तुलना करते हुए एक पोस्ट लिखी है , जो ऑनलाइन जजों के लिए उपयोगी है, लेकिन यह केवल रूसी में है, क्षमा करें। कोड नमूने और अंतिम तालिका, हालांकि, समझने योग्य होनी चाहिए।


1
स्पष्टीकरण और +1 समाधान के लिए के लिए धन्यवाद, लेकिन साथ अपने प्रस्तावित विकल्प std::readlineऔर std::stoiकार्यात्मक रूप ऑप्स कोड के बराबर नहीं है। दोनों cin >> x;और scanf("%f", &x);एंटी व्हॉट्सएप को विभाजक के रूप में स्वीकार करते हैं, एक ही लाइन पर कई नंबर हो सकते हैं।
चकरली

27

समस्या यह है कि, अपंगता को उद्धृत करना :

std :: cin, आउटपुट से std :: cerr, या प्रोग्राम टर्मिनेशन का कोई भी इनपुट std :: cout.flx () पर कॉल करने के लिए बाध्य करता है

यह परीक्षण करना आसान है: यदि आप प्रतिस्थापित करते हैं

cin >> fact_num;

साथ में

scanf("%d", &fact_num);

और उसी के लिए cin >> num_of_inputsलेकिन coutआप अपने C ++ संस्करण (या, बल्कि IOStream संस्करण) में C:

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

ऐसा ही होता है यदि आप रखते हैं cinलेकिन प्रतिस्थापित करते हैं

cout << num_of_trailing_zeros << "\n";

साथ में

printf("%d", num_of_trailing_zeros);
printf("%s","\n");

एक सरल उपाय है अनटेली coutऔर cinजैसा कि इल्या पोपोव ने उल्लेख किया है:

cin.tie(nullptr);

मानक पुस्तकालय कार्यान्वयन को कुछ मामलों में फ्लश करने के लिए कॉल को छोड़ने की अनुमति है, लेकिन हमेशा नहीं। यहाँ C ++ 14 27.7.2.1.3 से एक उद्धरण है (chrrlie के लिए धन्यवाद):

क्लास बेसिक_स्ट्रीम :: संतरी: पहला, अगर is.tie () एक अशक्त सूचक नहीं है, तो फ़ंक्शन किसी भी संबंधित बाहरी सी स्ट्रीम के साथ आउटपुट अनुक्रम को सिंक्रनाइज़ करने के लिए is.tie () -> फ्लश () को कॉल करता है। सिवाय इसके कि इस कॉल को दबाया जा सकता है यदि is.tie () का पुट क्षेत्र खाली है। इसके अलावा एक कार्यान्वयन को तब तक फ्लश करने की अनुमति दी जाती है जब तक कि is.rdbuf () -> अंडरफ्लो () कॉल न हो जाए। यदि संतरी वस्तु नष्ट होने से पहले ऐसी कोई कॉल नहीं आती है, तो फ्लश करने के लिए कॉल पूरी तरह से समाप्त हो सकती है।


स्पष्टीकरण के लिए धन्यवाद। फिर भी C ++ 14 27.7.2.1.3 को उद्धृत करते हुए: क्लास बेसिक_स्ट्रीम :: संतरी : सबसे पहले, यदि is.tie()एक शून्य पॉइंटर नहीं है, तो फ़ंक्शन is.tie()->flush()किसी भी संबंधित बाहरी सी स्ट्रीम के साथ आउटपुट अनुक्रम को सिंक्रनाइज़ करने के लिए कहता है। सिवाय इसके कि इस कॉल को दबाया जा सकता है अगर का पुट एरिया is.tie()खाली है। इसके अलावा जब तक कोई कॉल नहीं is.rdbuf()->underflow()होती तब तक एक कार्यान्वयन को कॉल को स्थगित करने की अनुमति दी जाती है। यदि संतरी वस्तु नष्ट होने से पहले ऐसी कोई कॉल नहीं आती है, तो फ्लश करने के लिए कॉल पूरी तरह से समाप्त हो सकती है।
चकरली

सी ++ के साथ हमेशा की तरह, चीजें जितनी दिखती हैं, उससे कहीं अधिक जटिल हैं। ओपी की सी ++ लाइब्रेरी उतनी कुशल नहीं है, जितनी स्टैंडर्ड की अनुमति है।
चकरली

Cppreference लिंक के लिए धन्यवाद। मैं प्रिंट स्क्रीन में "गलत जवाब" पसंद नहीं करता, हालांकि L
एलेक्स लोप।

@AlexLop। उफ़, "गलत उत्तर" मुद्दा =) तय किया। दूसरे सिने को अपडेट करना भूल गए (हालांकि यह समय को प्रभावित नहीं करता है)।
वितुत्त v

@chqrlie अधिकार, लेकिन यहां तक ​​कि अंडरफ्लो के मामले में भी प्रदर्शन की संभावना stdio समाधान की तुलना में खराब होने की संभावना है। मानक रेफरी के लिए धन्यवाद।
विट्टुत
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.