C样式的字符串操作平均执行速度比库string
类操作慢5倍,C ++ Primer,4th Edition会让我相信吗?
因为当我实际进行性能测试时,事实证明,对于特定示例(本书中使用的一个),C风格的字符串快了大约50%。
我正在阅读C++ Primer, 4th Edition,其中(第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
现在请记住我知道第2行strlen(pc +1)
,第一个for
使用size_t
但没有下标这个数组也可能是int
,但这正是它在书中的写法。
当我测试此代码(使用我认为是strlen(pc) + 1
时),我的结果是第一个块比第二个块执行快50%,这导致结论 C风格的字符串比这个特定的例子的库字符串类更快。
然而,我打赌我遗漏了一些东西(可能很明显),因为书中(第139页)中有关上述代码的内容:
实际上,平均而言,字符串类实现会执行 比C风格的字符串函数快得多。亲戚 我们超过五年的PC的平均执行时间是 如下:
user 0.47 # string class
user 2.55 # C-style character string
那是哪一个?我应该使用更长的字符串文字吗?也许是因为他们使用了GNU C编译器并使用了微软的编译器?是因为我有一台更快的电脑吗?
或者这本书错了吗?
Microsoft(R)32位C / C ++优化编译器版本16.00.40219.01 for 80x86
答案 0 :(得分: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;
}
典型结果:
[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的讨论中指出了不可变字符串的一般优越性之后,有人慷慨地提供了这个,并且存在一些分歧。 ; - )
答案 1 :(得分:7)
首先:这个问题没有明确的答案。
原因是性能取决于库实现,编译器和您使用的选项,您使用的操作系统以及您使用的CPU架构。
这本书有些陈旧(2005年,硬件和软件已经发展),它的代码已在旧编译器,旧实现和旧硬件上进行了测试。无论它对性能的评价是基于它的作者的观察结果,不同的人在尝试使用不同的编译器,库和硬件组合的代码之间肯定会有所不同。
你能做的最好的就是尝试自己。像这样的简单“基准”并不能说明C风格的字符串与真实世界中的std::string
之间的性能,常见情况,除非它们提供了尽可能多的方法的广泛报道。测试并比较性能 - 这本身就是一个很大的项目。
请注意,编译器优化可能会欺骗您使用本书中所示的代码。例如,由于空if
- 块,整个if
- 语句及其中的表达式(在本例中为例如对strcpy的调用)可以被删除(*)。使用本书中给出的代码块可以非常很难做出有意义的,真实的适用基准测试。
另请注意,无论这些微基准测试的结果如何,仅适用于他们基准测试的操作 - 换句话说 - 仅仅因为字符串分配,复制和比较似乎是 x 使用std::string
或C风格的字符串加快时间并不意味着另一个 x 的速度通常比另一个快一倍!
*:使用带有-Ofast
的GCC 4.7.1测试了C风格的字符串代码,并且在编译的可执行文件中没有引用strcmp
,这表明字符串比较在代码 - 确实 - 因为if
- 块是空的,所以没有理由在第一个地方有整个if
!
添加我自己的观察结果:我将两段代码分解为不同的函数,然后对其中一个进行了100次重复调用(带有for
- 循环),然后使用{{来测量运行时间1}} unix-utility。使用GCC 4.7.1和time
编译。
对C-Style字符串函数的100次调用大约需要7.05秒(3次运行,7到7.1秒之间的变化),而对std :: string版本的100次调用仅需 大约1.4秒平均超过3次!实际上,这表明std :: string远远胜过C风格的字符串。
答案 2 :(得分:4)
这不是一个公平的比较, std :: string 可能会使用 copy on write 等技术。根据你的时间结果,我猜 str2 根本不创建副本,而是引用 str ,这不仅节省了分配和复制,而且也可以比较 nop 。使用 strcpy()也不是最理想的,因为它需要检查终结符。为了更诚实的比较,我建议进行以下修订:
#include <stdlib.h>
inline void testCStr(const int argc, const char* argv)
{
const char* str = (argc == 10000) ? argv : "a very long literal string";
size_t len = strlen(str);
int i;
for ( i = 0; i < 1000000; i++ )
{
char* dup = (char*) malloc(len + 1);
memcpy(dup, str, len + 1);
dup[0] = str[0]; /* keep things even. */
if (strcmp(str, dup))
doNothing(dup);
free(dup);
}
}
inline void testCppStr(const int argc, const char* argv)
{
string str = (argc == 10000) ? argv : "a very long literal string";
for ( int i = 0; i < 1000000; i++ )
{
string dup = str;
dup[0] = str[0]; // force copy (defeats copy on write).
if (str != dup)
doNothing(dup.c_str());
}
}
C ++界面如此简化和简单,很容易忽视实际上在引擎盖下。实际情况是,如果两个代码序列都以与语言相同的方式编写,那么性能也应该大致相同。
testCStr()的版本功能上等同原始 testCppStr(),可能写成如下:
#include <stdlib.h>
#include <string.h>
/* utility. */
static int strcmpx(const char* x, size_t xsize, const char* y, size_t ysize)
{
int cmp = memcmp(x, y, xsize);
if (cmp != 0)
return cmp;
return -(xsize < ysize);
}
/* mystring. */
typedef struct mystring {
long* refcnt;
char* cstr;
size_t size;
} mystring;
/* mystring private. */
static inline int mystring_unique(const mystring* x)
{ return *x->refcnt == 1;
}
static inline void mystring_add_ref(const mystring* x)
{ ++*x->refcnt;
}
static void mystring_del_ref(mystring* x)
{
int unique = mystring_unique(x);
--*x->refcnt;
if (unique)
free(x->refcnt);
}
static void mystring_make_unique(mystring* x, const char* str, size_t size)
{
void* base = malloc(size + 1 + sizeof (long));
x->refcnt = (long*) base;
*x->refcnt = 1;
x->cstr = (char*) base + sizeof (long);
memcpy(x->cstr, str, size + 1);
x->size = size;
}
/* mystring public. */
void mystring_construct(mystring* x, const char* str)
{ mystring_make_unique(x, str, strlen(str));
}
void mystring_construct_copy(mystring* x, const mystring* src)
{
mystring_add_ref(src);
*x = *src;
}
void mystring_destroy(mystring* x)
{ mystring_del_ref(x);
}
int mystring_cmp(const mystring* x, const mystring* y)
{ return strcmpx(x->cstr, x->size, y->cstr, y->size);
}
const char* mystring_cstr(const mystring* x)
{ return x->cstr;
}
const char* mystring_at_const(const mystring* x, long i)
{ return x->cstr + i;
}
char* mystring_at(mystring* x, long i)
{
if (!mystring_unique(x))
{
mystring save = *x;
mystring_make_unique(x, x->cstr, x->size);
mystring_del_ref(&save);
}
return x->cstr + i;
}
/* test case. */
void testCStr(const int argc, const char* argv)
{
#define ITERATIONS 1000000
const char* temp = (argc == 10000) ? argv : "a very long literal string";
mystring str;
mystring_construct(&str, temp);
int i;
for ( i = 0; i < ITERATIONS; i++ )
{
mystring dup;
mystring_construct_copy(&dup, &str);
#ifdef FORCE_COPY
*mystring_at(&dup, 0) = *mystring_at_const(&str, 0);
#endif
if (mystring_cmp(&str, &dup))
doNothing(mystring_cstr(&dup));
mystring_destroy(&dup);
}
mystring_destroy(&str);
}
答案 3 :(得分:1)
同意@zxcdw说的话,我想补充一下:
库std::string
应该(显着)慢于C风格的字符串,这没有内在的原因。
std::string
实际上可以做更多的工作,因为它可以检查每个元素访问的边界(你应该能够通过编译时选项关闭),等等。它实际上可以少做工作,因为它知道字符串的长度(因此结束),并且当你追加某些东西时不必搜索它。 所以你永远不知道(没有测量)。
另一方面,了解更多意味着更大的内存占用(两个指针而不是一个),当你在短时间内处理大量不同的字符串对象时,这也会影响性能(由于更多的缓存未命中) 。但我不认为这就是你的情况。