C-Style字符串与库字符串性能

nee*_*eKo 2 c++

题

平均而言,C风格的字符串操作执行速度比库string类操作慢5倍,因为C++ Primer,4th Edition会让我相信吗?

为什么这么问?

因为当我实际进行性能测试时,事实证明,对于特定示例(本书中使用的一个),C风格的字符串快约50%.


建立

我正在阅读C++ Primer,第4版,(第138页)列出了这段代码:

//  C-style character string implementation
const char *pc = "a very long literal string";
const size_t  len = strlen(pc +1);    //  space to allocate

//  performance test on string allocation and copy
for (size_t ix = 0; ix != 1000000; ++ix) {
    char *pc2 = new char[len + 1];  //  allocate the space
    strcpy(pc2, pc);                //  do the copy
    if (strcmp(pc2, pc))            //  use the new string
        ;    //  do nothing
    delete [] pc2;                  //  free the memory
}

//  string implementation
string str("a very long literal string");

//  performance test on string allocation and copy
for(int ix = 0; ix != 1000000; ++ix) {
    string str2 = str;  //  do the copy, automatically allocated
    if (str != str2)    //  use the new string
        ;   //  do nothing
}    //  str2 is automatically freed
Run Code Online (Sandbox Code Playgroud)

现在请记住,我strlen(pc +1)在第2行知道这一点,并且第一个for使用size_t但不下标数组,所以它可能也是如此int,但这正是它在书中写下来的方式.

当我测试这段代码时(strlen(pc) + 1我假设是这样的),我的结果是第一个块比第二个块快了大约50%,这导致C样式字符串比这个特定的库字符串类快.例子.

但是,我打赌我错过了一些东西(可能很明显),因为书中(第139页)中有关上述代码的内容:

实际上,平均而言,字符串类实现的执行速度比C风格的字符串函数要快得多.我们超过五年的PC的相对平均执行时间如下:

 user    0.47  # string class 
 user    2.55  # C-style character string
Run Code Online (Sandbox Code Playgroud)

那么是哪一个呢?我应该使用更长的字符串文字吗?也许是因为他们使用了GNU C编译器并使用了微软的编译器?是因为我有一台更快的电脑吗?

或者这本书的错误呢?

编辑

Microsoft(R)32位C/C++优化编译器版本16.00.40219.01 for 80x86

Che*_*Alf 10

你的结论是C风格的字符串使用你的编译器和机器这个例子更快,几乎可以肯定是因为 - 你必须假设 - 你

  • 忘了打开优化,
  • 忘了让编译器的字符串长度"未知"(这很棘手),以防止它调整远离strlen调用,以及
  • 忘记并关闭安全范围检查(如果适用)会减慢速度std::string.

这是我测试过的代码:

#include <assert.h>
#include <iostream>
#include <time.h>
#include <string>
#include <string.h>
using namespace std;

extern void doNothing( char const* );

class StopWatch
{
private:
    clock_t     start_;
    clock_t     end_;
    bool        isRunning_;
public:
    void start()
    {
        assert( !isRunning_ );
        start_ = clock();
        end_ = 0;
        isRunning_ = true;
    }

    void stop()
    {
        if( isRunning_ )
        {
            end_ = clock();
            isRunning_ = false;
        }
    }

    double seconds() const
    {
        return double( end_ - start_ )/CLOCKS_PER_SEC;
    }

    StopWatch(): start_(), end_(), isRunning_() {}
};

inline void testCStr( int const argc, char const* const argv0 )
{
    //  C-style character string implementation
    //const char *pc = "a very long literal string";
    const char *pc = (argc == 10000? argv0 : "a very long literal string");
    //const size_t  len = strlen(pc +1);    //  space to allocate
    const size_t  len = strlen(pc)+1;    //  space to allocate

    //  performance test on string allocation and copy
    for (size_t ix = 0; ix != 1000000; ++ix) {
        char *pc2 = new char[len + 1];  //  allocate the space
        strcpy(pc2, pc);                //  do the copy
        if (strcmp(pc2, pc))            //  use the new string
            //;   //  do nothing
            doNothing( pc2 );
        delete [] pc2;                  //  free the memory
    }
}

