6 ms·
Small Strings in Rust: smolstr vs. smartstring (2020)
- masklinn 2y ago[2020]. I wouldn't be surprised if the field had changed a fair bit since.
- brigadier132 2y agoOk, I dived into the rabbit hole a bit and found this: https://github.com/rosetta-rs/string-rosetta-rs?tab=readme-ov-file https://github.com/rosetta-rs/string-rosetta-rs?tab=readme-o... Click the charts for actually readable data. From a quick scan, it seems like hipstr is the current fastest string implementation and is actively maintained.
- IshKebab 2y agoAnd has the best name!
- skavi 2y agoThose benchmarks aren’t great. A real world-ish workload made generic over all these types would provide much more interesting results. Especially if heap usage was also tracked.
- dathinab 2y agothe author has some recommendations, to quote formatting): > Suggestions: > > Generally, String > If you deal mostly with string literals but want some flexibility (like clap), generally you'll want Cow<'static, str> > If a profiler says your strings are a problem: > Try different crates and settings for that crate out with a profiler > O(1) clones are important when doing a lot of clones. > For one-off allocations, they are slower. > For short-lived programs, look into string interning i.e. author thinks you should profile with your own use case EDIT: Not sure why HN removes indention between a leading > and the start of the text, makes quoting unnecessary hard
- samatman 2y agoHTML doesn't do indentation. I typed four spaces between every word in these sentences.
- dathinab 2y agobut HN doesn't provide me with a HTML input field but a text input field and HTML has stuff like <blockquote> absent from HN input format and properly converting user input is a bread and butter task of anything displaying user content so HTML collapsing whitespace is really meaningless for the argument IMHO
- masklinn 2y ago> Not sure why HN removes indention between a leading > and the start of the text, makes quoting unnecessary hard Because HN’s markup is the most dreary, half-assed, and useless one in the history of lightweight markups. HN will remove a bunch of characters it doesn’t like because they might be used for fun, parse two+ newlines as a paragraph break, 4-indent as a literal block, and mis-parse * as emphasis. That’s it. So you get all the drawbacks of HTML with almost none of the tools provided by even just the original markdown. No quoting, no line breaks, no lists, no inline code, …
- wizzwizz4 2y ago2-indent is a literal block. 4-indent also is, but only because it's a 2-indent. HN markup also supports \-escapes (so you can write \*hello\* to get *hello*), but they grow linearly, not exponentially, with repeated escapes (\\\* becomes \\*). It also truncates long URLs in display, and it handles a single pair of parentheses in them (so you can write https://example.com/print(%22Hello,%20world%21%22) https://example.com/print(%22Hello,%20world%21%22)). But I think that's all.
- epage 2y agoReal world benchmarks also wouldn't be great because they would be showing how well it works in someone else's program, rather than yours. This at least gives you an idea of what the relative cost of different operations are so you can consider what are frequent operations in your program and then benchmark a couple from there, rather than everything.
- epage 2y agoAlso, smol_str was removed from the comparison because matklad, the author of smol_str, suggests ecow > I’d rather say the opposite: users of those crates should switch to ecow. It is exactly what smol_str would have been, if it were a proper crate with a stable API, rather than an implementation detail of rust-analyzer. > > It’s a drop-in replacement for String, with O(1) clone and SSO, and I believe this is all you need. Other crates either have needlessly restricted API (no mutation), questionable implementation choices, or a bunch of ad hoc traits in the API. https://www.reddit.com/r/rust/comments/117ksvr/ecow_compact_cloneonwrite_vector_and_string/j9eh35d/ https://www.reddit.com/r/rust/comments/117ksvr/ecow_compact_...
- abhorrence 2y agoSadly it seems like some of the images have broken since it was originally posted. :(
- 01HNNWZ0MV43FF 2y agoThis capture looks a little better. Even the CSS looks off on the live site. Maybe someone should ping him? I think he's on Mastodon https://web.archive.org/web/20201002084042/https://fasterthanli.me/articles/small-strings-in-rust https://web.archive.org/web/20201002084042/https://fastertha...
- cute_boi 2y agoActually previous design was better to my eyes.
- pronoiac 2y agoI emailed him.
- fasterthanlime 2y agoThanks for the heads up! I fixed the SVG graphs and made a couple layout updates based on the feedback here and some earlier feedback from my subreddit, see the before-after here.[1] I've been mostly focused on function lately, the redesign is, let's say, a work in progress. (Oh also, I use they/them pronouns these days [2]) [1] https://imgur.com/a/before-after-2024-08-24-PAmeHFX https://imgur.com/a/before-after-2024-08-24-PAmeHFX [2] https://fasterthanli.me/articles/state-of-the-fasterthanlime-2024 https://fasterthanli.me/articles/state-of-the-fasterthanlime...
- crowdstriker 2y agoYou're a twat for correcting people about your silly pronouns.
- unshavedyak 2y agoOn the note of small strings, Compact String[1] was i believed released after this article and has a nifty trick. Where Smol and Smart can fit 22 and 23 bytes, CompactStr can fit 24! Which is kinda nutty imo, that's the full size of the normal String on the stack.. but packed with actual string data. There's a nice explanation on their readme[2]. Love tricks like this. [1]: https://github.com/ParkMyCar/compact_str https://github.com/ParkMyCar/compact_str [2]: https://github.com/ParkMyCar/compact_str?tab=readme-ov-file#how-it-works https://github.com/ParkMyCar/compact_str?tab=readme-ov-file#...
- dataflow 2y agoNote for C++ developers: Their trick is only possible because the strings are UTF-8 and not null-terminated. It wouldn't work as a drop-in for standard strings in C++.
- kloop 2y agostd::string isn't null terminated (or at least it isn't guaranteed to be, I don't think it's forbidden for an implementation to do that). That's why the c_str method exists, so you can get a pointer to a null terminated character array
- searealist 2y agoI think std::string is actually required to be null terminated now in the latest standards. But even before it was basically required as that was the only way to make c_str() constant time.
- dataflow 2y ago> But even before it was basically required as that was the only way to make c_str() constant time. Note that c_str() could've inserted the terminator in constant time, as long as the string kept space reserved for that. So this wouldn't have violated constant-time-ness, and it's not an insane thing to do considering it would save some instructions elsewhere. But yeah, the flexibility wasn't all that useful even in the beginning, and became even more useless as soon as C++ incorporated threading, due to the constness of the function.
- mastax 2y agoIt’s nice that rustc’s niche optimization lets smolstr be implemented with a simple enum, rather than having to do some unsafe union bit packing[0]. The only concession that had to be made to the compiler is using an enum for the InlineSize value to show that the last 3 bits of that aren’t used. [0]: https://github.com/rust-analyzer/smol_str/blob/fde86a5c0cb8f397c8a59e3bb156a39fbeb754d5/src/lib.rs#L475 https://github.com/rust-analyzer/smol_str/blob/fde86a5c0cb8f...
- jtrueb 2y agoI made humanize-bytes[1] for that formatting reason (1000 vs 1024). Coincidentally, it uses smartstring to avoid allocations. [1] https://crates.io/crates/humanize-bytes https://crates.io/crates/humanize-bytes
- weinzierl 2y agoHere [1] is a nice talk that discusses various options and trade-offs for small string and small vector optimization in Rust. [1] https://m.youtube.com/watch?time_continue=2658&v=tLX_nvWD738&embeds_referring_euri=https%3A%2F%2Frustlab.it%2F&embeds_referring_origin=https%3A%2F%2Frustlab.it&source_ve_path=MTM5MTE3LDM2ODQyLDEzOTExNywzNjg0MiwxMzkxMTcsMjg2NjY https://m.youtube.com/watch?time_continue=2658&v=tLX_nvWD738...
- avinassh 2y agoslightly related: earlier I was experimenting with Rust string allocations, even though my code did almost same as standard library, the heap allocations were taking 10x of time. Relevant code: fn alloc_string(s: &str) -> NonNull<u8> { let boxed_slice = s.as_bytes().to_owned().into_boxed_slice(); NonNull::new(Box::into_raw(boxed_slice) as *mut u8).unwrap() } Approximately, if stdlib was taking 1µs, but my code was about 14-15µs for large strings. Profiling also did not help. Anyone have any guesses? Here is the full code: https://github.com/avinassh/string-alloc https://github.com/avinassh/string-alloc
- deleted 2y ago[deleted]
- _urvc 2y agoWhen you call to_owned, it creates a Vec<u8>. This must then be truncated due to the call to into_boxed_slice. The documentation says this could potentially mean a re-allocation depending on the allocator. When this happens the old allocation needs to be dropped too. You could try using a different allocator but before this I would recommend to replace those two calls with Box::from(s.as_bytes()).
- skitter 2y agoThe vec already gets allocated with capacity == length https://doc.rust-lang.org/nightly/src/alloc/slice.rs.html#161 https://doc.rust-lang.org/nightly/src/alloc/slice.rs.html#16...
- dathinab 2y agoon interesting thing to realize is that some small string types go beyond just the basic small len storage optimization - compact_str e.g. depends on the string being valid utf-8, and in turn has larger short strings - smol_str e.g. is a a enum over `[u8; CAP] | &'static str | Arc<str>` this means it avoids any allocations for static strings and has very fast clones, leading to similar perf. characteristics as string internalization in some use cases (like the use-cases it was designed for). But at the cost of it being immutable only and the heap allocation being slightly larger for the Rc. Other interesting differences can be the handling of shrinking mutable Strings, do you re-inline it or not? What is better here is highly use-case dependent. In the end there are many design decisions where there is no clear winner but it's a question of trade off with use-case specific preferences.
- conaclos 2y agoReaders who like this article may also like a more recent one [0]. It designs a compact string with extra capabilities. The crate was released under the name byteyarn [1] [0] https://mcyoung.xyz/2023/08/09/yarns/ https://mcyoung.xyz/2023/08/09/yarns/ [1] https://docs.rs/byteyarn/latest/byteyarn/ https://docs.rs/byteyarn/latest/byteyarn/
- Abismith 2y ago[dead]
- codedokode 2y agoI wonder, is there a "string-like interface" in Rust or one has to rewrite all the code when changing string implementation? Also if you want to change implementation in half of the code, is there automatic convertion between implementations?
- duped 2y agoThis is what the `AsRef` trait is great for impl AsRef<str> for MyCoolString { fn as_ref(&self) -> &str { ... } } To be totally defensive, other library authors can write their APIs like fn my_cool_function(s: impl AsRef<str>) { let s: &str = s.as_ref(); } and now `s` is treated like any other &str. But even if library authors don't program defensively and use `s: &str` in the argument type, callers can always do this: my_cool_function(my_cool_string.as_ref());
- dubi_steinkek 2y agoIt's possible but I would prefer to just pass &str at that point, leading to less monomorphizations and a simpler API
- nextaccountic 2y agoFor read-only strings you pass around &str usually if you can borrow at all, and all UTF-8 string types should be compatible with it. AsRef is just a trait for "all types that can be converted into &str as needed" so if you use AsRef you need to have immutable strings too But the thing about String is that it allows you to push data at the end (like Java's StringBuilder). I think there is no commonly used trait for that, no. Note that not all owned string types can be appended at the end. Box<str> for example is just like String but without a .push_str() method. (This saves some bytes because it doesn't need a spare capacity) If your string is not UTF-8 then usually you convert it at API boundaries, to deal with UTF-8 stuff internally (or at least WTF-8)
- nextaccountic 2y ago(2020)