পাইথনের চেয়ে সি ++ এ স্ট্রিংকে ধীরে ধীরে কেন ভাগ করা হচ্ছে?


94

আমি কিছুটা গতি অর্জন করতে এবং আমার মরিচা সি ++ দক্ষতা তীক্ষ্ণ করার প্রয়াসে পাইথন থেকে কিছু কোড সি ++ তে রূপান্তর করার চেষ্টা করছি। আমি গতকাল মর্মাহত যখন stdin থেকে লাইন পড়া একটি সরল বাস্তবায়ন অনেক দ্রুত সি চেয়ে পাইথন ছিল ++, (দেখুন হয়েছিল এই )। আজ, আমি অবশেষে শিখলাম কীভাবে সি ++ তে একটি স্ট্রিং বিভক্ত করতে হবে ডিলিমিটারগুলিতে (পাইথনের বিভক্তির অনুরূপ শব্দার্থক) (এবং), এবং এখন আমি দেজা ভুতে অনুভব করছি! আমার সি ++ কোডটি কাজটি করতে অনেক বেশি সময় নেয় (যদিও গতকালের পাঠের ক্ষেত্রে এটি আরও বিশালতার অর্ডার নয়)।

পাইথন কোড:

#!/usr/bin/env python
from __future__ import print_function                                            
import time
import sys

count = 0
start_time = time.time()
dummy = None

for line in sys.stdin:
    dummy = line.split()
    count += 1

delta_sec = int(time.time() - start_time)
print("Python: Saw {0} lines in {1} seconds. ".format(count, delta_sec), end='')
if delta_sec > 0:
    lps = int(count/delta_sec)
    print("  Crunch Speed: {0}".format(lps))
else:
    print('')

সি ++ কোড:

#include <iostream>                                                              
#include <string>
#include <sstream>
#include <time.h>
#include <vector>

using namespace std;

void split1(vector<string> &tokens, const string &str,
        const string &delimiters = " ") {
    // Skip delimiters at beginning
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);

    // Find first non-delimiter
    string::size_type pos = str.find_first_of(delimiters, lastPos);

    while (string::npos != pos || string::npos != lastPos) {
        // Found a token, add it to the vector
        tokens.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next non-delimiter
        pos = str.find_first_of(delimiters, lastPos);
    }
}

void split2(vector<string> &tokens, const string &str, char delim=' ') {
    stringstream ss(str); //convert string to stream
    string item;
    while(getline(ss, item, delim)) {
        tokens.push_back(item); //add token to vector
    }
}

int main() {
    string input_line;
    vector<string> spline;
    long count = 0;
    int sec, lps;
    time_t start = time(NULL);

    cin.sync_with_stdio(false); //disable synchronous IO

    while(cin) {
        getline(cin, input_line);
        spline.clear(); //empty the vector for the next line to parse

        //I'm trying one of the two implementations, per compilation, obviously:
//        split1(spline, input_line);  
        split2(spline, input_line);

        count++;
    };

    count--; //subtract for final over-read
    sec = (int) time(NULL) - start;
    cerr << "C++   : Saw " << count << " lines in " << sec << " seconds." ;
    if (sec > 0) {
        lps = count / sec;
        cerr << "  Crunch speed: " << lps << endl;
    } else
        cerr << endl;
    return 0;

//compiled with: g++ -Wall -O3 -o split1 split_1.cpp

মনে রাখবেন যে আমি দুটি পৃথক বিভাজন বাস্তবায়নের চেষ্টা করেছি। ওয়ান (split1) টোকেন জন্য অনুসন্ধান করতে স্ট্রিং পদ্ধতি ব্যবহার করে এবং একাধিক টোকেন সেইসাথে হ্যান্ডেল অসংখ্য টোকেন (এটা থেকে আসে একত্রীকরণ করতে সক্ষম হয় এখানে )। দ্বিতীয় (স্প্লিট 2) স্ট্রিমটি স্ট্রিম হিসাবে পড়তে গেটলাইন ব্যবহার করে, ডিলিমিটারগুলিকে একীভূত করে না এবং কেবল একটি একক ডিলিমিটার অক্ষরকে সমর্থন করে (এটি স্ট্রিং বিভক্ত প্রশ্নের উত্তরগুলির জন্য বেশ কয়েকটি স্ট্যাকওভারফ্লো ব্যবহারকারীরা পোস্ট করেছিলেন)।

আমি বিভিন্ন অর্ডার এ একাধিক বার চালানো। আমার পরীক্ষা মেশিনটি একটি ম্যাকবুক প্রো (২০১১, ৮ জিবি, কোয়াড কোর), এটি যে খুব বেশি গুরুত্বপূর্ণ তা নয়। আমি তিনটি স্পেস-বিভক্ত কলামগুলির সাথে 20 এম লাইন পাঠ্য ফাইলটি পরীক্ষা করছি যা প্রত্যেকে এর সাথে একই রকম দেখাচ্ছে: "foo.bar 127.0.0.1 home.foo.bar"

ফলাফল:

$ /usr/bin/time cat test_lines_double | ./split.py
       15.61 real         0.01 user         0.38 sys
Python: Saw 20000000 lines in 15 seconds.   Crunch Speed: 1333333
$ /usr/bin/time cat test_lines_double | ./split1
       23.50 real         0.01 user         0.46 sys
C++   : Saw 20000000 lines in 23 seconds.  Crunch speed: 869565
$ /usr/bin/time cat test_lines_double | ./split2
       44.69 real         0.02 user         0.62 sys
C++   : Saw 20000000 lines in 45 seconds.  Crunch speed: 444444

আমি কি ভুল করছি? সি ++ তে স্ট্রিং বিভক্ত করার আরও ভাল উপায় কি আছে যা বাহ্যিক গ্রন্থাগারগুলিতে নির্ভর করে না (যেমন কোনও উত্সাহ দেয় না), সীমানা বিহীন ক্রমগুলি (পাইথনের বিভাজনের মতো) সমর্থন করে, থ্রেড নিরাপদ (সুতরাং কোনও স্ট্রোক নেই), এবং যার অভিনয় কমপক্ষে অজগর সমান?

1 / আংশিক সমাধান সম্পাদনা করুন ?:

সি ++ এর মতো আমি অজগরটি ডামি তালিকাটি পুনরায় সেট করে এবং প্রতিবার এটিতে যুক্ত করে আরও সুষ্ঠু তুলনা করার চেষ্টা করেছি। এটি এখনও সি ++ কোডটি ঠিক কী করছে না তবে এটি কিছুটা কাছাকাছি। মূলত, লুপটি এখন:

for line in sys.stdin:
    dummy = []
    dummy += line.split()
    count += 1

পাইথনের পারফরম্যান্স এখন স্প্লিট 1 সি ++ বাস্তবায়নের সমান।

/usr/bin/time cat test_lines_double | ./split5.py
       22.61 real         0.01 user         0.40 sys
Python: Saw 20000000 lines in 22 seconds.   Crunch Speed: 909090

আমি এখনও অবাক হয়েছি যে, পাইথন স্ট্রিং প্রসেসিংয়ের জন্য এতটাই অনুকূলিত হয়ে উঠেছে (ম্যাট জয়েন্টার যেমন প্রস্তাব করেছে), এই সি ++ বাস্তবায়ন দ্রুততর হবে না। সি ++ ব্যবহার করে আরও অনুকূল উপায়ে কীভাবে এটি করা যায় সে সম্পর্কে কারও ধারনা থাকলে দয়া করে আপনার কোডটি ভাগ করুন। (আমি মনে করি যে আমার পরবর্তী পদক্ষেপটি খাঁটি সিতে এটি প্রয়োগের চেষ্টা করবে, যদিও আমি সিটিতে আমার সামগ্রিক প্রকল্পটি পুনরায় বাস্তবায়ন করতে প্রোগ্রামার উত্পাদনশীলতা বাণিজ্য করতে যাচ্ছি না, সুতরাং এটি কেবল স্ট্রিং বিভাজনের গতির জন্য একটি পরীক্ষা হবে))

সাহায্য করার জন্য সবাইকে ধন্যবাদ।

চূড়ান্ত সম্পাদনা / সমাধান:

আলফের গৃহীত উত্তর দেখুন দয়া করে। যেহেতু অজগরটি স্ট্রিংগুলির সাথে কঠোরভাবে রেফারেন্স দিয়ে কাজ করে এবং এসটিএল স্ট্রিংগুলি প্রায়শই অনুলিপি করা হয়, তাই ভ্যানিলা পাইথন বাস্তবায়নের সাথে পারফরম্যান্স আরও ভাল। তুলনার জন্য, আমি আল্ফের কোডের মাধ্যমে আমার তথ্য সংকলন করেছি এবং চালিয়েছি এবং এখানে অন্যান্য সমস্ত রানগুলির মতো একই মেশিনে পারফরম্যান্স রয়েছে, যা মূলত নিষ্পাপ অজগর বাস্তবায়নের সাথে সমান (যদিও পাইথন প্রয়োগের চেয়ে দ্রুততর যা তালিকা পুনরায় সেট / সংযোজন করে, যেমন উপরের সম্পাদনায় প্রদর্শিত):

$ /usr/bin/time cat test_lines_double | ./split6
       15.09 real         0.01 user         0.45 sys
C++   : Saw 20000000 lines in 15 seconds.  Crunch speed: 1333333

আমার কেবলমাত্র ছোট ছোট গ্রিপ এই ক্ষেত্রে সম্পাদন করতে সি ++ পেতে প্রয়োজনীয় পরিমাণের পরিমাণ সম্পর্কিত।

এই সমস্যাটি থেকে এবং গতকালের স্টিডিন লাইন পড়ার সমস্যাটি (উপরের লিঙ্কযুক্ত) এর মধ্যে একটি পাঠটি হ'ল ভাষার আপেক্ষিক "ডিফল্ট" পারফরম্যান্স সম্পর্কে নিষ্প্রভ অনুমানের পরিবর্তে সর্বদা একটি মানদণ্ড হওয়া উচিত। আমি শিক্ষার প্রশংসা করি।

আপনার পরামর্শের জন্য সবার আবার ধন্যবাদ!


4
আপনি সি ++ প্রোগ্রামটি কীভাবে সংকলন করলেন? আপনার কি অপ্টিমাইজেশন চালু আছে?
ইন্টারজয়

4
@ ইনটারজয়: এটি তাঁর উত্সের শেষ মন্তব্যে: g++ -Wall -O3 -o split1 split_1.cpp@ জেজেসি: আপনি যখন যথাযথ ব্যবহার করেন dummyএবং splineযথাক্রমে ব্যবহার করেন তখন আপনার বেঞ্চমার্ক কীভাবে ভাড়া পাবে , সম্ভবত পাইথন কলটি সরিয়ে ফেলবে line.split()কারণ এর কোনও পার্শ্ব প্রতিক্রিয়া নেই?
এরিক

4
আপনি বিভাজনগুলি সরিয়ে ফেলেন এবং স্টিডিন থেকে কেবল পঠন লাইন ছেড়ে দিলে আপনি কী ফলাফল পাবেন?
ইন্টারজয়

4
পাইথন সি-তে লেখা আছে এর অর্থ এটি করার একটি কার্যকর উপায় আছে, সি তে সম্ভবত এসটিএল ব্যবহারের চেয়ে কোনও স্ট্রিংকে বিভক্ত করার আরও ভাল উপায় আছে কি?
ixe013

উত্তর:


58

অনুমান হিসাবে, পাইথন স্ট্রিংগুলি প্রবর্তনযোগ্য স্ট্রিংগুলি গণনা করা হয়, যার ফলে পাইথন কোডে কোনও স্ট্রিং অনুলিপি করা হয় না, যখন সি ++ std::stringএকটি পরিবর্তনীয় মানের ধরণের হয় এবং এটি সবচেয়ে ছোট সুযোগে অনুলিপি করা হয়।

লক্ষ্য ফাস্ট বিভাজন হয়, তাহলে একটি ধ্রুবক সময় সাবস্ট্রিং অপারেশন, যার মানে শুধুমাত্র ব্যবহার করা হবে উল্লেখ করে মূল স্ট্রিং এর অংশ, পাইথন হিসেবে (এবং জাভা, এবং C # ...)।

সি ++ std::stringশ্রেণিতে একটি খালাস ফিচার রয়েছে, যদিও: এটি স্ট্যান্ডার্ড , যাতে দক্ষতা কোনও প্রধান বিবেচ্য নয় এমন চারপাশে নিরাপদে এবং বহনযোগ্যভাবে স্ট্রিংগুলি ব্যবহার করতে ব্যবহার করা যেতে পারে। তবে পর্যাপ্ত আড্ডা। কোড - এবং আমার মেশিনে এটি অবশ্যই পাইথনের চেয়ে দ্রুততর, যেহেতু পাইথনের স্ট্রিং হ্যান্ডলিং সিতে প্রয়োগ করা হয়েছে যা সি ++ (তিনি) এর উপসেট রয়েছে:

#include <iostream>                                                              
#include <string>
#include <sstream>
#include <time.h>
#include <vector>

using namespace std;

class StringRef
{
private:
    char const*     begin_;
    int             size_;

public:
    int size() const { return size_; }
    char const* begin() const { return begin_; }
    char const* end() const { return begin_ + size_; }

    StringRef( char const* const begin, int const size )
        : begin_( begin )
        , size_( size )
    {}
};

vector<StringRef> split3( string const& str, char delimiter = ' ' )
{
    vector<StringRef>   result;

    enum State { inSpace, inToken };

    State state = inSpace;
    char const*     pTokenBegin = 0;    // Init to satisfy compiler.
    for( auto it = str.begin(); it != str.end(); ++it )
    {
        State const newState = (*it == delimiter? inSpace : inToken);
        if( newState != state )
        {
            switch( newState )
            {
            case inSpace:
                result.push_back( StringRef( pTokenBegin, &*it - pTokenBegin ) );
                break;
            case inToken:
                pTokenBegin = &*it;
            }
        }
        state = newState;
    }
    if( state == inToken )
    {
        result.push_back( StringRef( pTokenBegin, &*str.end() - pTokenBegin ) );
    }
    return result;
}

int main() {
    string input_line;
    vector<string> spline;
    long count = 0;
    int sec, lps;
    time_t start = time(NULL);

    cin.sync_with_stdio(false); //disable synchronous IO

    while(cin) {
        getline(cin, input_line);
        //spline.clear(); //empty the vector for the next line to parse

        //I'm trying one of the two implementations, per compilation, obviously:
//        split1(spline, input_line);  
        //split2(spline, input_line);

        vector<StringRef> const v = split3( input_line );
        count++;
    };

    count--; //subtract for final over-read
    sec = (int) time(NULL) - start;
    cerr << "C++   : Saw " << count << " lines in " << sec << " seconds." ;
    if (sec > 0) {
        lps = count / sec;
        cerr << "  Crunch speed: " << lps << endl;
    } else
        cerr << endl;
    return 0;
}

//compiled with: g++ -Wall -O3 -o split1 split_1.cpp -std=c++0x

দাবি অস্বীকার: আমি আশা করি কোনও বাগ নেই। আমি কার্যকারিতা পরীক্ষা করিনি, তবে কেবল গতিটি পরীক্ষা করেছি। তবে আমি মনে করি, দু'একটি বাগ থাকলেও তা সংশোধন করা গতিটিকে উল্লেখযোগ্যভাবে প্রভাবিত করবে না।


4
হ্যাঁ, পাইথন স্ট্রিংগুলি রেফারেন্স গণনা করা অবজেক্টস, তাই পাইথন এর চেয়ে কম অনুলিপি করে। এগুলি এখনও হুডের নীচে নাল-টার্মিনেটেড সি স্ট্রিংগুলি ধারণ করে যদিও আপনার কোডের মতো (পয়েন্টার, আকার) জোড়া নয়।
ফ্রেড ফু

13
অন্য কথায় - উচ্চ স্তরের কাজের জন্য যেমন পাঠ্য হেরফের, উচ্চতর স্তরের ভাষার সাথে লেগে থাকুন, যেখানে এটি দক্ষতার সাথে করার চেষ্টা দশক বছর ধরে দশক বিকাশকারী দ্বারা সংহতভাবে তৈরি করা হয়েছে - বা কেবল এই সমস্ত বিকাশকারীদের মতোই কাজ করার জন্য প্রস্তুত করুন নিম্ন স্তরে তুলনামূলক কিছু থাকার জন্য।
jsbueno

4
@ জেজেসি: এর জন্য StringRef, আপনি স্ট্রিংগুলি std::stringখুব সহজেই অনুলিপি করতে পারবেন string( sr.begin(), sr.end() )
চিয়ার্স এবং এইচটিএইচ - আলফ

4
আমার ইচ্ছা সিপিথন স্ট্রিংগুলি কম কপি করা হত। হ্যাঁ, এগুলি রেফারেন্স গণনা করা এবং অপরিবর্তনীয় তবে স্ট্রিংস্প্লিট ()PyString_FromStringAndSize() কলগুলি ব্যবহার করে প্রতিটি আইটেমের জন্য নতুন স্ট্রিং বরাদ্দ করে PyObject_MALLOC()। সুতরাং একটি অংশীদারি উপস্থাপনের সাথে কোনও অপ্টিমাইজেশন নেই যা ব্যবহার করে যে স্ট্রিংগুলি পাইথনে অবিচ্ছেদ্য।
jfs

4
রক্ষণাবেক্ষণকারীকে: ঠিক করার চেষ্টা করেন বাগ পরিচয় করিয়ে করবেন না দয়া করে অনুভূত বাগ (বিশেষত রেফারেন্স না cplusplus.com )। টিআইএ
চিয়ার্স এবং এইচটিএইচ - আলফ

9

আমি কোনও আরও ভাল সমাধান (কমপক্ষে পারফরম্যান্স ভিত্তিক) সরবরাহ করছি না, তবে কিছু অতিরিক্ত তথ্য যা আকর্ষণীয় হতে পারে।

strtok_r(এর প্রবর্তক রূপ strtok) ব্যবহার করে :

void splitc1(vector<string> &tokens, const string &str,
        const string &delimiters = " ") {
    char *saveptr;
    char *cpy, *token;

    cpy = (char*)malloc(str.size() + 1);
    strcpy(cpy, str.c_str());

    for(token = strtok_r(cpy, delimiters.c_str(), &saveptr);
        token != NULL;
        token = strtok_r(NULL, delimiters.c_str(), &saveptr)) {
        tokens.push_back(string(token));
    }

    free(cpy);
}

অতিরিক্তভাবে পরামিতিগুলির fgetsজন্য এবং ইনপুটটির জন্য অক্ষরের স্ট্রিংগুলি ব্যবহার করে :

void splitc2(vector<string> &tokens, const char *str,
        const char *delimiters) {
    char *saveptr;
    char *cpy, *token;

    cpy = (char*)malloc(strlen(str) + 1);
    strcpy(cpy, str);

    for(token = strtok_r(cpy, delimiters, &saveptr);
        token != NULL;
        token = strtok_r(NULL, delimiters, &saveptr)) {
        tokens.push_back(string(token));
    }

    free(cpy);
}

এবং, কিছু ক্ষেত্রে, যেখানে ইনপুট স্ট্রিংটি ধ্বংস করা গ্রহণযোগ্য:

void splitc3(vector<string> &tokens, char *str,
        const char *delimiters) {
    char *saveptr;
    char *token;

    for(token = strtok_r(str, delimiters, &saveptr);
        token != NULL;
        token = strtok_r(NULL, delimiters, &saveptr)) {
        tokens.push_back(string(token));
    }
}

এর জন্য সময়গুলি নিম্নরূপ (প্রশ্ন এবং স্বীকৃত উত্তর থেকে অন্যান্য রূপগুলির জন্য আমার ফলাফল সহ):

split1.cpp:  C++   : Saw 20000000 lines in 31 seconds.  Crunch speed: 645161
split2.cpp:  C++   : Saw 20000000 lines in 45 seconds.  Crunch speed: 444444
split.py:    Python: Saw 20000000 lines in 33 seconds.  Crunch Speed: 606060
split5.py:   Python: Saw 20000000 lines in 35 seconds.  Crunch Speed: 571428
split6.cpp:  C++   : Saw 20000000 lines in 18 seconds.  Crunch speed: 1111111

splitc1.cpp: C++   : Saw 20000000 lines in 27 seconds.  Crunch speed: 740740
splitc2.cpp: C++   : Saw 20000000 lines in 22 seconds.  Crunch speed: 909090
splitc3.cpp: C++   : Saw 20000000 lines in 20 seconds.  Crunch speed: 1000000

আমরা দেখতে পাচ্ছি, গৃহীত উত্তর থেকে সমাধানটি এখনও দ্রুততম।

যে কেউ আরও পরীক্ষা করতে চান, তার জন্য আমি সমস্ত প্রোগ্রাম, প্রশ্ন, গৃহীত উত্তর, এই উত্তর, এবং একটি মেকফাইল এবং পরীক্ষার ডেটা উত্পন্ন করার জন্য একটি স্ক্রিপ্ট সহ একটি গিথুব রেপোও রেখেছি: https: // github। com / tobbez / স্ট্রিং-বিভক্তকরণ


4
আমি একটি টান অনুরোধ ( github.com/tobbez/string-splitting/pull/2 ) করেছি যা ডেটা (শব্দ এবং অক্ষরের সংখ্যা গণনা) "ব্যবহার করে" পরীক্ষাটিকে কিছুটা বাস্তবসম্মত করে তোলে। এই পরিবর্তনের সাথে সাথে সমস্ত সি / সি ++ সংস্করণ পাইথন সংস্করণগুলিকে পরাজিত করে (বুস্টের টোকেনাইজারের ভিত্তিতে যেটি আমি যুক্ত করেছি তার জন্য প্রত্যাশা করুন) এবং "স্ট্রিং ভিউ" ভিত্তিক পদ্ধতির আসল মান (স্প্লিট 6 এর মতো) চকচকে করে।
ডেভ জোহানসেন

আপনি ব্যবহার করা উচিত memcpy, না strcpy, যদি কম্পাইলার যে অপ্টিমাইজেশান লক্ষ্য করা ব্যর্থ। strcpyসাধারণত একটি ধীর স্টার্টআপ কৌশল ব্যবহার করে যা ছোট স্ট্রিংয়ের জন্য দ্রুততম ভারসাম্য রোধ করে long র‍্যাম্প পর্যন্ত দীর্ঘ স্ট্রিংয়ের জন্য সম্পূর্ণ সিমডি। memcpyএখনই আকারটি জানে এবং কোনও অন্তর্নিহিত দৈর্ঘ্যের স্ট্রিংয়ের সমাপ্তি পরীক্ষা করতে কোনও সিমড কৌশল ব্যবহার করতে হবে না। (আধুনিক x86 এ কোনও বড় বিষয় নয়)। কনস্ট্রাক্টরের std::stringসাথে অবজেক্ট তৈরি করা (char*, len)খুব দ্রুততরও হতে পারে, যদি আপনি এটির থেকে এটি বের করতে পারেন saveptr-token। স্পষ্টতই কেবল char*টোকেন সঞ্চয় করা এটি সবচেয়ে দ্রুত হবে : পি
পিটার কর্ডেস

4

আমি সন্দেহ করি যে এটি std::vectorএকটি পুশ_ব্যাক () ফাংশন কল প্রক্রিয়া চলাকালীন উপায়টিকে পুনরায় আকার দেয়। আপনি যদি বাক্যগুলির জন্য ব্যবহার করার জন্য std::listবা std::vector::reserve()পর্যাপ্ত জায়গা সংরক্ষণ করার চেষ্টা করেন তবে আপনার আরও ভাল পারফরম্যান্স পাওয়া উচিত। অথবা আপনি স্প্লিট 1 () এর জন্য নীচের মতো উভয়ের সংমিশ্রণটি ব্যবহার করতে পারেন:

void split1(vector<string> &tokens, const string &str,
        const string &delimiters = " ") {
    // Skip delimiters at beginning
    string::size_type lastPos = str.find_first_not_of(delimiters, 0);

    // Find first non-delimiter
    string::size_type pos = str.find_first_of(delimiters, lastPos);
    list<string> token_list;

    while (string::npos != pos || string::npos != lastPos) {
        // Found a token, add it to the list
        token_list.push_back(str.substr(lastPos, pos - lastPos));
        // Skip delimiters
        lastPos = str.find_first_not_of(delimiters, pos);
        // Find next non-delimiter
        pos = str.find_first_of(delimiters, lastPos);
    }
    tokens.assign(token_list.begin(), token_list.end());
}

সম্পাদনা : অন্যান্য সুস্পষ্ট জিনিস আমি দেখতে যে পাইথন পরিবর্তনশীল dummyপরার নির্ধারিত প্রতিটি সময় কিন্তু পরিবর্তন করা। সুতরাং এটি সি ++ এর বিপরীতে ন্যায্য তুলনা নয়। আপনার পাইথন কোডটি dummy = []আরম্ভ করার জন্য এটির পরে পরিবর্তন করার চেষ্টা করা উচিত dummy += line.split()। আপনি কি এই পরে রানটাইম রিপোর্ট করতে পারেন?

সম্পাদনা 2 : এটিকে আরও সুষ্ঠু করার জন্য আপনি সি ++ কোডের মধ্যে লুপটি সংশোধন করতে পারেন:

    while(cin) {
        getline(cin, input_line);
        std::vector<string> spline; // create a new vector

        //I'm trying one of the two implementations, per compilation, obviously:
//        split1(spline, input_line);  
        split2(spline, input_line);

        count++;
    };

ধারণার জন্য ধন্যবাদ। আমি এটি বাস্তবায়ন করেছি এবং দুর্ভাগ্যক্রমে এই বাস্তবায়নটি মূল স্প্লিট 1 এর চেয়ে ধীর। আমি লুপের আগে স্প্লাইন.রাইজার (16) চেষ্টা করেছিলাম কিন্তু এটি আমার স্প্লিট 1 এর গতিতে কোনও প্রভাব ফেলেনি। প্রতি লাইনে কেবল তিনটি টোকেন রয়েছে এবং ভেক্টর প্রতিটি লাইনের পরে সাফ হয়ে যায়, তাই আমি আশা করি না যে এটি বেশি সাহায্য করবে।
জেজেসি

আমি আপনার সম্পাদনার চেষ্টাও করেছি। দয়া করে আপডেট হওয়া প্রশ্নটি দেখুন। পারফরম্যান্স এখন বিভক্ত 1 এর সাথে সমান।
জেজেসি

আমি আপনার EDIT2 চেষ্টা করেছিলাম। পারফরম্যান্সটি কিছুটা খারাপ ছিল: us / usr / bin / সময় বিড়াল টেস্ট_লাইনস_ডুবল | ./split7 33.39 আসল 0.01 ব্যবহারকারী 0.49 সি সি ++: 33 সেকেন্ডে 20000000 লাইন দেখেছেন। কড়্কড়্ শব্দ গতি: 606060
Jjc

3

আমি মনে করি যে কয়েকটি সি ++ 17 এবং সি ++ 14 বৈশিষ্ট্য ব্যবহার করে নীচের কোডটি আরও ভাল:

// These codes are un-tested when I write this post, but I'll test it
// When I'm free, and I sincerely welcome others to test and modify this
// code.

// C++17
#include <istream>     // For std::istream.
#include <string_view> // new feature in C++17, sizeof(std::string_view) == 16 in libc++ on my x86-64 debian 9.4 computer.
#include <string>
#include <utility>     // C++14 feature std::move.

template <template <class...> class Container, class Allocator>
void split1(Container<std::string_view, Allocator> &tokens, 
            std::string_view str,
            std::string_view delimiter = " ") 
{
    /* 
     * The model of the input string:
     *
     * (optional) delimiter | content | delimiter | content | delimiter| 
     * ... | delimiter | content 
     *
     * Using std::string::find_first_not_of or 
     * std::string_view::find_first_not_of is a bad idea, because it 
     * actually does the following thing:
     * 
     *     Finds the first character not equal to any of the characters 
     *     in the given character sequence.
     * 
     * Which means it does not treeat your delimiters as a whole, but as
     * a group of characters.
     * 
     * This has 2 effects:
     *
     *  1. When your delimiters is not a single character, this function
     *  won't behave as you predicted.
     *
     *  2. When your delimiters is just a single character, the function
     *  may have an additional overhead due to the fact that it has to 
     *  check every character with a range of characters, although 
     * there's only one, but in order to assure the correctness, it still 
     * has an inner loop, which adds to the overhead.
     *
     * So, as a solution, I wrote the following code.
     *
     * The code below will skip the first delimiter prefix.
     * However, if there's nothing between 2 delimiter, this code'll 
     * still treat as if there's sth. there.
     *
     * Note: 
     * Here I use C++ std version of substring search algorithm, but u
     * can change it to Boyer-Moore, KMP(takes additional memory), 
     * Rabin-Karp and other algorithm to speed your code.
     * 
     */

    // Establish the loop invariant 1.
    typename std::string_view::size_type 
        next, 
        delimiter_size = delimiter.size(),  
        pos = str.find(delimiter) ? 0 : delimiter_size;

    // The loop invariant:
    //  1. At pos, it is the content that should be saved.
    //  2. The next pos of delimiter is stored in next, which could be 0
    //  or std::string_view::npos.

    do {
        // Find the next delimiter, maintain loop invariant 2.
        next = str.find(delimiter, pos);

        // Found a token, add it to the vector
        tokens.push_back(str.substr(pos, next));

        // Skip delimiters, maintain the loop invariant 1.
        //
        // @ next is the size of the just pushed token.
        // Because when next == std::string_view::npos, the loop will
        // terminate, so it doesn't matter even if the following 
        // expression have undefined behavior due to the overflow of 
        // argument.
        pos = next + delimiter_size;
    } while(next != std::string_view::npos);
}   

template <template <class...> class Container, class traits, class Allocator2, class Allocator>
void split2(Container<std::basic_string<char, traits, Allocator2>, Allocator> &tokens, 
            std::istream &stream,
            char delimiter = ' ')
{
    std::string<char, traits, Allocator2> item;

    // Unfortunately, std::getline can only accept a single-character 
    // delimiter.
    while(std::getline(stream, item, delimiter))
        // Move item into token. I haven't checked whether item can be 
        // reused after being moved.
        tokens.push_back(std::move(item));
}

ধারক পছন্দ:

  1. std::vector

    বরাদ্দকৃত অভ্যন্তরীণ অ্যারের প্রাথমিক আকারটি 1, এবং চূড়ান্ত আকার এন হ'ল, আপনি লগ 2 (এন) বারের জন্য বরাদ্দ এবং ডিএলোকট করবেন এবং আপনি (2 ^ (লগ 2 (এন) + 1) - 1) = অনুলিপি করবেন (2 এন - 1) বার। যেমন নির্দেশিত হয়েছে স্ট্যান্ড :: ভেক্টরের দুর্বল পারফরম্যান্স রিলোককে বহুবার লোগারিথমিক নম্বর না বলার কারণে? , যখন ভেক্টরের আকারটি অনির্দেশ্য হয় এবং এটি খুব বড় হতে পারে তখন এটির একটি খারাপ অভিনয় হতে পারে। তবে, যদি আপনি এর আকারটি অনুমান করতে পারেন তবে এটি কোনও সমস্যা হ'ল।

  2. std::list

    প্রতিটি পুশ_ব্যাকের জন্য, এটি ব্যয় করা সময়টি একটি ধ্রুবক, তবে পৃথক পুশ_ব্যাকের ক্ষেত্রে সম্ভবত স্ট্যান্ড :: ভেক্টরের চেয়ে বেশি সময় লাগবে। প্রতি-থ্রেড মেমরি পুল এবং একটি কাস্টম বরাদ্দকারী ব্যবহার করা এই সমস্যাটি সহজ করতে পারে।

  3. std::forward_list

    স্ট্যান্ড :: তালিকা হিসাবে একই, তবে উপাদান হিসাবে কম মেমরি দখল করে। এপিআই পুশ_ব্যাকের অভাবে কাজ করার জন্য একটি মোড়কের ক্লাসের প্রয়োজন।

  4. std::array

    আপনি যদি বৃদ্ধির সীমাটি জানতে পারেন তবে আপনি std :: অ্যারে ব্যবহার করতে পারেন। কারণ হিসাবে, আপনি এটি সরাসরি ব্যবহার করতে পারবেন না, কারণ এতে API টি পুশব্যাক নেই। তবে আপনি একটি মোড়কের সংজ্ঞা দিতে পারেন, এবং আমি মনে করি এটি এখানে সবচেয়ে দ্রুততম উপায় এবং যদি আপনার অনুমানটি যথাযথ হয় তবে কিছু স্মৃতি সংরক্ষণ করতে পারেন।

  5. std::deque

    এই বিকল্পটি আপনাকে পারফরম্যান্সের জন্য মেমরি বাণিজ্য করতে দেয়। কোনও উপাদান (2 ^ (এন + 1) - 1) বারের কপির অনুলিপি থাকবে, কেবলমাত্র N বার বরাদ্দ, এবং কোনও অবসন্নকরণ হবে না। এছাড়াও, আপনার কাছে অবিরাম অ্যাক্সেসের সময় এবং উভয় প্রান্তে নতুন উপাদান যুক্ত করার ক্ষমতা থাকবে।

মতে এসটিডি :: deque-cppreference

অন্যদিকে, ডেকসগুলির সাধারণত বড় ন্যূনতম মেমরির ব্যয় থাকে; কেবলমাত্র একটি উপাদান ধারণ করে এমন একটি যোগ্যকে তার পূর্ণ অভ্যন্তরীণ অ্যারে বরাদ্দ করতে হবে (উদাহরণস্বরূপ 64৪-বিট libstdc ++ এর উপরে বস্তুর আকারের 8 গুণ; অবজেক্টের আকারের 16 গুণ বা 4096 বাইট, যেটি বড়, 64-বিট লিবিসি ++ এ)

অথবা আপনি এগুলির কম্বো ব্যবহার করতে পারেন:

  1. std::vector< std::array<T, 2 ^ M> >

    এটি স্ট্যান্ড :: ডেকের অনুরূপ, পার্থক্য কেবল এই ধারকটি সামনের দিকে উপাদান যুক্ত করতে সমর্থন করে না। তবে এটি এখনও কার্য সম্পাদনে দ্রুত, এটি অন্তর্নিহিত স্টাড :: অ্যারে (2 ^ (এন + 1) - 1) বারের জন্য অনুলিপি করবে না বলে এটি কেবল (2 ^) এর জন্য পয়েন্টার অ্যারের অনুলিপি করবে (এন - এম + 1) - 1) বার, এবং যখন বর্তমান পূর্ণ থাকে এবং কোনও কিছুকে বিলোপ করার প্রয়োজন হয় না তখনই নতুন অ্যারে বরাদ্দ করা হয়। উপায় দ্বারা, আপনি অবিচ্ছিন্ন অ্যাক্সেস সময় পেতে পারেন।

  2. std::list< std::array<T, ...> >

    স্মৃতি ফ্রেমেন্টেশনের চাপকে দুর্দান্তভাবে আরাম করুন। বর্তমানটি পূর্ণ হলে এটি কেবল নতুন অ্যারে বরাদ্দ করবে এবং কোনও কিছু অনুলিপি করার প্রয়োজন নেই। কম্বো 1 এর তুলনায় আপনাকে অতিরিক্ত পয়েন্টারের জন্য এখনও মূল্য দিতে হবে।

  3. std::forward_list< std::array<T, ...> >

    2 হিসাবে একই, তবে কম্বো 1 এর সমান মেমরির দাম।


আপনি যদি 128 বা 256 এর মতো কিছু যুক্তিসঙ্গত প্রাথমিক আকারের সাথে স্টাডি :: ভেক্টর ব্যবহার করেন তবে মোট কপি (2 এর বৃদ্ধির ফ্যাক্টর ধরে নিচ্ছেন), আপনি সেই সীমা পর্যন্ত আকারের জন্য কোনও অনুলিপি এড়াতে পারবেন না। এরপরে আপনি প্রকৃত পরিমাণে উপাদানের সংখ্যার সাথে মানিয়ে নিতে বরাদ্দ সঙ্কুচিত করতে পারেন যাতে ছোট ইনপুটগুলির পক্ষে এটি ভয়ানক নয়। Nযদিও খুব বড় কেসের জন্য মোট অনুলিপিগুলিতে এটি খুব বেশি সহায়তা করে না । এটি অত্যন্ত খারাপ std :: ভেক্টর reallocবর্তমান বরাদ্দের শেষে আরও পৃষ্ঠাগুলি ম্যাপিংয়ের অনুমতি দিতে ব্যবহার করতে পারে না , সুতরাং এটি প্রায় 2x ধীর।
পিটার কর্ডেস

কি stringview::remove_prefixশুধু একটি স্বাভাবিক স্ট্রিং আপনার বর্তমান অবস্থান সম্পর্কে অবগত থাকার যেমন সস্তা হিসেবে? আপনাকে একটি অফসেট থেকে অনুসন্ধান শুরু করতে দেয় std::basic_string::findalচ্ছিক দ্বিতীয় আর্গ রয়েছে pos = 0
পিটার কর্ডেস

