当我在我的应用程序中进行一些性能测试时,我发现以下代码存在差异(Visual Studio 2010)。
较慢的版本
while(heavyloop)
{
if(path+node+"/" == curNode)
{
do something
}
}
这将为生成的字符串生成一些额外的malloc。
为了避免使用这些malloc,我按以下方式更改了它:
std::string buffer;
buffer.reserve(500); // Big enough to hold all combinations without the need of malloc
while(heavyloop)
{
buffer = path;
buffer += node;
buffer += "/";
if(buffer == curNode)
{
do something
}
}
虽然与第一个版本相比,第二个版本看起来有点尴尬,但它仍然可读。我想知道的是,这种优化是对编译器的一部分的监督,还是总是必须手动完成。由于它只改变了分配的顺序,我希望编译器也可以在它自己的基础上找出它。另一方面,必须满足某些条件,才能真正使其成为优化,这可能不会被完全填满,但如果条件不满足,则代码至少表现得与第一个版本一样好。在这方面,Visual Studio的新版本是否更好?
更完整的版本,显示差异(SSCE):
std::string gen_random(std::string &oString, const int len)
{
static const char alphanum[] =
"0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz";
oString = "";
for (int i = 0; i < len; ++i)
{
oString += alphanum[rand() % (sizeof(alphanum) - 1)];
}
return oString;
}
int main(int argc, char *argv[])
{
clock_t start = clock();
std::string s = "/";
size_t adds = 0;
size_t subs = 0;
size_t max_len = 0;
s.reserve(100000);
for(size_t i = 0; i < 1000000; i++)
{
std::string t1;
std::string t2;
if(rand() % 2)
{
// Slow version
//s += gen_random(t1, (rand() % 15)+3) + "/" + gen_random(t2, (rand() % 15)+3);
// Fast version
s += gen_random(t1, (rand() % 15)+3);
s += "/";
s += gen_random(t2, (rand() % 15)+3);
adds++;
}
else
{
subs++;
size_t pos = s.find_last_of("/", s.length()-1);
if(pos != std::string::npos)
s.resize(pos);
if(s.length() == 0)
s = "/";
}
if(max_len < s.length())
max_len = s.length();
}
std::cout << "Elapsed: " << clock() - start << std::endl;
std::cout << "Added: " << adds << std::endl;
std::cout << "Subtracted: " << subs << std::endl;
std::cout << "Max: " << max_len << std::endl;
return 0;
}
在我的系统上,我在两者之间得到大约1秒的差异(这次使用gcc进行测试,但似乎与Visual Studio没有任何明显的区别):
Elapsed: 2669
Added: 500339
Subtracted: 499661
Max: 47197
Elapsed: 3417
Added: 500339
Subtracted: 499661
Max: 47367
答案 0 :(得分:2)
您的慢速版本可能会被重写为
while(heavyloop)
{
std::string tempA = path + node;
std::string tempB = tempA + "/";
if(tempB == curNode)
{
do something
}
}
是的,它不是一个完整的模拟,但会使临时对象更加明显。
查看两个临时对象:tempA
和tempB
。创建它们是因为std::string::operator+始终生成新的std::string
对象。这就是std::string
的设计方式。编译器将无法优化此代码。
C ++中有一种名为expression templates的技术可以解决这个问题,但同样,它在库级别完成。
答案 1 :(得分:2)
对于类类型(如std::string
),不要求运算符+
和运算符+=
之间的常规关系像您期望的那样得到尊重。当然不要求a = a + b
和a += b
具有相同的净效果,因为operator=()
,operator+()
和operator+=()
都可以单独实施,而不是一起工作。
因此,如果替换
,编译器在语义上将是不正确的if(path+node+"/" == curNode)
带
std::string buffer = path;
buffer += node;
buffer += "/";
if (buffer == curNode)
如果标准中存在某些约束,例如重载operator+()
和重载operator+=()
之间的固定关系,则代码的两个片段将具有相同的净效果。但是,没有这样的约束,因此不允许编译器进行此类替换。结果将改变代码的含义。
答案 2 :(得分:1)
path + node +“/”将分配一个临时变量字符串与curNode进行比较,它是c ++工具。