back
20 comments
There are a LOT of people out there who will look at something like this and shrug. It's a relatively small change in code size, even less in memory usage, and the effort warrants a whole blog post.

To those people I say: Look, it's a small change to software structure that lead to non-trivial improvement in size. How many opportunities does your code have for this? Such optimizations have a compounding effect, and if you habitually ignore them we end up with resource gluttony and performance issues. By the time you end up with Electron apps it almost seems hopeless and people say "well 2gig is OK, most systems have 16 or 32gig these days.

Once you focus on optimisation for a bit and learn what works, it can become as fun as developing new features and your code will be better for it. Give it a try!

When trying to make my editor, KeenWrite[0], render TeX in real time using JMathTeX[1], I used initially used JFreeSVG[2] to generate SVG images (not rasterize them). After running the code through async-profiler[3], it turned out that JFreeSVG was using Java's NumberFormat to convert double values to strings.

Comparative benchmarks when generating 1,000,000 SVGs from TeX expressions, 1 run each, no warm-up:

* NumberFormat: 778060 milliseconds (~13 minutes)

* RyuDouble: 421160 milliseconds (~7 minutes)

Eventually I replaced JMathTeX with my NTS fork[4] and replaced the JFreeSVG library with a custom class to create SVG document strings[5].

Going from non-real-time TeX to real-time TeX rendering was worth it.

[0]: https://github.com/DaveJarvis/keenwrite

[1]: https://jmathtex.sourceforge.net/

[2]: https://github.com/jfree/jfreesvg/

[3]: https://github.com/async-profiler/async-profiler

[4]: https://github.com/DaveJarvis/KeenType

[5]: https://github.com/DaveJarvis/KeenType/blob/main/svg/src/mai...

[6]: https://github.com/jfree/jfreesvg/pull/30 (bonus link)

P.S. Eventually, I replaced the Ryu Algorithm with an even faster (and simpler) double-to-string conversion:

https://github.com/DaveJarvis/KeenType/blob/main/tex/src/mai...

>> Going from non-real-time TeX to real-time TeX rendering was worth it.

That is one of those jumps that completely changes the nature of an app. IMHO on today's computers almost everything should feel like real time. Highly interactive at the very least.

It would be interesting to see if Identical Code Folding (ICF) can remove the dupe automatically.
Perhaps a classic compression algorithm can help too. It will still fill the memory at runtime but the binary file would be smaller.
Runtime memory footprint is more important than static store except for a small class of embedded applications.
Software containers also benefits from being small. It’s not critical if a container is a bit big, it will still work, but it’s faster to deploy small containers. And the images are compressed.
How are software containers supposed to benefit from another compression? Typically the problem is image transfer to server/CI, but container images are already being transferred as compressed layers, so an additional layer of compression won't help much.
Indeed you shouldn't compress twice.
In embedded, it's static store size is almost always the same as 'memory footprint'.
Also true, hence “small class of”. Consider a space probe, especially one already launched, that has a library of programs to be run. Pretty rare case!
This is exactly the use case that inheritance (like in C#, ObjectPascal, Java etc..) make so much more sense in terms of modeling a solution.

On the other hand, how easy it was to find the size of every function is amazing to me... kudos to the rust community.

> On the other hand, how easy it was to find the size of every function is amazing to me... kudos to the rust community.

FWIW, ELF symbol tables record the size of all symbols, including function ones, so something like `objdump -tj .text EXECUTABLE` will print the size of every (presumably) function in the .text section of EXECUTABLE in objdump's somewhat annoying native format. More generically, try something like `objdump -t EXECUTABLE | awk 'pass && $3 ~ /F/ { print $5, $6 } /^SYMBOL TABLE:/ { pass=1 }' | sort` (I did say the format was annoying). To enable builtin name demangling in GNU objdump, also pass -C.

This will work for some languages, but I very much doubt it'll work for at least two of the three languages mentioned (C# and Java) because even their native images often contain some kind of embedded runtime inside the executable.

I think you can compile both C# and Java to native code entirely (without a self-contained runtime) but it's not the common way to deploy executables written in those languages.

That said, I'm sure there are ways to get these statistics out of a .dll/.jar as well. Reasoning about them may be a little tough as you have no idea if the code will be JIT'ed or evaluated as bytecode until you run the code, but raw bytes on disk shouldn't pose a problem.

Worst case scenario, you can just dump the disassembled bytecode (https://stackoverflow.com/questions/11345034/how-many-bytes-... or https://stackoverflow.com/a/6574882) and start counting. Provided you don't add any native calls, of course, then you'll need to add objdump's transformed output as well.

Java and .NET are typically JIT compiled, so you'd need to use something like jitwatch (for JVM) to get the actual generated assembly for any such methods.

Analyzing the bytecode can give some insight into how the compiler is likely to treat the code (inlining, intrisics, etc.), of course, so it's not useless, just not the whole story.

And, as you mention, JVM (and .NET) languages can also be AOT compiled. In those cases you could use more standard analysis similar to that in the OP.

I don't know about C#, but you can get the java jit to dump lots of information about compilation. -XX:+UnlockDiagnosticVMOptions -XX:+LogCompilation Will produce a log file that, among other things, gives the method sizes.
Can you explain how inheritance makes more sense here? I don't see much practical difference between Circle/Path/etc. inheriting from base class Shape or implementing trait Shape here.

The only difference is that AFAIK the languages you cite all have enough runtime information to allow downcasting, which in Rust one has to introduce manually (it is one of the solutions mentioned in the post).

Rust supports downcasting via the Any trait.
You don't need downcasting. Dyn traits are enough
I'm thinking a lot of this happens at work where Thrift code generated data structure conversion adapters are commonplace, e.g., from A into B with many fields. Personally, I think it's the wrong approach and should expose functions that present data as-needed without materializing it wholesale as a different struct because that's expensive, greedy evaluation.