限制 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
。
所以我会使用 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, '[=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_cast
但 std::to_string
)的差异非常大:运行 500 次第一个版本使用 72,890 字节而第二个仅使用 23,890 ! (用 valgrind memcheck 测量)
我有一个函数,它从带有两个数字的 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
。
所以我会使用 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, '[=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_cast
但 std::to_string
)的差异非常大:运行 500 次第一个版本使用 72,890 字节而第二个仅使用 23,890 ! (用 valgrind memcheck 测量)