LoginSignup
10
6

More than 3 years have passed since last update.

モダンな言語のバイナリサイズとコンパイル時間について調べてみた

Last updated at Posted at 2020-10-08

モダンなコンパイル言語のバイナリサイズとコンパイル時間を計測してみました。
比較のために C と C++ も含まれています。

言語 バイナリサイズ コンパイル時間 依存ライブラリ
Nim 61,908 0m0.720s /usr/lib/libSystem.B.dylib
V 27,276 0m1.526s /usr/lib/libSystem.B.dylib
Go 1,691,864 0m0.295s /usr/lib/libSystem.B.dylib
Rust 14,440 0m1.284s @rpath/libstd-91fba10bbd33db69.dylib
/usr/lib/libSystem.B.dylib
/usr/lib/libresolv.9.dylib
Haskell 18,332 0m1.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.dylib
C 12,816 0m0.083s /usr/lib/libSystem.B.dylib
C++ 29,836 0m0.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

cat_nim.nim
let s = readFile("cat_nim.nim")
echo s

Nim のコードは本当に簡単です。

V

cat_v.v
import os

s := os.read_file('cat_v.v')?
println(s)

V も簡単です。read_file がちゃんとモジュールの関数になっているのが好みです。

Go

cat_go.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

cat_rust.rs
use std::fs;

fn main() {
  let contents = fs::read_to_string("cat_rust.rs").expect("error");
  println!("{}", contents);
}

Rust はエラー処理を省略すると結構短く書けます。

Haskell

cat_haskell.hs
main = readFile "cat_haskell.hs" >>= putStrLn

1 行で書けました!

C

cat_c.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++

cat_cpp.cpp
#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 と変わらないです。

10
6
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6