date: 2020-01-04 layout: basic

title: Hello world

Let’s say you ask your programming language to do the simplest possible task: print out “hello world”. Generally this takes two syscalls: write and exit. The following assembly program is the ideal Linux x86_64 program for this purpose. A perfect compiler would emit this hello world program for any language.

``` bits 64 section .text global start start: mov rdx, len mov rsi, msg mov rdi, 1 mov rax, 1 syscall

   mov rdi, 0
   mov rax, 60

section .rodata msg: db “hello world”, 10 len: equ $-msg ```

Most languages do a whole lot of other crap other than printing out “hello world”, even if that’s all you asked for.

Test case Source Execution time Total syscalls Unique syscalls Size (KiB)
Assembly (x86_64) test.S 0.00s real 2 2 8.6 KiB*
Zig (small) test.zig 0.00s real 2 2 10.3 KiB
Zig (safe) test.zig 0.00s real 3 3 11.3 KiB
C (musl, static) test.c 0.00s real 5 5 95.9 KiB
C (musl, dynamic) test.c 0.00s real 15 9 602 KiB
C (glibc, static*) test.c 0.00s real 11 9 2295 KiB
C (glibc, dynamic) test.c 0.00s real 65 13 2309 KiB
Rust 0.00s real 123 21 244 KiB
Crystal (static) 0.00s real 144 23 935 KiB
Go (static w/o cgo) test.go 0.00s real 152 17 1661 KiB
D (dmd) test.d 0.00s real 152 20 5542 KiB
D (ldc) test.d 0.00s real 181 21 10305 KiB
Crystal (dynamic) 0.00s real 183 25 2601 KiB
Go (w/cgo) test.go 0.00s real 211 22 3937 KiB
Perl 0.00s real 255 25 5640 KiB
Java 0.07s real 226 26 15743 KiB
Node.js test.js 0.04s real 673 40 36000 KiB
Python 3 (PyPy) 0.68s real 884 32 9909 KiB
Julia test.jl 0.12s real 913 41 344563 KiB
Python 3 (CPython) 0.02s real 1200 33 15184 KiB
Ruby test.rb 0.04s real 1401 38 1283 KiB
* See notes for this test case

This table is sorted so that the number of syscalls goes up, because I reckon more syscalls is a decent metric for how much shit is happening that you didn’t ask for (i.e. write("hello world\n"); exit(0)). Languages with a JIT fare much worse on this than compiled languages, but I have deliberately chosen not to account for this.

These numbers are real. This is more complexity that someone has to debug, more time your users are sitting there waiting for your program, less disk space available for files which actually matter to the user.


Tests were conducted on January 3rd, 2020.

For each language, I tried to write the program which would give the most generous scores without raising eyebrows at a code review. The size of all files which must be present at runtime (interpreters, stdlib, libraries, loader, etc) are included. Binaries were stripped where appropriate.

This was not an objective test, this is just an approximation that I hope will encourage readers to be more aware of the consequences of their abstractions, and their exponential growth as more layers are added.


``` bits 64 section .text global start start: mov rdx, len mov rsi, msg mov rdi, 1 mov rax, 1 syscall

   mov rdi, 0
   mov rax, 60

section .rodata msg: db “hello world”, 10 len: equ $-msg ```

nasm -f elf64 test.S gcc -o test -static -nostartfiles -nostdlib -nodefaultlibs strip test: 8.6 KiB



``` const std = @import(“std”);

pub fn main() !void { const stdout = try; try stdout.write(“hello world\n”); } ```



zig build-exe test.zig –release-small –strip


zig build-exe test.zig –release-safe –strip ```



``` int puts(const char *s);

int main(int argc, char *argv[]) { puts(“hello world”); return 0; } ```



gcc -O2 -o test test.c strip test


gcc -O2 -o test -static test.c strip test ```


fn main() { println!("hello world"); }

rustc -C opt-levels=s



``` package main

import “os”

func main() { os.Stdout.Write([]byte(“hello world\n”)) } ```



go build -o test test.go

static w/o cgo

GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o test -ldflags ‘-extldflags “-f no-PIC -static”’ -buildmode pie -tags ‘osusergo netgo static_build’ test.go ```

Aside: it is getting way too goddamn difficult to build static Go binaries.


public class Test { public static void main(String[] args) { System.out.println("hello world"); } }

javac java Test

puts "hello world\n"



crystal build -o test


crystal build –static -o test ```



console.log("hello world");

node test.js


println("hello world")

julia test.jl


print("hello world")





pypy3 ```

print "hello world\n"




import std.stdio; void main() { writeln("hello world"); }



dmd -O test.d


ldc -O test.d ```


puts "hello world\n"

ruby test.rb