std :: string的性能优化

时间:2015-10-27 05:55:20

标签: c++ optimization

当我在我的应用程序中进行一些性能测试时,我发现以下代码存在差异(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

3 个答案:

答案 0 :(得分:2)

您的慢速版本可能会被重写为

while(heavyloop)
{
   std::string tempA = path + node;
   std::string tempB = tempA + "/";

   if(tempB == curNode)
   {
        do something
   }
}

是的,它不是一个完整的模拟,但会使临时对象更加明显。

查看两个临时对象:tempAtempB。创建它们是因为std::string::operator+始终生成新的std::string对象。这就是std::string的设计方式。编译器将无法优化此代码。

C ++中有一种名为expression templates的技术可以解决这个问题,但同样,它在库级别完成。

答案 1 :(得分:2)

对于类类型(如std::string),不要求运算符+和运算符+=之间的常规关系像您期望的那样得到尊重。当然不要求a = a + ba += 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 ++工具。