我有一个函数,它从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功能。
答案 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指出的那样,如果 {{1泄露了。解决此问题的最佳方法是使用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, '\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测量)