7 ms·
Thoughts on Rust bloat (2019)
- deleted 6y ago[deleted]
- jerf 6y agoThe article does mention this a bit, but I would suggest to people to be a bit more careful about accusing everything of executable-size-bloat. We have the mentioned Unicode tables. Can you cut them? Sure, but now you've got a binary that only works in English and very-English like languages for no reason. We use 64-bit stuff for good reason, because we routinely blow out 32-bit limits, but that's 8 bytes for all sorts of things that used to be 4, pervasively throughout the code. We have localization support. We have some sort of async runtime or memory managing runtime or other thing like that. We have all sorts of things like this. If you sit down and take an inventory of what's going on, the minimum size for some sort of "serious" executable is still going to be quite a lot. (Could you move some of this stuff out of the executable? Yeah, but that really only addresses the immediately visible executable size and comes with tradeoffs of its own... it doesn't mean the executable is "smaller", it just means it's split into pieces.) It's a bit of a pipe dream now to expect some sort of "serious" executable (one that actually has all these concerns) to fit into half-a-megabyte anymore, just because Commodore 64 programs used to fit into a hundred kilobytes.
- flohofwoe 6y ago100 KBytes is quite bloaty for a C64 program and wouldn't even fit into RAM in one piece ;) But anyway... wouldn't the better and "obvious" solution be to move foundational things like the above mentioned UNICODE tables into operating systems (if they're not already provided) instead of shipping them with every application? After all, what's the point of operating systems if they don't provide commonly needed services (and I wonder if such solutions had even been considered for said Rust libraries). PS: One situation where binary size matters a lot is WASM running in browsers. It's not just download bandwidth, but also the time it takes to compile the executable. We're now back at WASM binaries stuttering on startup for a few seconds because the JIT needs to "warm up". For a very short time, when WASM was AOT compiled, this wasn't an issue. But it became an issue as people became careless about binary size and just-in-time compilation had to be reintroduced to cope with massively big WASM binaries.
- jandrese 6y agoDoes this mean you need to ship a new Rust binary for every application you build when the Unicode consortium updates the tables?
- PopsiclePete 6y agoIf your application needs support for new emojis or some dead language, then yes.
- throwaway894345 6y ago> After all, what's the point of operating systems if they don't provide commonly needed services My answer is "managing access to shared resources"--the primary role of an OS is to allow multiple programs to run on the same hardware without needing to be aware of each other. I specifically don't think the OS should be a kitchen sink for common utilities / services. I would actually prefer fewer things managed by the OS (microkernel if not unikernel). In my utopia, the line between "OS" and "Hypervisor" would be very thin/blurry. If we really want to be space-conscious, I think the answer is dynamic linkage, although I think people have forgotten the horror that is dependency hell. Nix conceptually provides an answer for dependency hell, but in my experience there remains quite a chasm between what Nix promises or aspires towards and what it actually delivers. For the time being, I have to think that static binaries are overwhelmingly the most economical solution when you account for time spent mucking with dependencies.
- gpm 6y ago> what's the point of operating systems if they don't provide commonly needed services To abstract over hardware, including to allow running multiple programs simultaneously on the same hardware. I'd argue that there's pretty widespread agreement in how we actually deploy modern systems when given the choice, that this is all we want the operating system to do too. There's all these operating systems that try to do more (e.g. approximately every linux distribution ever), and yet modern software primarily runs in some sort of sandbox who doesn't allow any of that to seep into the environment. On servers that sandbox looks like "containers" or less frequently "jails". On clients that sandbox looks like "a browser" or "electron". Empirically the idea of shipping commonly used dependencies with the hardware abstraction layer, seems to have been the wrong idea. Even if you disagree with that, certainly it's not the case that it's the only point of shipping the hardware abstraction layer (operating system) in the first place.
- agumonkey 6y agoReminds me of the articles about text processing in haskell vs C. haskell wasn't as lean but it was more general (full unicode support). There was no mystery in the size difference.
- wyldfire 6y agoPrevious HN discussion: https://news.ycombinator.com/item?id=20761449 https://news.ycombinator.com/item?id=20761449
- kvark 6y agoWe need more downstream projects like this putting pressure on the library authors to cut down their dependency trees as well as generic usage. Perhaps, we need better tools for this. Take `cargo-bloat` for example: if library A makes a generic type from library B to monomorphize, would the build time reflected in `A`? It would be good to have something telling us: this API has a build complexity metric of 1.23 - something we could feature and/or track on CI.
- throwaway894345 6y agoI think "better tools" would be a good way to go. In general I think we need to make these things that we care about (performance, binary size, compilation time, etc) more visible and quantifiable and to allow us to easily root cause them. I think we need to make profiling these things dead simple and fast--with respect to performance, the time to get a flamegraph should be nearly zero, and the flamegraph's UI should be optimized (e.g., the ability to "peek" at a function's definition directly from the flamegraph). We need similarly easy ways to trace compilation time and code bloat to their causes in the code, perhaps even with automated suggestions for improvements.
- pjmlp 6y agoThis is mostly an issue because cargo doesn't handle binary libraries.
- raphlinus 6y agoThis was written a year and a half ago, but I think holds up pretty well. Some thoughts. First, one lesson is that the exact wording in headlines matters. Many people interpreted the essay to say "Rust has a bloat problem," which is not what I was trying to say. Bloat is unfortunately a super common problem in the modern world, and Rust is not immune. But Rust also gives the end developer a lot of control, and is capable of producing wicked-lean programs (at some cost) if that's the goal. The point of the essay was to give practical advice on how to reduce compile times and executable size. From that perspective, publishing the essay was a pretty big success, as it immediately provoked improvements in the gfx-hal project. Serialization continues to be a problem. Now, something like half the compile time and executable size for Runebender is serialization for the XML-based UFO file format for representing fonts. I think a better solution to serialization might make a good project for a smart, motivated person, but it's pretty far away from current academic fads, and there's also this thing that a lot of people have tried and we're still not in a great place. What about serialization makes it so intractable? Could some kind of language support make it better? Also, from followups in UI threads, the expectation for what constitutes "bloat" is very different for different people. The calculator example in Druid is now about 1.5M (release build, Windows), which I think is acceptable for most people, but I'm also aware that a simple calculator like that could be built in a couple hundred k or so without too much problem, and I'm sure much less if you start using demoscene style techniques. But I also don't want to focus on that too much - I don't want tiny calculator demos, I want rich applications that work in all languages (including input methods) and also play nicely with accessibility tools such as screen readers. That can't be, and shouldn't be, a tiny little executable.
- freeopinion 6y agoThis is a great place to explore a recent line of thought I have had. I'd love to hear your thoughts on sqlite as a file format. Just hypothetically, if Runebender ditched xml and used sqlite files instead, what would that do to serialization? What other bloat implications would it have? What value would you lose by going away from xml?
- raphlinus 6y ago
- slmjkdbtl 6y ago"but I’ve found that the rust-objc macros produce quite bloated code, on the order of 1.5k per method invocation" this is horror story, is dynamic ObjC even a valid approach over ObjC-in-C FFI? I hear it's quite slow / bloated in other languages' bindings too
- comex 6y agoEither there's something wrong with their measurement or the issue has been fixed. I tried modifying rust-objc's examples/example.rs to add 1000 method invocations, each with a unique selector: let hash123: usize = msg_send![*obj, hash123]; Comparing the built version with and without the addition, on x86-64 macOS, each message send adds, on average, 52 bytes of code (suboptimal but not terribly unreasonable) and 8 bytes to store the selector name itself (including nul terminator). That's it. Plus 113 bytes for the symbol table entry, but that goes away if you strip the binary. It's possible that the bloat comes from something else, perhaps class declarations.
- raphlinus 6y agoI think it got better, but unfortunately I never filed a bug documenting the bloat, so it's hard to track down what changed, or if I was simply wrong. My recollection is that most of the bloat was a per-invocation copy of the panic code, including formatting of the error message. Class declarations are still heavy, cargo-bloat on the current druid-shell library is showing about 900 bytes per add_method, which can add up but is unlikely to be a major source of bloat.
- baby 6y ago> I’m about to accept a PR that will increase druid’s compile time about 3x and its executable size almost 2x I looked at the PR and it is a mere 700 LOC. I agree that compile time are an awful thing in Rust, but there are big glaring issues as well: LOC expansion and dependencies expansion.
- richardwhiuk 6y agoThis change: [dependencies] fluent-bundle = "0.7" fluent-locale = "0.7" fluent-syntax = "0.9" unic-langid = "0.4.1" and the corresponding change in Cargo.lock is the reason. I'm not sure about how you are expecting dependencies to be handled?
- baby 6y agoYeah, that's the issue. Dependencies growth is absurd in Rust. > I'm not sure about how you are expecting dependencies to be handled? I'll give you my point of view: a better standard library.
- richardwhiuk 6y agoThat just mean every binary needs to ship everything, even stuff they don't use.
- fmakunbound 6y ago> proc macros. The support crates for these (syn and quote) take maybe 10s to compile Common Lisp user here. What's up with Rust where macros take 10s to compile? I'm keen on seeing macros in other languages, but that's kinda brutal from a usability perspective.
- IshKebab 6y agoIt takes 10s to compile the crate that defines the macros, it doesn't take 10s to evaluate them. And the reason they're so slow to compile is because they're compiled to native code, and you aren't given a parser, just a list of tokens, so basically all of them depend on some extra parsing crates.
- fmakunbound 6y ago> It takes 10s to compile the crate that defines the macros Brutal. > they're so slow to compile is because they're compiled to native code I guess that applies just to Rust
- diragon 6y agoCompiling Common Lisp took 10 seconds or more as well back when it was last used. ;-)