I have a function that constructs a std::string
from a const char*
with two numbers, passed as parameters, appended to the end of it.
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;
}
This function gets called thousands of times to create unique names for small objects allocated on the heap.
Object* object1= new Object(makeName("Object", i, j)); // i and j are simply loop indices
I have discovered using valgrind's massif tool that the calls to makeName allocates a lot of memory since it gets called so many times.
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%)
My question is, how can I minimize these allocations to help reduce the overall total amount of memory my program uses?
EDIT: I also want to note that as a program requirement I cannot use c++11 features.
There is no official limit on the size of a string. The software will ask your system for memory and, as long as it gets it, it will be able to add characters to your string.
While an individual quoted string cannot be longer than 2048 bytes, a string literal of roughly 65535 bytes can be constructed by concatenating strings.
However, std::string requires space to be dynamically allocated in the buffer and more memory to be dynamically allocated each time an operation is performed on the string.
A std::string's allocation is not guaranteed to be contiguous under the C++98/03 standard, but C++11 forces it to be. In practice, neither I nor Herb Sutter know of an implementation that does not use contiguous storage.
Only a DIY custom conversion beats using sprintf
in such case.
So I would use sprintf
and MEASURE.
Only if that wasn't good enough would I implement my own integer-to-string (knowing from numerous cases that it will certainly be somewhat faster, but not enough to justify starting with that).
Example. Instead of the current high level code
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;
}
just do
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;
}
Disclaimer: code not touched by compiler's hands.
"My question is, how can I minimize these allocations"
It occurs to me that you have a reason for those names. Can you compute them at the time of need, rather than always generating the name in the constructor? That would be the best improvement - don't do it at all until needed.
If you happen to already have a virtual base, and the class type determines the string, that makes it really easy. Otherwise, an enumerated type could replace the string, and you have a lookup table.
Object* object1= new Object(i, j));
std::string Object::getName(){ compute here }
If this doesn't help, then you actually do need the string for each and every object, and you can only get a small speedup by optimizing that function. I noticed that you construct the string at one size then grow it afterwards. Also you could work with a char buffer then assign it to the member string (in the constructor).
Yes, your code is doing a lot of allocations. Analyzing the allocations:
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' because it depends on the characters needed by the numbers (the higher, the more).
If you're really concerned about memory allocations, asprintf
(not standard though) or your version (based on the return value of s(n)printf
) is likely to be the best choice:
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
}
Note: As @Cheersandhth.-Alf pointed out, in case std::string
failed to allocate memory, ptr
would beptr
is leaked. The best way to solve this would involve using std::unique_ptr
but I'll leave you to work it out to your needs.
if you don't want to use asprintf
, you can get a similar behavior with 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
}
The difference with your version (I didn't use boost::lexical_cast
but std::to_string
) is very big: ran 500 times the first version uses 72,890 bytes while the second only 23,890! (measured with valgrind memcheck)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With