用于处理短字符串的std :: string性能

时间:2014-12-24 06:55:17

标签: c++

Bjarne Stroustrup和其他专家说,在Bjarne Stroustrup' articlemy previous question

中处理短字符串时,C ++比C更快

但在我的测试中,C++C慢了约110%。

g ++版本是4.4.6(在CentOS 6.3上运行)。这是因为g ++ 4.4.6具有较少的c ++ 11功能,例如Rvalue Reference (move semantics)

测试结果

$ time a.out input_file的输出减去无调用compose_X()函数的执行时间

  • cpp版本:0.192秒
  • C版:0.091秒

源代码

使用-O2

编译

修改

compose_cpp()compose_p()来自Bjarne的文章。他说compose_cpp()compose_p()更早。我想用真实的测试来检查这个事实。

如果我测试错误,我该如何改进测试?

#include <iostream>
#include <fstream>

#include <cstdlib>
#include <cstring>

std::string compose_cpp(const std::string& name, const std::string& domain)
{
    return name + '@' + domain;
}

char* compose_c(const char* name, const char* domain)
{
    char* res = (char*) malloc(strlen(name)+strlen(domain)+2);
    char* p = strcpy(res,name);

    p += strlen(name);
    *p = '@';
    strcpy(p+1,domain);

    return res;
}

int main(int argc, char* argv[])
{
    std::ifstream ifs;
    ifs.open(argv[1]);

    std::string email, domain;

    while (ifs.good())
    {
        ifs >> email;
        ifs >> domain;

        // std::string composed = compose_cpp(email, domain);

        char* composed = compose_c(email.c_str(), domain.c_str());
        free(composed);
    }

    ifs.close();
}

输入文件

输入文件长度为1毫米。每行少于20个字节,随机生成。

$ head -n 10 input.txt.1m
9742720 1981857.com
22504 4127435.com
342760 69167.com
53075 26710.com
3837481 1851920.com
98441 278536.com
4503887 9588108.com
193947 90885.com
42603 8166125.com
3587671 297296.com

2 个答案:

答案 0 :(得分:2)

我想在这里猜一下,因为我没有要测试的数据文件。我认为你的结果可能与Stroustrup的期望不符,因为他在这里说的话:

  

是的,C ++版本,因为它不必计算参数字符,也不使用免费存储(动态内存)作为短参数字符串。

但是,我的理解是libstdc++对所有字符串使用动态内存(零长度字符串除外)。有关std::stringlibstdc++对象的小尺寸问题,请参阅最近的回答:https://stackoverflow.com/a/27631366/12711

使用短字符串优化的实现(例如MSVC - 我不确定clang的libc ++是否使用它)可能会有更好的结果。

答案 1 :(得分:2)

我冒昧地将测试程序扩展了一点。特别是,我添加了代码,让它在内部生成数据,而不是依赖于外部文件,添加时序代码来隔离有问题的字符串处理,并让它在同一个运行中执行字符串操作的C ++和C版本,所以它立即产生了我可以比较的结果。这给了我以下代码:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>

#include <cstdlib>
#include <cstring>

#include <ctime>

char* compose_c(const char* name, const char* domain)
{
    char* res = (char*) malloc(strlen(name)+strlen(domain)+2);
    char* p = strcpy(res,name);

    p += strlen(name);
    *p = '@';
    strcpy(p+1,domain);

    return res;
}

std::string rand_string(int size){
    std::string ret;

    for (int i = 0; i < size; i++)
        ret.push_back(rand() % 10 + '0');
    return ret;
}

struct address {
    std::string email, domain;

    address() : email(rand_string(5)), domain(rand_string(4) + ".com") { }
};

struct composed {
    std::string addr;
    composed(address const &a) : addr(a.email + "@" + a.domain) {}
};

void report(clock_t d, std::string const &label){
    std::cout << double(d) / CLOCKS_PER_SEC << " seconds for " << label << "\n";
}

int main(int argc, char **argv) {
    static const int NUM = 1024 * 1024;

    std::vector<address> addresses(NUM);

    clock_t start = clock();
    {
        std::vector<composed> c{ addresses.begin(), addresses.end() };
    }
    report(clock() - start, "C++");

    std::vector<char *> c_results(addresses.size());

    clock_t start_c = clock();
    for (int i = 0; i < addresses.size(); i++)
        c_results[i] = compose_c(addresses[i].email.c_str(), addresses[i].domain.c_str());
    for (char *c : c_results)
        free(c);
    report(clock() - start_c, "C");
}

然后我用VC ++ 2013,x64使用标志-O2b2 -GL -EHsc编译了它。当我跑步时,我得到的结果是:

0.071 seconds for C++
0.12 seconds for C

虽然运行之间存在某些变体,但这些变量具有相当的代表性 - C代码几乎(但不完全)是C ++代码的两倍。

请注意,尽管事实上我实际上给了C版本一些不公平的优势。 C ++代码的时间不仅包括进行字符串操作的时间,还包括创建和销毁向量以保存结果的时间。对于C代码,我提前准备向量,然后代码来创建和销毁字符串本身。

为了确保这个结果不是侥幸,我也尝试改变一些自变量。例如,增加我们组成的地址字符串数量10倍会增加总时间,但对比率几乎没有影响:

0.714 seconds for C++
1.206 seconds for C

同样,更改顺序以便首先运行C代码,然后运行C ++代码没有明显的效果。

我想我应该添加:确实如此,这段代码实际上并没有像原作那样使用compose_cpp函数,而是选择将功能合并到composed的构造函数中。为了完整起见,我写了一个使用compose_cpp的版本,如下所示:

std::vector<std::string> composed;
composed.reserve(NUM);

clock_t start = clock();

for (auto const &a : addresses)
    composed.push_back(compose_cpp(a.email, a.domain));

这实际上稍微改善了时间,但是我猜它主要是 因为时间代码中移动创建向量本身的时间,并且不够大差异很在乎:

0.631 seconds for C++
1.21 seconds for C

这些结果在很大程度上依赖于标准库实现 - 特别是std::string实现短字符串优化的事实。在相同的硬件上运行相同的代码,但使用缺少此优化的实现(在我的情况下,gcc 4.9.1的nuwen MinGW分布)给出相当不同的结果:

2.689 seconds for C++
1.131 seconds for C

在这种情况下,C代码比VC ++的代码快一点,但是C ++代码的速度减慢了大约4倍。我尝试了一些不同的编译器标志(-O2与-O3等)但它们只有很小的影响 - 对于这个测试,缺乏短串优化显然支配其他因素。

结论:我认为这证实了C ++代码可以比C代码快得多,实现这一速度更多地取决于实现的质量。如果实现无法提供短字符串优化,则C ++代码可以轻松快2倍,而不是C版本的2倍。