inline void testCppStr( int const argc, char const* const argv0 )
{
    //  string implementation
    //string str("a very long literal string");
    string str( argc == 10000? argv0 : "a very long literal string" );

    //  performance test on string allocation and copy
    for(int ix = 0; ix != 1000000; ++ix) {
        string str2 = str;  //  do the copy, automatically allocated
        if (str != str2)    //  use the new string
            //;   //  do nothing
            doNothing( &str2[0] );
    }    //  str2 is automatically freed
}

int main( int argc, char* argv[] )
{
    StopWatch   timer;

    timer.start();  testCStr( argc, argv[0] );  timer.stop();
    cout << "C strings: " << timer.seconds() << " seconds." << endl;

    timer.start();  testCppStr( argc, argv[0] );  timer.stop();
    cout << "C++ strings: " << timer.seconds() << " seconds." << endl;
}
Run Code Online (Sandbox Code Playgroud)

典型结果:

[d:\dev\test]
> g++ foo.cpp doNothing.cpp -O2

[d:\dev\test]
> a
C strings: 0.417 seconds.
C++ strings: 0.084 seconds.

[d:\dev\test]
> a
C strings: 0.398 seconds.
C++ strings: 0.082 seconds.

[d:\dev\test]
> a
C strings: 0.4 seconds.
C++ strings: 0.083 seconds.

[d:\dev\test]
> _

上述,C++字符串通常不是字符串最快的实现.

通常,不可变字符串(引用计数)可以很好地超过C++字符串,而且,当我了解到这一点时,简单地复制字符串数据的字符串实现在使用适当的快速自定义分配器时更快.但是,不要问我如何实施后者.我只在另一个论坛上看到了代码和测试结果,在我与STL的讨论中指出了不可变字符串的一般优越性之后,有人慷慨地提供了这个,并且存在一些分歧.;-)


zxc*_*cdw 7

首先:这个问题没有明确的答案.

原因是性能取决于库实现,编译器和您使用的选项,您使用的操作系统以及您使用的CPU体系结构.

这本书有些陈旧(2005年,硬件和软件已经发展),它的代码已在旧编译器,旧实现和旧硬件上进行了测试.无论它对性能的评价是基于它的作者的观察结果,不同的人在尝试使用不同的编译器,库和硬件组合的代码之间肯定会有所不同.

你能做的最好的就是尝试自己.像这样的简单"基准"并不能说明C风格字符串与现实世界中的std::string s 之间的性能,常见情况,除非它们提供尽可能多的可能方法来测试和比较性能 - 这将是这是一个非常大的项目.

请注意,编译器优化可能会欺骗您使用本书中所示的代码.例如,由于空块if,if可以删除整个语句及其中的表达式(在本例中为例如对strcpy的调用)(*).使用本书中给出的代码块来执行有意义的,真实的适用基准测试可能非常困难.

另请注意,无论这些微基准测试的结果如何,仅适用于他们基准测试的操作 - 换句话说 - 仅仅因为字符串分配,复制和比较似乎比使用任何一种或C风格的字符串快x倍std::string,并不意味着另一个通常比另一个快x倍!

*:使用GCC 4.7.1测试C风格的字符串代码,-Ofast并且strcmp在编译的可执行文件中没有引用,这表明字符串比较在代码中被删除了 - 实际上它是 - 因为if-block是空的所以没有理由if在第一时间把整个地方放在那里!

添加我自己的观察结果:我将两段代码分解为不同的函数,然后对其中一个进行了100次重复调用(使用for-loop),然后使用timeunix-utility 测量运行时间.用GCC 4.7.1编译-Ofast.

对C-Style字符串函数的100次调用大约需要7.05秒(3次运行,7到7.1秒之间的变化),而对std :: string版本的100次调用平均需要大约1.4秒才能进行3次运行!实际上,这表明std :: string远胜于C风格的字符串.