@ পিটার কর্ডস এটি সঠিক। আমি লিবিসিএক্সএক্সএইচপি
জিয়াআওও এক্সু

আমি libstdc ++ ইমপ্লিও যাচাই করেছিলাম ।
জিয়াআওও জু

আপনার ভেক্টরের অভিনয় বিশ্লেষণ বন্ধ Your আপনি যখন প্রথম সন্নিবেশ করান এবং প্রথম বার এটির সক্ষমতা প্রয়োজন হয় তখন দ্বিগুণ হয়ে যায় এমন ভেক্টরটির বিবেচনা করুন 1 যদি আপনার 17 টি আইটেম স্থাপন করতে হয়, প্রথম বরাদ্দ 1, তারপরে 2, তারপরে 4, তারপরে 8, তারপরে 16, পরে অবশেষে 32. এর অর্থ মোট 6 টি বরাদ্দ ছিল ( log2(size - 1) + 2পূর্ণসংখ্যার লগ ব্যবহার করে)। প্রথম বরাদ্দটি 0 টি স্ট্রিং সরানো হয়েছে, দ্বিতীয়টি 1 টি সরানো হয়েছে, তারপরে 2, তারপরে 4, তারপরে 8, পরে অবশেষে 16, মোট 31 টি পদক্ষেপের জন্য ( 2^(log2(size - 1) + 1) - 1))। এটি ও (এন), ও নয় (2 ^ n)। এটি ব্যাপকভাবে ছাড়িয়ে যাবে std::list
ডেভিড স্টোন

