为什么在C++中分割字符串比Python慢?

101
我正在尝试将一些Python代码转换为C++,以获得一点速度并锤炼我的生疏的C++技能。昨天,我惊讶地发现,一个天真的从标准输入读取行的实现在Python中比在C++中快得多(见这里)。今天,我终于找到了如何在C++中使用合并分隔符(类似于Python的split())拆分字符串,并且现在经历了似曾相识的感觉!我的C++代码需要更长的时间来处理工作(虽然不像昨天的教训那样需要一个数量级的时间)。

Python 代码:

#!/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('')

C++ 代码:

#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)使用字符串方法搜索标记,能够合并多个标记并处理众多标记(它来自这里)。第二种(split2)使用getline将字符串读取为流,不合并分隔符,并且仅支持单个分隔符字符(这是由几个StackOverflow用户在字符串分割问题的答案中发布的)。

我以各种顺序多次运行了这个程序。我的测试机器是Macbook Pro(2011年,8GB,四核),这并不太重要。我使用一个20M行的文本文件进行测试,其中包含三个类似于此的空格分隔列:"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

我做错了什么?有没有更好的方法在C ++中进行字符串拆分,不依赖于外部库(即没有boost),支持合并分隔符序列(如python的split),线程安全(因此不能使用strtok),并且其性能至少与python相当?

编辑1 / 部分解决方案?:

我试图通过让Python重置虚拟列表并每次附加到它上面来使比较更公平,就像C ++一样。 这仍然不是C ++代码正在做的事情,但更接近了一点。 基本上,循环现在是:

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

现在Python的性能与split1版本的C++实现差不多。

/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

我仍然感到惊讶的是,即使Python在字符串处理方面进行了如此优化(正如Matt Joiner所建议的那样),这些C ++实现也不会更快。如果有人有关于如何使用C ++以更优的方式执行此操作的想法,请分享您的代码。(我认为下一步将尝试在纯C中实现它,但我不会为了重新实现整个项目而牺牲程序员的生产力,因此这只是一个字符串拆分速度的实验。)

感谢大家的帮助。

最终编辑/解决方案:

请参见Alf的被接受的答案。由于Python严格通过引用处理字符串,而STL字符串经常被复制,因此使用普通的Python实现性能更好。为了比较,在相同的机器上编译并运行我的数据通过Alf的代码,以下性能与朴素的Python实现基本相同(尽管比重置/附加列表的Python实现要快,如上所述):

$ /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

我唯一的小抱怨是,在这种情况下让C++执行所需的代码量太多了。

从这个问题和昨天的stdin行读取问题(以上链接)中得出的教训之一是,应该始终进行基准测试,而不是对语言的相对“默认”性能做出幼稚的假设。感谢您的教育。

再次感谢大家的建议!


2
你是如何编译C++程序的?你开启了优化吗? - interjay
2
@interjay:在他的源代码的最后一个评论中有:g++ -Wall -O3 -o split1 split_1.cpp。@JJC:当您实际使用dummyspline时,您的基准测试表现如何?也许Python会删除对line.split()的调用,因为它没有副作用? - Eric
2
如果您删除拆分并仅从stdin读取行,则会得到什么结果? - interjay
2
Python是用C语言编写的。这意味着有一种在C语言中高效完成它的方法。也许有比使用STL更好的方法来分割字符串? - ixe013
3
可能是 为什么std::string的操作效率较低? 的重复问题。 - Matt Joiner
显示剩余15条评论
8个回答

62

猜测Python字符串是引用计数的不可变字符串,因此在Python代码中没有字符串被复制,而C++的std::string是一种可变值类型,并且在最小机会时被复制。

