限制std :: string分配的数量

时间:2015-02-05 15:13:26

标签: c++ valgrind allocation stdstring

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

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功能。

3 个答案:

答案 0 :(得分:3)

在这种情况下,只使用sprintf进行DIY自定义转化。

所以我会使用sprintf MEASURE

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


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

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, '\0' );    // 25 is large enough.
    int const n = sprintf( &result[0], "%s:%d:%d", name, num1, num2 );
    result.resize( n );
    return result;
}

免责声明:编译器未触及的代码。

答案 1 :(得分:2)

“我的问题是,如何最大限度地减少这些分配”

我觉得你有这些名字的理由。你可以在需要时计算它们,而不是总是在构造函数中生成名称吗?这将是最好的改进 - 在需要之前不要这样做。

如果您碰巧已经拥有虚拟基础,并且类类型确定了字符串,那么这非常容易。否则,枚举类型可以替换字符串,并且您有一个查找表。

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

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

答案 2 :(得分:1)

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

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

'可能',因为它取决于数字所需的字符(越高越多)。

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

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 {{1泄露了。解决此问题的最佳方法是使用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, '\0'); std::snprintf(&new_name[0], new_name.length(), "%s:%u:%u", name, num1, num2); return new_name; } // else handle failure } boost::lexical_cast)的差异非常大:第一个版本运行500次使用72,890字节,而第二个版本只运行23,890! (用valgrind memcheck测量)