Skip to content

Instantly share code, notes, and snippets.

@mrts
Created February 20, 2025 17:54
Show Gist options
  • Save mrts/b3b5512c7de1d62ceccaf2434b0c2b64 to your computer and use it in GitHub Desktop.
Save mrts/b3b5512c7de1d62ceccaf2434b0c2b64 to your computer and use it in GitHub Desktop.
Determine whether manually reserving capacity in a std::string reduces memory usage when that string is later moved into a std::ostringstream.
#include <iostream>
#include <iomanip>
#include <sstream>
#include <string>
#include <vector>
#include <random>
using byte = unsigned char;
using byte_vector = std::vector<byte>;
std::ostream& operator<<(std::ostream& os, const byte_vector& data)
{
os << std::setfill('0') << std::hex;
for (const auto byte : data)
os << std::setw(2) << short(byte);
return os << std::setfill(' ') << std::dec;
}
std::string operator+(std::string lhs, const byte_vector& rhs)
{
#ifdef USE_RESERVE
lhs.reserve(lhs.size() + rhs.size() * 2);
#endif
std::ostringstream hexStringBuilder(std::move(lhs), std::ios::ate);
hexStringBuilder << rhs;
return hexStringBuilder.str();
}
int main()
{
const size_t largeSize = 100000;
std::string lhs(largeSize, 'A');
byte_vector rhs(largeSize, 'B');
std::string result;
for (int i = 0; i < 100; ++i)
{
result = lhs + rhs;
}
// Prevent compiler from optimizing out 'result'.
std::cout << "Final string size: " << result.size() << std::endl;
return 0;
}
@mrts
Copy link
Author

mrts commented Feb 20, 2025

Compile as follows with and without lhs.reserve():

$ g++ -g -O2 test-string-reserve-impact-for-ostringstream.cpp -o test_no_reserve
$ g++ -g -O2 -DUSE_RESERVE test-string-reserve-impact-for-ostringstream.cpp -o test_with_reserve

Verify memory usage with Valgrind + Massif as follows:

$ valgrind --tool=massif --massif-out-file=massif_no_reserve.out ./test_no_reserve
$ valgrind --tool=massif --massif-out-file=massif_with_reserve.out ./test_with_reserve

Finally, view the textual report with ms_print:

$ ms_print massif_no_reserve.out | tail -10
--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
...
 89  3,322,572,396        1,372,840        1,372,709           131            0

$ ms_print massif_with_reserve.out | tail -10
--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
...
 96  3,332,588,425        1,572,840        1,572,709           131            0

This shows that the with-reserve version had a higher peak memory usage (1,572,840 B) compared to the no-reserve version (1,372,840 B). This indicates that the reserved capacity is not being reused by std::ostringstream and leads to extra allocation overhead. Therefore, removing lhs.reserve() appears best in terms of overall memory usage.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment