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
I was studying how labels work in this crate, and I kind of got nerd-sniped by the story of the custom Cow type in this crate which is an improved fork of beef, which has a const new function for static strings, among some other things.
However, one thing that stands out to me as I read the details is, beef tried to marginally improve on the size of the std Cow type by bit munging etc. but no thought appears to have been given to the possibility of string interning.
String interning basically means that, if mem::size_of<MyCustomString>() is 24 let's say, you try to engineer it so that if the actual string being stored is less than 23 characters, then you don't hold a pointer to it at all, you just write the string in the memory allocated to MyCustomString on the stack. This was done extensively in standard C++ libraries, and similar projects like folly etc. (It isn't done at all in the rust standard library, in rust it only exists in third party crates.)
String interning is often an enormous win in real programs, because it greatly improves memory locality. Empirically, most strings in real programs are less than 24 characters, and if you don't actually have to go chase a pointer to find the characters, you don't get a cache miss when you read from or modify the string and so on. Copying an interned string passed by value to a function is like copying 3 i64's from one part of the stack to another, and again you don't have to chase any pointers or get any cache misses.
I would bet that (if the goal is to minimize overheads in the facade on the order of microseconds), this is an significant win in the context of metrics as well. Most metric names are not more than 24 characters, a lot of mine are like system.avg_cpu etc. Most metric labels are also not very long, because the point is that you want to be able to filter for them and create charts with relative ease, and often even if the metric label is short, the metric name provides context that helps engineers understand / remember what it means.
Implementing string interning is annoying and involves a bunch of unsafe code, but it is very well understood and there are now a number of rust libs out there that do it. I think SmolStr is certainly among the most well-known and possibly the best. https://docs.rs/smol_str/latest/smol_str/struct.SmolStr.html
A SmolStr is a string type that has the following properties:
size_of::() == 24 (therefor == size_of::() on 64 bit platforms)
Clone is O(1)
Strings are stack-allocated if they are:
Up to 23 bytes long
Longer than 23 bytes, but substrings of WS (see below). Such strings consist solely of consecutive newlines, followed by consecutive spaces
If a string does not satisfy the aforementioned conditions, it is heap-allocated
Additionally, a SmolStr can be explicitely created from a &'static str without allocation
So actually, afaiu SmolStr seems to have every advantage of the SharedString type used in this library, (smaller than COW, created from &'static str without allocation) but it additionally supports interning strings up to 23 bytes long.
Additionally, SmolStr has both const fn new_static and const fn new_inline. According to doc comments, the lack of that in beef was one of the reasons that beef was forked into this lib initially.
Respectfully, have the current developers considered if it would not be better to drop the custom Cow type and use SmolStr instead? I haven't done any benchmarks and I'm willing to be totally wrong, but it seems likely to me that the string interning features are strictly a perf improvement for the typical user of the metrics facade, for reasons mentioned. I'm not sure if the total perf saved is significant in many real programs, but you seem to have put some effort in already to minimize small overheads in the facade. You might also be able to shed a bunch of SLOC and not have to maintain a custom string type anymore if you did this. Curious what your thoughts are.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
I was studying how labels work in this crate, and I kind of got nerd-sniped by the story of the custom
Cow
type in this crate which is an improved fork ofbeef
, which has aconst
new function for static strings, among some other things.However, one thing that stands out to me as I read the details is,
beef
tried to marginally improve on the size of the stdCow
type by bit munging etc. but no thought appears to have been given to the possibility of string interning.String interning basically means that, if
mem::size_of<MyCustomString>()
is 24 let's say, you try to engineer it so that if the actual string being stored is less than 23 characters, then you don't hold a pointer to it at all, you just write the string in the memory allocated toMyCustomString
on the stack. This was done extensively in standard C++ libraries, and similar projects likefolly
etc. (It isn't done at all in the rust standard library, in rust it only exists in third party crates.)String interning is often an enormous win in real programs, because it greatly improves memory locality. Empirically, most strings in real programs are less than 24 characters, and if you don't actually have to go chase a pointer to find the characters, you don't get a cache miss when you read from or modify the string and so on. Copying an interned string passed by value to a function is like copying 3
i64
's from one part of the stack to another, and again you don't have to chase any pointers or get any cache misses.I would bet that (if the goal is to minimize overheads in the facade on the order of microseconds), this is an significant win in the context of metrics as well. Most metric names are not more than 24 characters, a lot of mine are like
system.avg_cpu
etc. Most metric labels are also not very long, because the point is that you want to be able to filter for them and create charts with relative ease, and often even if the metric label is short, the metric name provides context that helps engineers understand / remember what it means.Implementing string interning is annoying and involves a bunch of unsafe code, but it is very well understood and there are now a number of rust libs out there that do it. I think
SmolStr
is certainly among the most well-known and possibly the best. https://docs.rs/smol_str/latest/smol_str/struct.SmolStr.htmlSo actually, afaiu
SmolStr
seems to have every advantage of theSharedString
type used in this library, (smaller than COW, created from &'static str without allocation) but it additionally supports interning strings up to 23 bytes long.Additionally,
SmolStr
has bothconst fn new_static
andconst fn new_inline
. According to doc comments, the lack of that inbeef
was one of the reasons thatbeef
was forked into this lib initially.Respectfully, have the current developers considered if it would not be better to drop the custom
Cow
type and useSmolStr
instead? I haven't done any benchmarks and I'm willing to be totally wrong, but it seems likely to me that the string interning features are strictly a perf improvement for the typical user of themetrics
facade, for reasons mentioned. I'm not sure if the total perf saved is significant in many real programs, but you seem to have put some effort in already to minimize small overheads in the facade. You might also be able to shed a bunch of SLOC and not have to maintain a custom string type anymore if you did this. Curious what your thoughts are.Beta Was this translation helpful? Give feedback.
All reactions