Note: this is avoiding using format! because it tends to bloat WASM apps.
I'm curious about everyone's opinions. Would the size benefit of using option 2 ever possibly be good enough to justify the loss of speed? Is the loss of speed really that significant anyway? Like, imagine I want to optimize size over all else because I assume Rust is already much faster than JS, but I want to minimize bundle size as much as possible.
I think you'd really have to profile doing it both ways to know if it was worth it. In general I would think the size savings are going to be fairly small in practice, though. The code to recombine the strings is going to take up space too, and class strings usually aren't THAT long.
If you're primarily worried about download size, looking into setting up compression on your server might be more reliable at reducing page sizes.
So I decided to go ahead and test this. Check out this repo for the implementation details.
Basically, I took all the form classes from bootstrap. In one cargo workspace (no_concat in the repo), I define the entire classes each as their own &'static str. In the second (concat) I define only the subsections of the classes (anything separated by a dash) each by their own &'static string. I compiled the code in release mode using the settings most optimized for size. Here are the results:
no_concat: 1312 bytes
concat: 19807 bytes
That's more than 100 times difference in the size. The answer is clear: whether you're optimizing for speed or for size, it seems like not concatenating the strings results in a smaller binary.
Any thoughts? Did I get anything wrong or make any huge mistakes? Let me know.
Inspecting the dissassembled wat, it looks like quite a bit of the extra size in concat is coming from additional parts of std that get used when you call concat(). If you were interested in how far you could push the concat version, you might be able to improve things by rolling your own concat. You'd probably need to use some unsafe to get anywhere close though.
I'm using wasm2wat to disassemble the wasm files to wat.
(Not to mention that the sum is unused, and thus LLVM might even compile it out entirely. Const propagation could plausibly fold it down to one unused usize, and not have any of that code at all either.)
So I think what it's showing is "it's smaller if you don't need to bring in a wasm allocator". That's certainly true, and good to know, but also means that if you're doing more than just this and thus need that allocator somewhere else in the program anyway, then you're measuring a cost that you've already paid elsewhere.
So it's hard to say whether it's telling you something useful to you or not.
Hi Mng12345, thanks that's a good point. I'm actually not concerned about whether it was a hot part of my code. I was just approaching the idea from a point of curiosity. I was just thinking about how many repeated substrings there were in a large app and wondered if eliminating them in this way could have any positive impact. I was surprised by the results so far because I expected at worst that the improvement would be too small to matter and was surprised to find that it actually bloated the code more, at least in this naive test case.
Thanks for our feedback, scottmcm. I admit I have almost no experience with profiling (this is actually my first attempt to do something like this). I appreciate the suggestions.
I updated the code and added no_std and switched to using wee-alloc to check the result, I did get some significant improvement on the concat version -- binary size dropped to around 13429 bytes, an improvement of roughly 7kb on the previous version.