如果目标是快速拆分,则应使用常数时间子字符串操作,这意味着只能像Python(和Java、C#等)一样对原始字符串的部分进行引用。

C++的std::string类有一个弥补缺陷的功能:它是标准的,因此可以在效率不是主要考虑因素的情况下安全地和可移植地传递字符串。但足够的聊天了。代码--在我的机器上当然比Python更快,因为Python的字符串处理是使用C实现的,而C是C++的子集(嘿嘿):

#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

声明:我希望没有任何bug。我没有测试功能,只是检查速度。但即使有一两个bug,我认为纠正这些问题也不会显著影响速度。


2
是的,Python字符串是引用计数对象,因此Python进行的复制要少得多。但是,在底层,它们仍然包含以空字符结尾的C字符串,而不是像您的代码那样的(指针,大小)对。 - Fred Foo
14
换句话说,如果要进行高级的文本处理等工作,请使用高级语言,在这些语言中,数十名开发人员在长达数十年的时间里累积了使其高效运行所需的努力 - 或者准备像所有这些开发人员一样付出同样的努力来获得相似的结果。 - jsbueno
2
@JJC:对于 StringRef,你可以很容易地将子字符串复制到 std::string 中,只需使用 string(sr.begin(), sr.end()) 即可。 - Cheers and hth. - Alf
3
我希望CPython能够减少复制字符串。是的,它们具有引用计数和不可变性,但是str.split()为每个项分配新字符串,使用PyString_FromStringAndSize()调用了PyObject_MALLOC()。因此,在Python中,没有利用共享表示来优化字符串不可变性的方法。 - jfs
3
维护者们请不要试图修复认为的错误而引入新的错误(尤其是参考http://www.cplusplus.com)。谢谢。 - Cheers and hth. - Alf
显示剩余8条评论

10

我没有提供更好的解决方案(至少在性能方面),但是有一些可能会有趣的额外数据。

使用strtok_rstrtok的可重入变体):

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

正如我们所看到的,被接受的答案仍然是最快的解决方案。

对于任何想要进行进一步测试的人,我还在Github上发布了一个存储库,其中包括所有问题中的程序、接受的答案、这个答案,以及一个Makefile和用于生成测试数据的脚本: https://github.com/tobbez/string-splitting


2
我提交了一个拉取请求(https://github.com/tobbez/string-splitting/pull/2),通过“使用”数据(计算单词和字符数)使测试更加真实。通过这个改变,所有的C/C++版本都击败了Python版本(除了我添加的基于Boost的tokenizer版本),并且“string view”方法(例如split6)的真正价值得到了展现。 - Dave Johansen
在编译器无法注意到优化时,应使用memcpy而不是strcpystrcpy通常使用较慢的启动策略,在短字符串和长字符串之间取得平衡。 memcpy立即知道大小,并且不必使用任何SIMD技巧来检查隐式长度字符串的结尾(在现代x86上并不重要)。如果可以从saveptr-token中获取,则使用(char*,len)构造函数创建std::string对象也可能更快。显然,最快的方法是只存储char*标记:P - Peter Cordes

4

我怀疑这是由于在调用push_back()函数时,std::vector被重新调整大小的方式导致的。如果您尝试使用std::liststd::vector::reserve()来预留足够的空间给句子,那么性能应该会更好。或者您可以像下面这样结合两者一起使用split1():

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());
}

编辑:我发现另一个明显的问题是,Python变量dummy每次都会被赋值,但没有修改。所以这不是对C++进行公平比较的方式。您应该尝试修改Python代码为dummy = []进行初始化,然后执行dummy += line.split()。您能在此之后报告运行时间吗?

编辑2:为了使比较更加公平,您可以修改C++代码中的while循环:

    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++;
    };

谢谢你的建议。我已经实现了它,但是这个实现比原来的split1要慢,很遗憾。我也尝试在循环之前使用spline.reserve(16),但这对我的split1速度没有影响。每行只有三个标记,并且向量在每行后都被清除,所以我并不指望它能够帮助太多。 - JJC
我也尝试了你的编辑。请查看更新后的问题。现在性能与split1相当。 - JJC
我尝试了你的EDIT2。性能有点变差:$/usr/bin/time cat test_lines_double | ./split7 33.39真实时间 0.01用户时间 0.49系统时间 C++:33秒内看到20000000行。压缩速度为606060。 - JJC

4

我认为下面这段代码更好,使用了一些C++17和C++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,最终大小为N,则您将进行log2(N)次分配和释放操作,并且将复制(2 ^ (log2(N) + 1) - 1) = (2N - 1)次。正如在Is the poor performance of std::vector due to not calling realloc a logarithmic number of times?中指出的那样,当向量的大小不可预测且可能非常大时,这可能会导致性能较差。但是,如果您可以估计它的大小,这就不是问题了。

  2. std::list.

    对于每个push_back,消耗的时间是一个常数,但单个push_back的时间可能比std::vector长。使用线程专用的内存池和自定义分配器可以缓解这个问题。

  3. std::forward_list.

    与std::list相同,但每个元素占用的内存较少。由于缺乏API push_back,因此需要一个包装类来工作。

  4. std::array.

    如果您可以知道增长的限制,则可以使用std::array。当然,您不能直接使用它,因为它没有API push_back。但是您可以定义一个包装器,在这里我认为这是最快的方式,并且如果您的估计相当准确,则可以节省一些内存。

  5. std::deque.

    此选项允许您以空间换时间。不会有(2 ^ (N + 1) - 1)次元素复制,仅有N次分配,没有释放。而且,您将拥有常数随机访问时间以及在两端添加新元素的能力。

根据std::deque-cppreference

另一方面,双端队列通常具有较大的最小内存成本;持有一个元素的双端队列必须分配其完整的内部数组(例如对于64位libstdc++,对象大小的8倍;对于64位libc++,对象大小的16倍或4096字节中的较大值)

或者您可以组合使用这些容器:

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

    这与std :: deque类似,不同之处在于该容器不支持在前面添加元素。但由于它不会为(2 ^(N + 1)-1)次复制基础std :: array,而只会为(2 ^(N-M + 1)-1)次复制指针数组,因此它的性能仍然更快,并且只有在当前数组已满且不需要释放任何内容时才分配新数组。顺便说一下,您可以获得恒定的随机访问时间。

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

    大大减轻了内存碎片压力。仅在当前数组已满时分配新数组,无需复制任何内容。与组合1相比,您仍需要支付额外的指针费用。

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

    与2相同,但占用与组合1相同的内存。


@ Peter Cordes,没错。我查了libcxx实现 - JiaHao Xu
我还检查了libstdc++实现,它是相同的。 - JiaHao Xu
你对向量性能的分析有误。考虑一个向量,当你第一次插入时它的初始容量为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)。这是O(n),而不是O(2^n)。这将大大优于std::list - David Stone
在原回答中,我假设最终元素的大小为(2^N),这就是为什么它如此之大。感谢您指出这一点,我应该使用log2。 - JiaHao Xu
仅使用平均 2N - 1 个副本进行 N 次 push_back 操作,这很可能比 std::list 更便宜。list 节点需要一个指针以及数据,因此额外的工作就在那里了。对于 intlong 元素,它所需的工作量与存储数据元素一样多。为 N 个新节点分配内存也是昂贵的:除非您有每个线程的内存池,否则 new 可能需要原子操作才能保证线程安全。然后,如果您想查看数据,std::vector 比遍历链表要便宜得多。 - Peter Cordes
显示剩余2条评论

2
如果您采用split1实现,并将其签名更改得更接近于split2,方法是更改以下内容:
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

2
你犯了一个错误的假设,认为你选择的C++实现一定比Python更快。Python中的字符串处理已经高度优化。更多信息请参见此问题:为什么std::string操作表现不佳?

4
我没有对整体语言性能做出任何声明,只是针对我的特定代码。因此,这里没有任何假设。感谢您提供了对其他问题的良好指引。我不确定您是在说C++中的这种特定实现不够优化(您的第一句话),还是说C++在字符串处理方面比Python慢(您的第二句话)。如果您知道在C++中实现我正在尝试做的事情的快速方法,请为了大家的利益分享一下。谢谢。为了澄清,我喜欢Python,但我并不是一个盲目的粉丝,这就是为什么我正在尝试学习最快的方法来做这件事的原因。 - JJC
1
@JJC:鉴于Python的实现更快,我认为你的方法不够优化。请记住,语言实现可以为您省去一些麻烦,但最终算法复杂度和手动优化才是胜出的关键。在这种情况下,Python默认具有优势。 - Matt Joiner

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;
        }
    }
}

谢谢n.m.!不幸的是,这似乎在我的数据集和机器上以与原始(split 1)实现大致相同的速度运行:$ /usr/bin/time cat test_lines_double | ./split8 21.89 实际 0.01 用户 0.47 系统 C++ :在22秒内看到20000000行。压缩速度:909090 - JJC
在我的电脑上:split1 - 54秒,split.py - 35秒,split5 - 16秒。我不知道。 - n. m.
嗯,你的数据是否符合我上面注明的格式?我假设你运行了多次以消除瞬态效应,例如初始磁盘缓存填充? - JJC

0

1
嗯...我不明白。在C++中,仅读取行(不使用split)比Python快(包括cin.sync_with_stdio(false)语句)。这就是昨天我遇到的问题,如上所述。 - JJC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接