繁体   English   中英

为什么 std::string 操作表现不佳?

[英]Why do std::string operations perform poorly?

我做了一个测试来比较几种语言的字符串操作,以便为服务器端应用程序选择一种语言。 结果似乎很正常,直到我终于尝试了 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(V8)上的javascript:3.1s
  • CPython 2.7.2 上的 Python:8.8s
  • 带有 libstdc++ 的 C++:5.9 秒
  • OpenJDK 7 上的 Java:50.4 秒

出奇! 我在 C++ 中尝试了“-O2,-O3”,但注意到有帮助。 C++ 在 V8 中似乎只有 javascript 的 50% 性能,甚至比 CPython 还差。 谁能向我解释一下我是否错过了 GCC 中的一些优化,或者只是这种情况? 非常感谢。

这并不是说std::string表现不佳(尽管我不喜欢C ++),而是字符串处理对其他语言进行了大量优化。

你对字符串性能的比较是误导性的,如果它们不仅仅是为了代表它们,那么它就是冒昧的。

我知道Python字符串对象完全用C实现 ,事实上在Python 2.7上,由于unicode字符串和字节之间缺乏分离,因此存在大量 优化 如果你在Python 3.x上运行这个测试,你会发现它相当慢。

Javascript有许多经过大量优化的实现。 可以预期,字符串处理非常好。

您的Java结果可能是由于不正确的字符串处理或其他一些不良情况造成的。 我希望Java专家可以介入并通过一些更改来修复此测试。

至于你的C ++示例,我希望性能稍微超过Python版本。 它执行相同的操作,减少了解释器开销。 这反映在您的结果中。 s.reserve(limit);之前进行测试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

所以我去了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的结果是time: 3.37s (当然,这是非常值得怀疑的,但请放纵我一会儿等待其他结果。)

现在我们采用此代码并交换注释行,以测试追加,而不是查找。 请注意,这一次,我在尝试查看任何时间结果时增加了十倍的迭代次数。

#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的结果是time: 0s

我的结论:在C ++中,这个基准是由搜索操作高度支配的 ,循环中字符的追加对结果没有任何影响。 这真的是你的意图吗?

惯用的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 sys 0m0.001s

这是最明显的一个:请尽量做s.reserve(limit); 在主循环之前。

文档在这里

我应该提一下,在C ++中直接使用标准类的方式与在Java或Python中使用它的方式相同,如果您不知道桌面背后的操作,通常会给您带来低于标准的性能。 语言本身并没有神奇的表现,它只是为您提供了正确的工具。

您在这里缺少的是查找搜索的固有复杂性。

您正在执行搜索102 * 1024 (104 448)次。 一个天真的搜索算法,每次都会尝试匹配从第一个字符开始,然后是第二个字符等的模式......

因此,您有一个从长度1N的字符串,并且在每一步中您都会针对此字符串搜索模式,这是C ++中的线性操作。 N * (N+1) / 2 = 5 454 744 576比较。 我并不像你那样惊讶,这需要一些时间......

让我们通过使用搜索单个Afind的重载来验证假设:

Original: 6.94938e+06 ms
Char    : 2.10709e+06 ms

大约快3倍,因此我们处于同一数量级。 因此,使用完整的字符串并不是很有趣。

结论? 也许这个find可以稍微优化一下。 但问题不值得。

注意:对于那些吹嘘Boyer Moore的人,我担心针太小了,所以它无济于事。 可能会减少一个数量级(26个字符),但不会更多。

我的第一个想法是没有问题。

C ++提供了第二好的性能,比Java快近十倍。 也许除了Java之外的所有功能都接近该功能可实现的最佳性能,您应该考虑如何修复Java问题(提示 - StringBuilder )。