2

আপনি ভুল ধারণাটি তৈরি করছেন যে আপনার নির্বাচিত সি ++ বাস্তবায়ন পাইথনের চেয়ে অগত্যা দ্রুত। পাইথনে স্ট্রিং হ্যান্ডলিং অত্যন্ত অনুকূলিত। আরও জানতে এই প্রশ্নটি দেখুন: কেন স্টাড :: স্ট্রিং অপারেশনগুলি খারাপভাবে সম্পাদন করে?


4
আমি সামগ্রিক ভাষার পারফরম্যান্স সম্পর্কে কোনও দাবি করছি না, কেবল আমার নির্দিষ্ট কোড সম্পর্কে। সুতরাং, এখানে কোন অনুমান। অন্য প্রশ্নের ভাল পয়েন্টার জন্য ধন্যবাদ। আমি নিশ্চিত না যে আপনি যদি বলছেন যে সি ++ তে এই বিশেষ প্রয়োগটি সাবওটিমাল (আপনার প্রথম বাক্য) বা স্ট্রিং প্রসেসিংয়ে (আপনার দ্বিতীয় বাক্য) পাইথনের চেয়ে সি ++ স্লো আছে। এছাড়াও, আপনি যদি সি ++ তে যা করার চেষ্টা করছেন তা করার একটি দ্রুত উপায় সম্পর্কে আপনি যদি জানেন তবে দয়া করে সবার সুবিধার জন্য এটি ভাগ করুন। ধন্যবাদ কেবল স্পষ্ট করে বলতে গেলে, আমি পাইথনকে ভালবাসি, তবে আমি কোনও অন্ধ ফ্যানবয় নেই, এজন্যই আমি এটি করার দ্রুততম উপায় শিখতে চাইছি।
জেজেসি

4
@ জেজেসি: পাইথনের বাস্তবায়ন দ্রুততর হওয়াতে আমি বলতে পারি যে আপনার কাজটি সাবমোটিমাল। মনে রাখবেন যে ভাষা বাস্তবায়নগুলি আপনার জন্য কোণগুলি কেটে ফেলতে পারে তবে শেষ পর্যন্ত অ্যালগরিদমিক জটিলতা এবং হাত অপ্টিমাইজেশানগুলি জিততে পারে। এই ক্ষেত্রে, ডিফল্টরূপে ব্যবহারের ক্ষেত্রে পাইথনের উপরের হাত রয়েছে।
ম্যাট জেন্ডার

2

আপনি যদি স্প্লিট 1 বাস্তবায়ন গ্রহণ করেন এবং স্বাক্ষরটিকে আরও ঘনিষ্ঠভাবে স্প্লিট 2 এর সাথে মিলে যায় তবে এটি পরিবর্তন করে:

void split1(vector<string> &tokens, const string &str, const string &delimiters = " ")

এটি:

void split1(vector<string> &tokens, const string &str, const char delimiters = ' ')

আপনি split1 এবং split2 এর মধ্যে একটি আরও নাটকীয় পার্থক্য এবং একটি সুন্দর তুলনা পাবেন:

split1  C++   : Saw 10000000 lines in 41 seconds.  Crunch speed: 243902
split2  C++   : Saw 10000000 lines in 144 seconds.  Crunch speed: 69444
split1' C++   : Saw 10000000 lines in 33 seconds.  Crunch speed: 303030

1
void split5(vector<string> &tokens, const string &str, char delim=' ') {

    enum { do_token, do_delim } state = do_delim;
    int idx = 0, tok_start = 0;
    for (string::const_iterator it = str.begin() ; ; ++it, ++idx) {
        switch (state) {
            case do_token:
                if (it == str.end()) {
                    tokens.push_back (str.substr(tok_start, idx-tok_start));
                    return;
                }
                else if (*it == delim) {
                    state = do_delim;
                    tokens.push_back (str.substr(tok_start, idx-tok_start));
                }
                break;

            case do_delim:
                if (it == str.end()) {
                    return;
                }
                if (*it != delim) {
                    state = do_token;
                    tok_start = idx;
                }
                break;
        }
    }
}

ধন্যবাদ এনএম! দুর্ভাগ্যক্রমে, এটি আমার ডেটাসেট এবং মেশিনে মূল (বিভক্ত 1) বাস্তবায়ন হিসাবে প্রায় একই গতিতে চলবে বলে মনে হচ্ছে: us / usr / bin / সময় বিড়াল টেস্ট_লাইনস_ডুবল | .spsp8 21.89 আসল 0.01 ব্যবহারকারী 0.47 sys সি ++: 22 সেকেন্ডে 20000000 লাইন দেখেছেন। ক্রাঞ্চের গতি: 909090
জেজেসি

আমার মেশিনে: split1 - 54s, split.py - 35s, split5 - 16s। আমার কোন ধারণা নাই.
এন। 'সর্বনাম' মি।

হুম, আপনার ডেটাটি আমি উপরে উল্লিখিত বিন্যাসটির সাথে মেলে? আমি ধরে নিয়েছি আপনি প্রাথমিক ডিস্ক ক্যাশে জনসংখ্যার মতো ক্ষণস্থায়ী প্রভাবগুলি দূর করতে প্রতি কয়েকবার ছুটে এসেছেন?
জেজেসি

0

আমি সন্দেহ করি যে এটি পাইথনের sys.stdin এ বাফারিং সম্পর্কিত, তবে সি ++ বাস্তবায়নে কোনও বাফারিং নেই।

বাফার আকারটি কীভাবে পরিবর্তন করতে হয় তার বিশদ জন্য এই পোস্টটি দেখুন, তারপরে আবার তুলনা করে দেখুন: sys.stdin এর জন্য ছোট বাফার আকার নির্ধারণ করছেন?


4
হুম ... আমি অনুসরণ করি না পাইথনের তুলনায় সি ++ তে কেবল লাইনগুলি (বিভাজন ছাড়াই) পড়া দ্রুত (সিএনসিএনসিএন_ভিথ_স্টডিও (মিথ্যা); লাইন অন্তর্ভুক্ত করার পরে) আরও দ্রুত। উপরের রেফারেন্সে আমি গতকালই এটাই ছিলাম।
জেজেসি
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.