限制 std::string 分配的数量

Limit number of std::string allocations

我有一个函数,它从带有两个数字的 const char* 构造一个 std::string,作为参数传递,附加到它的末尾。

std::string makeName(const char* name, uint16_t num1, uint16_t num2) {

    std::string new_name(name);
    new_name.reserve(new_name.length()+5);

    new_name += ":";
    new_name += boost::lexical_cast<std::string>(num1);
    new_name += ":";
    new_name += boost::lexical_cast<std::string>(num2);

    return new_name;
}

这个函数被调用了数千次,为堆上分配的小对象创建唯一的名称。

Object* object1= new Object(makeName("Object", i, j)); // i and j are simply loop indices

我发现使用 valgrind 的 massif 工具调用 makeName 会分配大量内存,因为它被调用了很多次。

87.96% (1,628,746,377B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.

->29.61% (548,226,178B) 0xAE383B7: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)

| ->26.39% (488,635,166B) 0xAE38F79: std::string::_Rep::_M_clone(std::allocator<char> const&, unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)

| | ->26.39% (488,633,246B) 0xAE39012: std::string::reserve(unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.19)

| | | ->15.51% (287,292,096B) 0x119A80FD: makeName(char const*, unsigned short, unsigned short) (Object.cpp:110)

| | | | ->15.51% (287,292,096B) in 42 places, all below massif's threshold (01.00%)

我的问题是,如何最大限度地减少这些分配以帮助减少我的程序使用的内存总量?

编辑: 我还想指出,作为程序要求,我不能使用 c++11 功能。

在这种情况下,只有 DIY 自定义转换才能使用 sprintf

所以我会使用 sprintfMEASURE

只有当这还不够好时,我才会实现我自己的整数到字符串(从许多案例中知道它肯定会更快一些,但不足以证明从那开始是合理的)。


例子。而不是当前的高级代码

std::string makeName(const char* name, uint16_t num1, uint16_t num2) {

    std::string new_name(name);
    new_name.reserve(new_name.length()+5);

    new_name += ":";
    new_name += boost::lexical_cast<std::string>(num1);
    new_name += ":";
    new_name += boost::lexical_cast<std::string>(num2);

    return new_name;
}

就这样

auto makeName( const char* const name, const uint16_t num1, const uint16_t num2 )
    -> std::string
{
    std::string result( strlen( name ) + 25, '[=11=]' );    // 25 is large enough.
    int const n = sprintf( &result[0], "%s:%d:%d", name, num1, num2 );
    result.resize( n );
    return result;
}

免责声明:代码未经编译器处理。

"My question is, how can I minimize these allocations"

我突然想到你有这些名字的原因。您能否在需要时计算它们,而不是总是在构造函数中生成名称?那将是最好的改进 - 除非需要,否则不要这样做。

如果您碰巧已经有一个虚拟基础,并且 class 类型决定了字符串,那就真的很容易了。否则,枚举类型可以替换字符串,并且您可以查找 table.

Object* object1= new Object(i, j));
std::string Object::getName(){ compute here }

如果这没有帮助,那么您实际上确实需要每个对象的字符串,并且您只能通过优化该函数来获得小幅加速。我注意到您以一种大小构建字符串,然后再将其增长。您也可以使用 char 缓冲区,然后将其分配给成员字符串(在构造函数中)。

是的,您的代码进行了大量分配。分析分配:

std::string new_name(name); // 1
new_name.reserve(new_name.length()+5); // 2

new_name += ":";
new_name += boost::lexical_cast<std::string>(num1); // possibly 4 (boost + operator+=)
new_name += ":"; // possibly 5
new_name += boost::lexical_cast<std::string>(num2); // possibly 7

'possibly'因为要看数字需要的字符(越高越多)。

如果您真的很关心内存分配,asprintf(虽然不是标准的)或您的版本(基于 s(n)printf 的 return 值)可能是最佳选择:

std::string makeName(const char* name, uint16_t num1, uint16_t num2)
{
     char *ptr = nullptr; 
     int size  = asprintf( &ptr, "%s:%u:%u", name, num1, num2);
  return std::string(ptr, size); // to avoid copying the chars
}  

注意:正如@Cheersandhth.-Alf 指出的那样,万一std::string 分配内存失败,ptr 将是 ptr 泄露。解决此问题的最佳方法是使用 std::unique_ptr,但我会让您根据自己的需要自行解决。

如果您不想使用 asprintf,您可以使用 std::snprintf

获得类似的行为
std::string makeName(const char* name, uint16_t num1, uint16_t num2)
{
    int length = std::snprintf(nullptr, 0, "%s:%u:%u", name, num1, num2);

    if (length > 0 )
    {
        std::string new_name(length + 1, '[=12=]');
        std::snprintf(&new_name[0], new_name.length(), "%s:%u:%u", name, num1, num2);

       return new_name;
    } 
    // else handle failure
} 

与你的版本(我没有使用 boost::lexical_caststd::to_string)的差异非常大:运行 500 次第一个版本使用 72,890 字节而第二个仅使用 23,890 ! (用 valgrind memcheck 测量)