That's a debug binary, and the vast majority of that is debug symbols. A release build of this project is 4.3M, an order of magnitude smaller.
Also, compiling out the default features of the git2 crate eliminates several dependencies and reduces it further to 3.6M.
https://github.com/bgreenwell/lstr/pull/5
https://github.com/rust-lang/git2-rs/pull/1168
Stripping the binary further improves it to 2.9M, and some further optimizations get it to 2.2M without any compromise to performance. (You can get it smaller by optimizing for size, but I wouldn't recommend that unless you really do value size more than performance.)
Most shells dynamically link to a runtime your OS provides "for free". The 4.3 MiB binary in question is bundling the Rust runtime and its dependencies.
For reference, a statically-compiled C++ "Hello, World" is 2.2 MiB after stripping.
% cat hello.nix
{
pkgs ? import <nixpkgs> { crossSystem = "aarch64-linux"; }
}:
pkgs.stdenv.mkDerivation {
name = "hello-static";
src = pkgs.writeText "hello.cpp" ''
#include <iostream>
int main() {
std::cout << "Hello, World!" << std::endl;
return 0;
}
'';
dontUnpack = true;
buildInputs = [ pkgs.glibc.static ];
buildPhase = "$CXX -std=c++17 -static -o hello $src";
installPhase = "mkdir -p $out/bin; cp hello $out/bin/";
}
% nix-build hello.nix
...
% wc -c result/bin/hello
2224640 result/bin/hello
The executable takes 33KB in C, 75KB in nim.
But in all seriousness, my example is quite cherrypicked, since nobody will actually statically link glibc. And even if they did, one can make use of link-time optimization to remove lots of patches of unused code. Note that this is the same strategy one would employ to debloat their Rust binaries. (Use LTO, don't aggressively inline code, etc.)
Did you statically link Glibc...? Or is this with a non-GNU libc?
Either way, it probably is true that this 2.2MiB number would be smaller on, say, Debian 5. And much smaller on PDP Unix.
ARM64 assembly program (hw.s):
//
// Assembler program to print "Hello World!"
// to stdout.
//
// X0-X2 - parameters to linux function services
// X16 - linux function number
//
.global _start // Provide program starting address to linker
.align 2
// Setup the parameters to print hello world
// and then call Linux to do it.
_start: mov X0, #1 // 1 = StdOut
adr X1, helloworld // string to print
mov X2, #13 // length of our string
mov X16, #4 // MacOS write system call
svc 0 // Call linux to output the string
// Setup the parameters to exit the program
// and then call Linux to do it.
mov X0, #0 // Use 0 return code
mov X16, #1 // Service command code 1 terminates this program
svc 0 // Call MacOS to terminate the program
helloworld: .ascii "Hello World!\n"
Assembling and linking commands: as -o hw.o hw.s &&
ld -macos_version_min 14.0.0 -o hw hw.o -lSystem -syslibroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.2.sdk -e _start -arch arm64
Resulting file sizes: -rwxr-xr-x 1 <uid> <gid> 16K Jun 18 21:23 hw
-rw-r--r-- 1 <uid> <gid> 440B Jun 18 21:23 hw.o
-rw-r--r-- 1 <uid> <gid> 862B Jun 18 21:21 hw.s
0 - https://smist08.wordpress.com/2021/01/08/apple-m1-assembly-l...Rust binaries also dynamically link to and rely on this runtime.
just to have everything in one file? how to show how to do it with nix?
because it seem simpler to have a separate C++ file, and a simple shell script or makefile to compile it.
e.g. although I could figure out roughly what the .nix file does, many more people would know plain unix shell than nix.
and where is $out defined in the .nix file?
$out is a magic variable in nix that means the output of the derivation - the directory that nix moves to its final destination
from a Google search:
>Overview Lake Titicaca, straddling the border between Peru and Bolivia in the Andes Mountains, is one of South America's largest lakes and the world’s highest navigable body of water. Said to be the birthplace of the Incas, it’s home to numerous ruins. Its waters are famously still and brightly reflective. Around it is Titicaca National Reserve, sheltering rare aquatic wildlife such as giant frogs.
:)
2288K /bin/bash-static (per manual, "too big and too slow")
1936K /bin/busybox-static (including tools not just the shell)
192K /usr/lib/klibc/bin/mksh
2456K zsh-static
For comparison, some dynamically-linked binaries (some old) 804K ./bin/bash-3.2
888K ./bin/bash-4.0
908K ./bin/bash-4.1
956K ./bin/bash-4.2
1016K ./bin/bash-4.3
1092K ./bin/bash-4.4
1176K ./bin/bash-5.0
1208K ./bin/bash-5.1
1236K /bin/bash (5.2)
124K /bin/dash
1448K /bin/ksh93 (fattest when excluding libc!)
292K /bin/mksh
144K /bin/posh
424K /bin/yash
848K /bin/zsh
(The reason I don't have static binaries handy is because they no longer run on modern systems. As long as you aren't using shitty libraries, dynamic binaries are more portable and reliable, contrary to internet "wisdom".) cargo build --release
du -sh ./target/release/lstr -> 4.4M
Building with other release options brings it down to 2.3M: [profile.release]
codegen-units = 1
opt-level = "s"
lto = true
panic = "abort"
strip = "symbols"
If you just think about how roughly (napkin math) 2MB can be 100k loc, that’s nuts
Further I think everyone keeps getting larger and larger memory because software keeps getting more and more bloated.
I remember when 64gb iPhone was more than enough (I don’t take pictures so just apps and data) Now my 128 is getting uncomfortable due to the os and app sizes. My next phone likely will be a 256
> First because it’s kind of a fallacy. Size is absolute not relative to something. Especially for software. No one thinks of software size primarily in the context of their disk space.
That’s exactly how most people think about file sizes.
When your disk is full, you don’t delete the smallest files first. You delete the biggest.
> Further I think everyone keeps getting larger and larger memory because software keeps getting more and more bloated.
RAM sizes have actually stagnated over the last decade.
> I remember when 64gb iPhone was more than enough (I don’t take pictures so just apps and data) Now my 128 is getting uncomfortable due to the os and app sizes. My next phone likely will be a 256
That’s because media sizes increase, not executable sizes.
And people do want higher resolution cameras, higher definition videos, improved audio quality, etc. These are genuinely desirable features.
Couple that with improved internet bandwidth allowing for content providers to push higher bitrate media, however the need to still locally cache media.
Part of it is app sizes on mobile. But it's apps in the 200mb - 2gb range that are the problem, not ones that single-digit megabytes.
This is wrong. The reason why many old tools are so small was because you had far less space. If you have a 20tb harddrive you wouldn't care about whether ls took up 1kb or 2mb, on a 1gb harddrive it matters/ed much more.
Optimization takes time, I'm sure if OP wanted he could shrink the binary size by quite a lot but doing so has its costs and nowadays its rarely worth paying that since nobody even notices wether a program is 2kb or 2mb. It doesn't matter anymore in the age of 1TB bootdrives.
What incentive does Apple have to help iOS devs get package sizes down, then?
says for this one the deps clock in at: ~19–29MB ~487K SLoC
Second off, I didn't realize how deep the dep tree would be for this type of program -- 141 total! So much of it is the url crate, itself a dep of the git crate, but there's a bunch of others too. I'm just getting into learning Rust -- is this typical of Rust projects or perhaps typical of TUI projects in general?
(EDIT to strikeout) ~~The binary is also 53M as a result whereas /usr/sbin/tree is 80K on my machine -- not really a problem on today's storage, but very roughly 500-1000x different in size isn't nothing.~~
Maybe it's linking-related? I don't know how to check really.
(EDIT: many have pointed out that you can run `cargo build --release` with other options to get a much smaller binary. Thanks for teaching me!)