在C ++的情况下,有一些事情要尝试提高性能。 特别是...

  • s += 'X'; 而不是s += "X";
  • 声明string searchpattern ("ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 在循环之外,并将其传递给find调用。 std::string实例知道它自己的长度,而C字符串需要线性时间检查来确定,并且这可能(或可能不)与std::string::find性能相关。
  • 尝试使用std::stringstream ,原因类似于为什么你应该使用StringBuilder for Java,尽管重复转换回string很可能会产生更多问题。

总的来说,结果并不令人惊讶。 具有良好JIT编译器的JavaScript可能能够比在这种情况下允许的C ++静态编译更好地优化。

有了足够的工作,你应该总是能够比JavaScript更好地优化C ++,但是总会有这样的情况,这不仅仅是自然发生的,也可能需要相当多的知识和努力才能实现。

C / C ++语言并不容易,需要多年制作快速程序。

使用从c版本修改的strncmp(3)版本:

#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 (!strncmp(s, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 26)) {
            fprintf(stderr, "zomg\n");
            return;
        }
    }
    printf("x's length = %zu\n", size);
}

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

对于C ++,尝试使用std::string作为“ABCDEFGHIJKLMNOPQRSTUVWXYZ” - 在我的实现中, string::find(const charT* s, size_type pos = 0) const计算字符串参数的长度。

我自己测试了C ++示例。 如果我删除了对std::sting::find的调用,程序立即终止。 因此,字符串连接期间的分配在这里没有问题。

如果我添加一个变量sdt::string abc = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"并在std::string::find的调用中替换“ABC ... XYZ”的出现,程序需要几乎与原始完成相同的时间例。 这再次表明,分配以及计算字符串的长度不会给运行时增加太多。

因此,libstdc ++使用的字符串搜索算法似乎不如javascript或python的搜索算法那么快。 也许你想用你自己的字符串搜索算法再次尝试C ++,这更适合你的目的。

似乎在nodejs中搜索子串的算法更好。 你可以实现自我并尝试它。

您的测试代码正在检查过多字符串连接的病态场景。 (测试的字符串搜索部分可能已被省略,我敢打赌,它对最终结果几乎没有贡献。)过多的字符串连接是大多数语言强烈反对的缺陷,并提供了众所周知的替代方案, (即StringBuilder,)所以你在这里测试的是这些语言在完全预期失败的情况下失败的程度。 那毫无意义。

类似无意义测试的一个例子是在紧密循环中抛出和捕获异常时比较各种语言的性能。 所有语言都警告异常抛出和捕获异常缓慢。 他们没有说明有多慢,他们只是警告你不要期待任何事情。 因此,要进行测试,那将是毫无意义的。

因此,重复您的测试更有意义的是将无意义的字符串连接部分(s + =“X”)替换为这些语言中的每一种提供的任何构造,以避免字符串连接。 (比如StringBuilder类。)

正如sbi所提到的,测试用例由搜索操作主导。 我很好奇文本分配在C ++和Javascript之间的比较速度。

系统:Raspberry Pi 2,g ++ 4.6.3,节点v0.12.0,g ++ -std = c ++ 0x -O2 perf.cpp

C ++:770ms

没有保留的C ++:1196ms

Javascript:2310ms

C ++

#include <iostream>
#include <string>
#include <chrono>
using namespace std;
using namespace std::chrono;

void test()
{
    high_resolution_clock::time_point t1 = high_resolution_clock::now();
    int x = 0;
    int limit = 1024 * 1024 * 100;
    string s("");
    s.reserve(1024 * 1024 * 101);
    for(int i=0; s.size()< limit; i++){
        s += "SUPER NICE TEST TEXT";
    }

    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( t2 - t1 ).count();
    cout << duration << endl;
}

int main()
{
    test();
}

JavaScript的

function test()
{
    var time = process.hrtime();
    var x = "";
    var limit = 1024 * 1024 * 100;
    for(var i=0; x.length < limit; i++){
        x += "SUPER NICE TEST TEXT";
    }

    var diff = process.hrtime(time);
    console.log('benchmark took %d ms', diff[0] * 1e3 + diff[1] / 1e6 );
}

test();

字符串在其他语言中的处理效率更高。 总而言之,C++ 是一种非常高效的语言。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM