You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Last year I posted about static_any and I am back with a similar container that I implemented
recently: inplace_string.
For the record, static_any should have been be called inplace_any, but at this time I chose the prefix static as Boost.StaticVector was popular and it was clear to everybody what to expect from such a container.
inplace_string
The idea behind inplace_string is to get a full replacement of C++17’s std::string, with an in-place memory storage:
usingName=inplace_string<15>;// 16 bytes on stack, size includedNamename="foo";
autoit=name.find("r"); assert(it==Name::npos);
name+="bar";
std::stringstr(name);// implicit string_view construction
What you get as benefits:
Faster string operations: string construction and destruction are much faster since no memory allocation takes place. Other operations are also slightly faster: as its buffer
does not grow, inplace_string benefits from a simpler code with less branches.
Cache-friendly: it stays close to your other class members, your cache likes it.
Simplicity: it allows you to avoid dynamic allocations without implementing your own memory allocator.
As for the implementation, no surprises: the underlying container is std::array<CharT, N> and it uses the famous trick — made popular by fbstring — of storing the remaining size within the last byte of the string — if you don’t know it yet look here and think how the value will change when the string reaches the maximum capacity.
One of the only difference with std::string is the presence of a constructor from const CharT(&)[M]), allowing a compile-time error it the input exceeds the maximum capacity:
inplace_string<5>too_small;too_small="foobar";// error: static_assert failed "basic_inplace_string: size exceeds maximum capacity"
std::string’s SSO
At first glance, it might seem that inplace_string overlaps with std::string’s SSO (Short String Optimization), although it does not.
One of the reason is that SSO is implementation dependent:
15 bytes with gcc 5
22 bytes with clang 5
15 bytes with VS 2015 and 2017
Another reason is that even though everybody thinks that using gcc 5 is enough to get SSO, it is wrong: as of this writing, most of the popular Linux distributions (CentOS, Debian, RedHat) patched the glibc/gcc to keep the previous std::string implementation without SSO (and with COW!). This has been done to not break std::string’s ABI when C++11 has been release, cf this post on RedHat developers’ blog.
More importantly, inplace_stringguarantees that your code will never allocate. If, for any reason, your string grows more than you expected, it will throw… and you will know that you were wrong, as your application will likely crash! On the other hand, crossing fingers and relying on std::string’s SSO is somewhat a bet, and you will never know if one day, strings get bigger and start allocating.
string_view
In the current C++ world, std::string has a monopoly and the following code is common practice:
voidfoo(conststd::string&bar){...}
In a code base with std::string as unique string class, this code is reasonable — it is not when multiple string classes live together, as it defeats the
purpose of not using std::string if you need to do conversions all over the place.
foo("foobar");// compiles, but allocates
inplace_string<15>ss="foobar"; foo(ss);// does not compile, std::string does not know inplace_string
foo(ss.c_str());// compiles, but allocates and copies the string
To solve this issue, C++17 brings us std::string_view: std::string, inplace_string and friends implement an implicit cast operator to std::string_view, and mixing all of them work nicely:
voidfoo(std::string_viewbar){...}
foo("foobar");// compiles, does not allocate
inplace_string<15>ss="foobar"; foo(ss);// compiles, building string_view is cheap (taking pointer + size)
This object is very similar to QVariantMap, which is a QMap<QString, QVariant> — QVariant is actually not like std::variant, but similar to std::any. I use this pattern a lot, as it is convenient to use a string to index various objects. One issue with this was speed: having to allocate memory due to many std::string is a major obstacle.
Now, let’s benchmark these two guys — I know, it is unfair, but we all love looking at numbers! For that, let’s take a simple usage of our MetadataTree class:
In-place containers for fun and profit
https://ift.tt/lQT0G6i
David Gross
read
Last year I posted about static_any and I am back with a similar container that I implemented recently: inplace_string.
inplace_string
The idea behind
inplace_string
is to get a full replacement of C++17’sstd::string
, with an in-place memory storage:What you get as benefits:
inplace_string
benefits from a simpler code with less branches.As for the implementation, no surprises: the underlying container is
std::array<CharT, N>
and it uses the famous trick — made popular by fbstring — of storing the remaining size within the last byte of the string — if you don’t know it yet look here and think how the value will change when the string reaches the maximum capacity.One of the only difference with
std::string
is the presence of a constructor fromconst CharT(&)[M])
, allowing a compile-time error it the input exceeds the maximum capacity:std::string’s SSO
At first glance, it might seem that
inplace_string
overlaps withstd::string
’s SSO (Short String Optimization), although it does not.One of the reason is that SSO is implementation dependent:
Another reason is that even though everybody thinks that using gcc 5 is enough to get SSO, it is wrong: as of this writing, most of the popular Linux distributions (CentOS, Debian, RedHat) patched the glibc/gcc to keep the previous
std::string
implementation without SSO (and with COW!). This has been done to not breakstd::string
’s ABI when C++11 has been release, cf this post on RedHat developers’ blog.More importantly,
inplace_string
guarantees that your code will never allocate. If, for any reason, your string grows more than you expected, it will throw… and you will know that you were wrong, as your application will likely crash! On the other hand, crossing fingers and relying onstd::string
’s SSO is somewhat a bet, and you will never know if one day, strings get bigger and start allocating.string_view
In the current C++ world,
std::string
has a monopoly and the following code is common practice:In a code base with
std::string
as unique string class, this code is reasonable — it is not when multiple string classes live together, as it defeats the purpose of not usingstd::string
if you need to do conversions all over the place.To solve this issue, C++17 brings us
std::string_view
:std::string
,inplace_string
and friends implement an implicit cast operator tostd::string_view
, and mixing all of them work nicely:Mixing in-place containers
Let’s consider the following code:
This object is very similar to
QVariantMap
, which is aQMap<QString, QVariant>
—QVariant
is actually not likestd::variant
, but similar tostd::any
. I use this pattern a lot, as it is convenient to use a string to index various objects. One issue with this was speed: having to allocate memory due to manystd::string
is a major obstacle.Its equivalent with in-place containers could be:
Now, let’s benchmark these two guys — I know, it is unfair, but we all love looking at numbers! For that, let’s take a simple usage of our
MetadataTree
class:… and the result (time, instructions and cycles per iteration):
You can find the benchmark on my github. Here is the full output of
perf
:via Thoughts from a Wall Street developer
November 1, 2024 at 02:55PM
The text was updated successfully, but these errors were encountered: