c++ पाइथन की तुलना में सी++ में स्टडीन से बहुत धीमी गति से लाइनें क्यों पढ़ रही हैं?




python benchmarking (8)

वैसे, कारण सी ++ संस्करण के लिए लाइन गिनती पाइथन संस्करण के लिए गिनती से अधिक है क्योंकि ईओएफ ध्वज केवल ईओ से परे पढ़ने के लिए प्रयास किए जाने पर सेट हो जाता है। तो सही पाश होगा:

while (cin) {
    getline(cin, input_line);

    if (!cin.eof())
        line_count++;
};

मैं पायथन और सी ++ का उपयोग करके स्टडीन से स्ट्रिंग इनपुट की रीडिंग लाइनों की तुलना करना चाहता था और मेरे सी ++ कोड को बराबर पाइथन कोड की तुलना में धीमी गति के क्रम को चलाने के लिए चौंका दिया गया था। चूंकि मेरा सी ++ जंगली है और मैं अभी तक एक विशेषज्ञ पायथनिस्ट नहीं हूं, कृपया मुझे बताएं कि क्या मैं कुछ गलत कर रहा हूं या अगर मैं कुछ गलत समझ रहा हूं।

(टीएलडीआर उत्तर: कथन शामिल करें: cin.sync_with_stdio(false) या इसके बजाए केवल fgets उपयोग करें।

टीएलडीआर परिणाम: मेरे प्रश्न के निचले भाग तक नीचे स्क्रॉल करें और तालिका को देखें।)

सी ++ कोड:

#include <iostream>
#include <time.h>

using namespace std;

int main() {
    string input_line;
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    while (cin) {
        getline(cin, input_line);
        if (!cin.eof())
            line_count++;
    };

    sec = (int) time(NULL) - start;
    cerr << "Read " << line_count << " lines in " << sec << " seconds.";
    if (sec > 0) {
        lps = line_count / sec;
        cerr << " LPS: " << lps << endl;
    } else
        cerr << endl;
    return 0;
}

// Compiled with:
// g++ -O3 -o readline_test_cpp foo.cpp

पायथन समतुल्य:

#!/usr/bin/env python
import time
import sys

count = 0
start = time.time()

for line in  sys.stdin:
    count += 1

delta_sec = int(time.time() - start_time)
if delta_sec >= 0:
    lines_per_sec = int(round(count/delta_sec))
    print("Read {0} lines in {1} seconds. LPS: {2}".format(count, delta_sec,
       lines_per_sec))

मेरे परिणाम यहां दिए गए हैं:

$ cat test_lines | ./readline_test_cpp
Read 5570000 lines in 9 seconds. LPS: 618889

$cat test_lines | ./readline_test.py
Read 5570000 lines in 1 seconds. LPS: 5570000

संपादित करें: मुझे ध्यान रखना चाहिए कि मैंने मैक ओएस एक्स v10.6.8 (हिम तेंदुए) और लिनक्स 2.6.32 (Red Hat Linux 6.2) के तहत दोनों को आजमाया। पूर्व मैकबुक प्रो है, और बाद वाला एक बहुत ही मधुर सर्वर है, यह नहीं कि यह बहुत प्रासंगिक है।

2 संपादित करें: (इस संपादन को हटाया गया है, जैसा कि अब लागू नहीं है)

$ for i in {1..5}; do echo "Test run $i at `date`"; echo -n "CPP:"; cat test_lines | ./readline_test_cpp ; echo -n "Python:"; cat test_lines | ./readline_test.py ; done
Test run 1 at Mon Feb 20 21:29:28 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 2 at Mon Feb 20 21:29:39 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 3 at Mon Feb 20 21:29:50 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 4 at Mon Feb 20 21:30:01 EST 2012
CPP:   Read 5570001 lines in 9 seconds. LPS: 618889
Python:Read 5570000 lines in 1 seconds. LPS: 5570000
Test run 5 at Mon Feb 20 21:30:11 EST 2012
CPP:   Read 5570001 lines in 10 seconds. LPS: 557000
Python:Read 5570000 lines in  1 seconds. LPS: 5570000

संपादित करें 3:

ठीक है, मैंने जेएन के सुझाव को पाइथन स्टोर लाइन पढ़ने की कोशिश करने की कोशिश की: लेकिन इससे पाइथन की गति में कोई फर्क नहीं पड़ता।

मैंने getline std::string में getline बजाय एक char सरणी में getline का उपयोग करने के जेएन के सुझाव की भी कोशिश की। बिंगो! इसके परिणामस्वरूप पायथन और सी ++ दोनों के बराबर प्रदर्शन हुआ। (3,333,333 एलपीएस मेरे इनपुट डेटा के साथ, जिस तरह से प्रत्येक तीन फ़ील्ड की छोटी लाइनें होती हैं, आमतौर पर लगभग 20 वर्ण चौड़ी होती हैं, हालांकि कभी-कभी अधिक)।

कोड:

char input_a[512];
char input_b[32];
char input_c[512];
while(scanf("%s %s %s\n", input_a, input_b, input_c) != EOF) {
    line_count++;
};

गति:

$ cat test_lines | ./readline_test_cpp2
Read 10000000 lines in 3 seconds. LPS: 3333333
$ cat test_lines | ./readline_test2.py
Read 10000000 lines in 3 seconds. LPS: 3333333

(हाँ, मैंने इसे कई बार चलाया।) तो, मुझे लगता है कि अब मैं getline बजाय getline का उपयोग getline । लेकिन, मैं अभी भी उत्सुक हूं अगर लोग सोचते हैं कि यह प्रदर्शन std::string / getline से हिट विशिष्ट और उचित है।

4 संपादित करें (था: अंतिम संपादन / समाधान):

जोड़ना:

cin.sync_with_stdio(false);

मेरे मूल से ऊपर जबकि लूप ऊपर कोड में परिणाम जो पाइथन से तेज़ी से चलता है।

नई प्रदर्शन तुलना (यह मेरे 2011 मैकबुक प्रो पर है), मूल कोड का उपयोग करके, सिंक अक्षम के साथ मूल, और क्रमशः मूल पायथन कोड, पाठ की 20 एम लाइनों वाली फ़ाइल पर। हां, मैंने डिस्क कैशिंग confound को खत्म करने के लिए कई बार भाग लिया।

$ /usr/bin/time cat test_lines_double | ./readline_test_cpp
       33.30 real         0.04 user         0.74 sys
Read 20000001 lines in 33 seconds. LPS: 606060
$ /usr/bin/time cat test_lines_double | ./readline_test_cpp1b
        3.79 real         0.01 user         0.50 sys
Read 20000000 lines in 4 seconds. LPS: 5000000
$ /usr/bin/time cat test_lines_double | ./readline_test.py
        6.88 real         0.01 user         0.38 sys
Read 20000000 lines in 6 seconds. LPS: 3333333

उनके जवाब के लिए @ वॉन कैटो के लिए धन्यवाद! कोई भी विस्तार करने वाले लोग या अच्छे संदर्भ दे सकते हैं, इस बात को इंगित कर सकते हैं कि यह सिंक्रनाइज़ेशन क्यों होता है, इसका क्या अर्थ है, जब यह उपयोगी होता है, और अक्षम होने के ठीक होने पर पोस्टरिटी द्वारा इसकी सराहना की जाएगी। :-)

5 / बेहतर समाधान संपादित करें:

जैसा कि गैंडफैफ़ द ग्रे द्वारा सुझाया गया है, scanf या असिंक्रनाइज़ किए गए cin दृष्टिकोण से भी तेज़ gets है। मैंने यह भी सीखा कि scanf और यूएनएसएएफई दोनों हैं और बफर ओवरफ्लो की संभावना के कारण उपयोग नहीं किया जाना चाहिए। इसलिए, मैंने fgets का fgets करके यह पुनरावृत्ति लिखा, पाने के लिए सुरक्षित विकल्प। मेरे साथी नोब्स के लिए प्रासंगिक रेखाएं यहां दी गई हैं:

char input_line[MAX_LINE];
char *result;

//<snip>

while((result = fgets(input_line, MAX_LINE, stdin )) != NULL)
    line_count++;
if (ferror(stdin))
    perror("Error reading stdin.");

अब, पाइथन कोड, असीमित cin , और fgets दृष्टिकोण की तुलना में, साथ ही साथ wc उपयोगिता की तुलना में, एक तेज़ सर्वर पर एक तेज सर्वर पर एक बड़ी फ़ाइल (100 एम लाइन; ~ 3.4 जीबी) का उपयोग कर परिणाम भी दिए गए हैं। । [ scanf संस्करण संस्करण विभाजन विफल हुआ और मुझे समस्या निवारण की तरह महसूस नहीं होता है।]:

$ /usr/bin/time cat temp_big_file | readline_test.py
0.03user 2.04system 0:28.06elapsed 7%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 28 seconds. LPS: 3571428

$ /usr/bin/time cat temp_big_file | readline_test_unsync_cin
0.03user 1.64system 0:08.10elapsed 20%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
Read 100000000 lines in 8 seconds. LPS: 12500000

$ /usr/bin/time cat temp_big_file | readline_test_fgets
0.00user 0.93system 0:07.01elapsed 13%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 7 seconds. LPS: 14285714

$ /usr/bin/time cat temp_big_file | wc -l
0.01user 1.34system 0:01.83elapsed 74%CPU (0avgtext+0avgdata 2464maxresident)k
0inputs+0outputs (0major+182minor)pagefaults 0swaps
100000000


Recap (lines per second):
python:         3,571,428
cin (no sync): 12,500,000
fgets:         14,285,714
wc:            54,644,808

जैसा कि आप देख सकते हैं, fgets बेहतर है, लेकिन अभी भी डब्ल्यूसी प्रदर्शन से बहुत दूर है; मुझे पूरा यकीन है कि यह इस तथ्य के कारण है कि डब्ल्यूसी प्रत्येक चरित्र की किसी भी स्मृति प्रतिलिपि के बिना जांच करता है। मुझे संदेह है कि, इस बिंदु पर, कोड के अन्य हिस्सों में बाधा बन जाएगी, इसलिए मुझे नहीं लगता कि उस स्तर को अनुकूलित करना भी सार्थक होगा, भले ही संभव हो (क्योंकि, आखिर में, मुझे वास्तव में पढ़ने वाली रेखाओं को स्टोर करने की आवश्यकता है याद में)।

यह भी ध्यान रखें कि char * बफर और fgets बनाम unsynchronised cin स्ट्रिंग के साथ एक छोटा सा व्यापार है कि बाद में किसी भी लंबाई की रेखाएं पढ़ सकते हैं, जबकि पूर्व को कुछ सीमित संख्या में इनपुट सीमित करने की आवश्यकता होती है। व्यावहारिक रूप से, यह शायद अधिकांश लाइन-आधारित इनपुट फ़ाइलों को पढ़ने के लिए एक गैर-मुद्दा है, क्योंकि बफर को बहुत बड़े मान पर सेट किया जा सकता है जो मान्य इनपुट से अधिक नहीं होगा।

यह शैक्षिक रहा है। आपकी टिप्पणियों और सुझावों के लिए सभी को धन्यवाद।

संपादित करें 6:

जैसा कि नीचे दी गई टिप्पणियों में जेएफ सेबेस्टियन द्वारा सुझाए गए अनुसार, जीएनयू डब्ल्यूसी उपयोगिता एक समय में भाग (16k बाइट्स) को पढ़ने और नई लाइनों की गिनती करने के लिए सादा सी read() (सुरक्षित-read.c wrapper के भीतर read() का उपयोग करती है। यहां जेएफ के कोड के आधार पर एक पायथन समकक्ष है (केवल प्रासंगिक स्निपेट दिखा रहा है जो पाइथन for लूप के for प्रतिस्थापित करता है:

BUFFER_SIZE = 16384
count = sum(chunk.count('\n') for chunk in iter(partial(sys.stdin.read, BUFFER_SIZE), ''))

इस संस्करण का प्रदर्शन काफी तेज़ है (हालांकि अभी भी कच्चे सी डब्ल्यूसी उपयोगिता की तुलना में थोड़ा धीमा है):

$ /usr/bin/time cat temp_big_file | readline_test3.py
0.01user 1.16system 0:04.74elapsed 24%CPU (0avgtext+0avgdata 2448maxresident)k
0inputs+0outputs (0major+181minor)pagefaults 0swaps
Read 100000000 lines in 4.7275 seconds. LPS: 21152829

दोबारा, यह मेरे लिए सी ++ fgets / cin और पहले पायथन कोड को एक तरफ wc -l और दूसरे पर इस अंतिम पायथन स्निपेट की तुलना करने के लिए थोड़ा मूर्खतापूर्ण है, क्योंकि बाद वाले दो वास्तव में पढ़ने वाली रेखाओं को संग्रहीत नहीं करते हैं, लेकिन केवल न्यूलाइन की गिनती करें। फिर भी, सभी अलग-अलग कार्यान्वयनों का पता लगाना और प्रदर्शन के प्रभावों के बारे में सोचना दिलचस्प है। एक बार फिर धन्यवाद!

संपादित करें 7: छोटे बेंचमार्क परिशिष्ट और पुनरावृत्ति

पूर्णता के लिए, मैंने सोचा कि मैं उसी फ़ाइल पर उसी फ़ाइल के लिए मूल (सिंक) C ++ कोड के साथ पढ़ने की गति अपडेट करूंगा। फिर, यह एक तेज डिस्क पर 100 एम लाइन फ़ाइल के लिए है। यहां पूरी तालिका है:

Implementation      Lines per second
python (default)           3,571,428
cin (default/naive)          819,672
cin (no sync)             12,500,000
fgets                     14,285,714
wc (not fair comparison)  54,644,808

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

std::ios_base::sync_with_stdio(false);

आम तौर पर, जब एक इनपुट स्ट्रीम buffered है, एक समय में एक चरित्र पढ़ने की बजाय, स्ट्रीम बड़े हिस्से में पढ़ा जाएगा। इससे सिस्टम कॉल की संख्या कम हो जाती है, जो आमतौर पर अपेक्षाकृत महंगी होती है। हालांकि, चूंकि FILE* आधारित stdio और iostreams में अलग-अलग कार्यान्वयन होते हैं और इसलिए अलग-अलग बफर होते हैं, इसलिए दोनों एक साथ उपयोग किए जाने पर समस्या उत्पन्न हो सकती है। उदाहरण के लिए:

int myvalue1;
cin >> myvalue1;
int myvalue2;
scanf("%d",&myvalue2);

यदि वास्तव में आवश्यकतानुसार cin द्वारा अधिक इनपुट पढ़ा गया था, तो दूसरा पूर्णांक मान scanf फ़ंक्शन के लिए उपलब्ध नहीं होगा, जिसका अपना स्वतंत्र बफर है। यह अप्रत्याशित परिणाम का कारण बन जाएगा।

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

सौभाग्य से, पुस्तकालय डिजाइनरों ने निर्णय लिया कि यदि आप जानते थे कि आप क्या कर रहे थे, तो बेहतर प्रदर्शन प्राप्त करने के लिए आपको इस सुविधा को अक्षम करने में भी सक्षम होना चाहिए, इसलिए उन्होंने sync_with_stdio विधि प्रदान की।


निम्न कोड यहां पोस्ट किए गए दूसरे कोड की तुलना में मेरे लिए निम्न कोड तेज था: (विजुअल स्टूडियो 2013, 64-बिट, 500 एमबी फ़ाइल लाइन लंबाई के साथ समान रूप से [0, 1000) में)।

const int buffer_size = 500 * 1024;  // Too large/small buffer is not good.
std::vector<char> buffer(buffer_size);
int size;
while ((size = fread(buffer.data(), sizeof(char), buffer_size, stdin)) > 0) {
    line_count += count_if(buffer.begin(), buffer.begin() + size, [](char ch) { return ch == '\n'; });
}

यह एक कारक 2 से अधिक द्वारा मेरे सभी पायथन प्रयासों को धड़कता है।


आपके दूसरे उदाहरण में (scanf () के साथ) कारण यह अभी भी धीमा क्यों हो सकता है क्योंकि scanf ("% s") स्ट्रिंग पार्स करता है और किसी भी स्पेस चार (स्पेस, टैब, न्यूलाइन) की तलाश करता है।

इसके अलावा, हाँ, सीपीथॉन हार्डडिस्क पढ़ने से बचने के लिए कुछ कैशिंग करता है।


उत्तर का पहला तत्व: <iostream> धीमा है। धीमा मुझे नीचे दिए गए scanf साथ एक बड़ा प्रदर्शन बढ़ावा मिलता है, लेकिन यह अभी भी पायथन से दो गुना धीमा है।

#include <iostream>
#include <time.h>
#include <cstdio>

using namespace std;

int main() {
    char buffer[10000];
    long line_count = 0;
    time_t start = time(NULL);
    int sec;
    int lps;

    int read = 1;
    while(read > 0) {
        read = scanf("%s", buffer);
        line_count++;
    };
    sec = (int) time(NULL) - start;
    line_count--;
    cerr << "Saw " << line_count << " lines in " << sec << " seconds." ;
    if (sec > 0) {
        lps = line_count / sec;
        cerr << "  Crunch speed: " << lps << endl;
    } 
    else
        cerr << endl;
    return 0;
}

जिज्ञासा से बाहर मैंने हुड के नीचे क्या होता है, इस पर एक नज़र dtruss/strace है, और मैंने प्रत्येक परीक्षण पर dtruss/strace उपयोग किया है।

सी ++

./a.out < in
Saw 6512403 lines in 8 seconds.  Crunch speed: 814050

syscalls sudo dtruss -c ./a.out < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            6
pread                                           8
mprotect                                       17
mmap                                           22
stat64                                         30
read_nocancel                               25958

अजगर

./a.py < in
Read 6512402 lines in 1 seconds. LPS: 6512402

syscalls sudo dtruss -c ./a.py < in

CALL                                        COUNT
__mac_syscall                                   1
<snip>
open                                            5
pread                                           8
mprotect                                       17
mmap                                           21
stat64                                         29

मैक पर g ++ का उपयोग करके मैंने अपने कंप्यूटर पर मूल परिणाम दोहराया।

निम्न लूप को C ++ संस्करण में जोड़ने से ठीक पहले लूप इसे Python संस्करण के साथ इनलाइन लाता है:

std::ios_base::sync_with_stdio(false);
char buffer[1048576];
std::cin.rdbuf()->pubsetbuf(buffer, sizeof(buffer));

sync_with_stdio बेहतर गति 2 सेकंड तक, और एक बड़ा बफर सेट करने से इसे 1 सेकंड तक लाया गया।


getline , स्ट्रीम ऑपरेटर, getline , सुविधाजनक हो सकता है यदि आपको फ़ाइल लोडिंग समय की परवाह नहीं है या आप छोटी टेक्स्ट फ़ाइलों को लोड कर रहे हैं। लेकिन, यदि प्रदर्शन ऐसा कुछ है जिसके बारे में आप परवाह करते हैं, तो आपको वास्तव में पूरी फ़ाइल को स्मृति में बफर करना चाहिए (माना जाता है कि यह फिट होगा)।

यहां एक उदाहरण दिया गया है:

//open file in binary mode
std::fstream file( filename, std::ios::in|::std::ios::binary );
if( !file ) return NULL;

//read the size...
file.seekg(0, std::ios::end);
size_t length = (size_t)file.tellg();
file.seekg(0, std::ios::beg);

//read into memory buffer, then close it.
char *filebuf = new char[length+1];
file.read(filebuf, length);
filebuf[length] = '\0'; //make it null-terminated
file.close();

यदि आप चाहते हैं, तो आप इस तरह के अधिक सुविधाजनक पहुंच के लिए उस बफर के चारों ओर एक स्ट्रीम लपेट सकते हैं:

std::istrstream header(&buffer[0], length);

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





getline