为什么std::string操作性能差?

64 投票
12 回答
22849 浏览
提问于 2025-04-17 07:18

我做了一个测试,比较了几种编程语言在字符串操作上的表现,目的是为了选择一个适合服务器端应用的语言。结果看起来还不错,直到我试了C++,这让我大吃一惊。所以我想知道我是不是漏掉了什么优化,来这里寻求帮助。

这个测试主要是对字符串进行大量操作,包括拼接和搜索。测试是在Ubuntu 11.10 amd64上进行的,使用的是GCC 4.6.1版本。我的机器是戴尔Optiplex 960,配有4G内存和四核CPU。

在Python (2.7.2)中的表现:

def test():
    x = ""
    limit = 102 * 1024
    while len(x) < limit:
        x += "X"
        if x.find("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) > 0:
            print("Oh my god, this is impossible!")
    print("x's length is : %d" % len(x))

test()

结果是:

x's length is : 104448

real    0m8.799s
user    0m8.769s
sys     0m0.008s

在Java (OpenJDK-7)中的表现:

public class test {
    public static void main(String[] args) {
        int x = 0;
        int limit = 102 * 1024;
        String s="";
        for (; s.length() < limit;) {
            s += "X";
            if (s.indexOf("ABCDEFGHIJKLMNOPQRSTUVWXYZ") > 0)
            System.out.printf("Find!\n");
        }
        System.out.printf("x's length = %d\n", s.length());
    }
}

结果是:

x's length = 104448

real    0m50.436s
user    0m50.431s
sys     0m0.488s

在Javascript (Nodejs 0.6.3)中的表现:

function test()
{
    var x = "";
    var limit = 102 * 1024;
    while (x.length < limit) {
        x += "X";
        if (x.indexOf("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) > 0)
            console.log("OK");
    }
    console.log("x's length = " + x.length);
}();

结果是:

x's length = 104448

real    0m3.115s
user    0m3.084s
sys     0m0.048s

在C++ (g++ -Ofast)中的表现:

Nodejs的表现比Python或Java好,这一点并不奇怪。但我本来以为libstdc++的表现会比Nodejs好很多,结果让我很意外。

#include <iostream>
#include <string>
using namespace std;
void test()
{
    int x = 0;
    int limit = 102 * 1024;
    string s("");
    for (; s.size() < limit;) {
        s += "X";
        if (s.find("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) != string::npos)
            cout << "Find!" << endl;
    }
    cout << "x's length = " << s.size() << endl;
}

int main()
{
    test();
}

结果是:

x length = 104448

real    0m5.905s
user    0m5.900s
sys     0m0.000s

简要总结

好了,现在我们来看看总结:

  • Nodejs上的Javascript (V8): 3.1秒
  • CPython 2.7.2上的Python : 8.8秒
  • 使用libstdc++的C++: 5.9秒
  • OpenJDK 7上的Java: 50.4秒

真是令人惊讶!我在C++中尝试了“-O2, -O3”选项,但没有任何帮助。C++的性能似乎只有V8中Javascript的50%左右,甚至还不如CPython。有人能告诉我,我是不是在GCC中漏掉了什么优化,还是说这就是结果?非常感谢。

12 个回答

16

标准的C++解决方案是:

#include <iostream>
#include <string>
#include <algorithm>

int main()
{
    const int limit = 102 * 1024;
    std::string s;
    s.reserve(limit);

    const std::string pattern("ABCDEFGHIJKLMNOPQRSTUVWXYZ");

    for (int i = 0; i < limit; ++i) {
        s += 'X';
        if (std::search(s.begin(), s.end(), pattern.begin(), pattern.end()) != s.end())
            std::cout << "Omg Wtf found!";
    }
    std::cout << "X's length = " << s.size();
    return 0;
}

我可以通过把字符串放在栈上,并使用memmem来大幅提高速度,但似乎没有这个必要。在我的机器上,这个速度已经比Python的解决方案快了10倍以上。

[在我的笔记本电脑上]

运行时间:./test
X的长度 = 104448
实际时间 0m2.055s
用户时间 0m2.049s
系统时间 0m0.001s

34

我在ideone.org上玩了一下这个代码。

这里有一个稍微修改过的版本,跟你原来的C++程序比,去掉了循环中的追加操作,这样就只测量了调用std::string::find()的时间。注意,我把循环的次数减少到了大约40%,否则ideone.org会终止这个进程。

#include <iostream>
#include <string>

int main()
{
    const std::string::size_type limit = 42 * 1024;
    unsigned int found = 0;

    //std::string s;
    std::string s(limit, 'X');
    for (std::string::size_type i = 0; i < limit; ++i) {
        //s += 'X';
        if (s.find("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) != std::string::npos)
            ++found;
    }

    if(found > 0)
        std::cout << "Found " << found << " times!\n";
    std::cout << "x's length = " << s.size() << '\n';

    return 0;
}

我在ideone.org上的结果是时间: 3.37秒。(当然,这个结果有点可疑,但请先听我说完,等会儿会有其他结果。)

接下来我们把代码中的注释行互换,来测试追加操作,而不是查找。这次,我把循环的次数增加了十倍,想看看能否得到任何时间结果。

#include <iostream>
#include <string>

int main()
{
    const std::string::size_type limit = 1020 * 1024;
    unsigned int found = 0;

    std::string s;
    //std::string s(limit, 'X');
    for (std::string::size_type i = 0; i < limit; ++i) {
        s += 'X';
        //if (s.find("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0) != std::string::npos)
        //    ++found;
    }

    if(found > 0)
        std::cout << "Found " << found << " times!\n";
    std::cout << "x's length = " << s.size() << '\n';

    return 0;
}

我在ideone.org上的结果,尽管循环次数增加了十倍,却是时间: 0秒

我的结论是:在C++中,这个基准测试主要受查找操作的影响,循环中的字符追加操作对结果没有任何影响。这真的是你想要的结果吗?

73

并不是说 std::string 表现得很差(虽然我不太喜欢C++),而是其他语言在字符串处理方面做了很多优化。

你对字符串性能的比较有些误导,如果你是想表达更多的意思,那就显得有些自以为是了。

我知道,Python的字符串对象完全是用C实现的,而且在Python 2.7中,由于没有区分unicode字符串和字节,所以有很多优化。如果你在Python 3.x上跑这个测试,你会发现速度明显慢很多。

Javascript有很多经过深度优化的实现,所以在字符串处理上表现得很好是可以预期的。

你在Java上的结果可能是因为字符串处理不当,或者其他一些不好的情况。我相信一个Java专家可以通过一些修改来解决这个测试的问题。

至于你的C++例子,我预计它的性能会稍微超过Python版本。因为它做的是相同的操作,但解释器的开销更小。这在你的结果中得到了体现。如果在测试前加上 s.reserve(limit);,可以消除重新分配内存的开销。

我再强调一下,你只是测试了这些语言实现的一个方面。这次测试的结果并不能反映整体语言的速度。

我提供了一个C语言的版本,来说明这种争论有多么无聊:

#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>

void test()
{
    int limit = 102 * 1024;
    char s[limit];
    size_t size = 0;
    while (size < limit) {
        s[size++] = 'X';
        if (memmem(s, size, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26)) {
            fprintf(stderr, "zomg\n");
            return;
        }
    }
    printf("x's length = %zu\n", size);
}

int main()
{
    test();
    return 0;
}

计时:

matt@stanley:~/Desktop$ time ./smash 
x's length = 104448

real    0m0.681s
user    0m0.680s
sys     0m0.000s

撰写回答