モダンなコンパイル言語のバイナリサイズとコンパイル時間を計測してみました。
比較のために C と C++ も含まれています。
/usr/lib/libSystem.B.dylib
/usr/lib/libresolv.9.dylibHaskell18,3320m1.915s/usr/lib/libSystem.B.dylib
@rpath/libHSbase-4.12.0.0-ghc8.6.5.dylib
@rpath/libHSghc-prim-0.5.3-ghc8.6.5.dylib
@rpath/libHSrts-ghc8.6.5.dylibC12,8160m0.083s/usr/lib/libSystem.B.dylibC++29,8360m0.694s/usr/lib/libc++.1.dylib
/usr/lib/libSystem.B.dylib
確認に使用したプラットフォームは macOS Catalina です。
比較に使用したのは自分自身のソースコードをファイルから読み取り、標準出力に表示するプログラムです。まったく同等の処理ではないかも知れないので、参考程度に見てください。各言語のコンパイルは速度優先で最適化しています。
感想
C のコンパイル時間の速さは圧巻です。
C++ のコンパイルは遅いとよく言われるのですが、今回のケースでは V、Rust、Haskell はそれよりもかなり遅いです。ただし、Rust と Haskell は動的リンクオプションをやめて静的リンクにすると、Rust は Go ぐらいまで、Haskell は C++ ぐらいまで速くなります(なぜ静的リンクは動的リンクよりコンパイルが速いのか、については私はわかっていません)。
Nim と V は C へのトランスパイラで、それぞれの言語のランタイムも C へトランスパイルされてバイナリに含まれます(という理解です)。V はかなり小さなバイナリを出力しています。
Nim は言語機能としてガベージコレクションを持っており、当然ランタイムにもガベージコレクションのコードが含まれるとすると、このサイズは大健闘だと思います。ただ、V(や Rust)はコンパイル時にオブジェクトの開放のタイミングを決定し自動開放してくれるという、GC とはまた違った強みがあります。
Go のバイナリが極端に大きいのは、Go のランタイムを含む外部ライブラリをすべて静的リンクしているためです。本当の意味でのシングルバイナリでポータビリティを担保しているのは Go だけ(という認識)です。
以下、検証したコードと一言コメントです。
Nim
let s = readFile("cat_nim.nim")
echo s
Nim のコードは本当に簡単です。
V
import os
s := os.read_file('cat_v.v')?
println(s)
V も簡単です。read_file がちゃんとモジュールの関数になっているのが好みです。
Go
package main
import (
"fmt"
"io/ioutil"
)
func main() {
s, err := ioutil.ReadFile("cat_go.go")
if err != nil {
panic(err)
}
fmt.Println(string(s))
}
ちょっとコード量が多い印象です。
Rust
use std::fs;
fn main() {
let contents = fs::read_to_string("cat_rust.rs").expect("error");
println!("{}", contents);
}
Rust はエラー処理を省略すると結構短く書けます。
Haskell
main = readFile "cat_haskell.hs" >>= putStrLn
1 行で書けました!
C
#include <stdio.h>
int main() {
char str[256];
FILE *fp = fopen("cat_c.c", "r");
while (fgets(str, sizeof(str), fp) != NULL) {
printf("%s", str);
}
printf("\n");
fclose(fp);
return 0;
}
バッファを用意してそこに繰り返し読み込んで、とやっていることは原始的です。が、これが基本にして最速なんですね。
C++
#include <fstream>
#include <iostream>
int main() {
std::string s;
std::ifstream f("cat_cpp.cpp");
while (std::getline(f, s)) {
std::cout << s << std::endl;
}
std::cout << std::endl;
f.close();
return 0;
}
C++ のライブラリを使っていますが、やっていることはほとんど C と変わらないです。