Skip to content
Work Practice Products Coding interviews Machine learning coding Quant developer HFT course Blog Method Why me About Enquire Book a call

Quant developer HFT course · Module 3: Containers and memory

Small buffer optimisation

Lesson · 8 min

The question

Why does std::string("ESZ6") not allocate, but a 40-character string does? What is sizeof(std::string)?

The trick

Many objects are usually small. Most strings in a trading system are symbols, venue codes and short tags; most callbacks capture a pointer or two; most vectors of legs hold one or two legs. Allocating heap memory for each is wasteful when the contents would fit in the object itself.

Small buffer optimisation reserves space inside the object for small contents and uses the heap only when they do not fit. The object is a little larger; most instances never allocate.

std::string

All three major standard libraries implement it for strings, with different layouts:

std::stringptr8 bytessize8 bytesE S Z 6inline\0 …unused16-byte local buffer (libstdc++)points at the local buffer
A short string in libstdc++: 32 bytes, holding up to 15 characters in place. Only longer strings allocate. Other libraries lay it out differently but keep the same idea.
Librarysizeof(std::string) on 64-bitCharacters stored inline
libstdc++ (GCC)32 bytesup to 15
libc++ (Clang)24 bytesup to 22
MSVC32 bytesup to 15

So std::string("ESZ6") stores four characters and a terminator inside the object; a 40-character string allocates. The boundary is a property of the library, not of the language, which is a good thing to say when asked.

Some consequences:

  • Moving a short string copies its characters. There is no heap buffer to steal, so a move of an inline string is a small copy. It is still cheap.
  • Pointers into a short string move with it. s.data() points inside the object, so after the string is moved or copied elsewhere, a pointer taken earlier refers to the old object's buffer.
  • capacity() of an empty string is not zero. It reports the inline capacity, 15 or 22.

Other places it appears

  • std::function stores small callables inline, as covered in Module 1. The buffer is small, typically room for two pointers in libstdc++.
  • std::any does the same for small values.
  • Small vectors. Libraries such as Boost, Folly and LLVM provide small_vector<T, N>: a vector with room for N elements inside it that moves to the heap only beyond N. The standard does not have one; C++26 adds std::inplace_vector<T, N>, which has fixed capacity and never allocates at all.
boost::container::small_vector<Leg, 4> legs;   // up to 4 legs without touching the heap
std::inplace_vector<Leg, 4> fixed;              // C++26: at most 4, never allocates

Costs

  • Size. Every instance pays for the inline buffer, used or not. A vector of a million std::strings with libstdc++ is 32 MB before any characters are stored.
  • A branch. Operations check whether the contents are inline or on the heap. This is cheap and predictable for uniform data.
  • Larger moves. Moving an object with inline contents copies the buffer rather than a pointer.

Doing it yourself

For hot-path data with a known bound, the simplest version is a fixed buffer with no heap fallback at all: a symbol stored as char sym[8] and compared as one 64-bit integer, or a fixed-capacity array of legs. The fallback branch disappears, the size is fixed, and running past the bound becomes a validation error at the edge of the system.

struct Symbol {
    char c[8] = {};
    bool operator==(const Symbol& o) const { return std::memcmp(c, o.c, 8) == 0; }  // one 8-byte compare
};
static_assert(sizeof(Symbol) == 8);

Check yourself

Question 1 of 3

With libstdc++, which of these constructions allocates on the heap?

Question 2 of 3

What does moving a short string stored inline cost?

Question 3 of 3

What is the main cost of an inline buffer?

In the interview

Say your own answer out loud first, then open the one we would give.

What is small string optimisation?

The string object keeps a small buffer inside itself and stores short strings there, allocating only for longer ones. In libstdc++ the object is 32 bytes with room for 15 characters; libc++ is 24 bytes with room for 22. Most symbols and tags never touch the heap.

Where else is small buffer optimisation used?

std::function and std::any store small callables and values inline, and libraries provide small_vector with inline room for a few elements. C++26's inplace_vector goes further with a fixed capacity and no heap at all.

What are its downsides?

Every object carries the buffer, so it is bigger; operations branch on inline versus heap; and moving inline contents copies them, which also means pointers into the old object's buffer do not follow the data.