strace で C / Go / Rust / Python / Node.js のシステムコールを覗いてみた。

strace で C / Go / Rust / Python / Node.js のシステムコールを覗いてみた。

2026.02.07

人材育成室 育成メンバーチームで 研修中の はす です。

最近低レイヤーに興味を持ち始め、strace というコマンドを知りました。
なんとこれを使うと、プログラムの内部動作が見えるらしい。ということで、各言語の内部を覗いてみました。

今回はシンプルに比較するため、どの言語も Hello World を出力する処理にします。

strace コマンドについて
strace は System Call Trace の略で、つまりはシステムの呼び出しを追跡するということです。これを使うことで、ファイル操作やネットワーク通信、メモリ管理など、プログラムをトレースして、エラーの原因やプロセスの動きを確認することができます。

環境

項目 バージョン
OS Ubuntu 24.04
C (gcc) 13.3.0
Go 1.24.0
Rust 1.93.0
Python 3.14.3
Node.js 24.13.0
strace 6.8

検証

まずは、各言語で hello world を書いていきます。

C
#include <stdio.h>

int main() {
    printf("Hello World\n");
    return 0;
}
Go
package main

import (
	"fmt"
)

func main() {
	fmt.Println("Hello World")
}
Rust
fn main() {
    println!("Hello World");
}
Python
print("Hello World")
Node.js
console.log("Hello World");

システムコールの統計を比較

strace-cオプションをつけることで、各言語のシステムコール数の統計を出すことができます。

strace -c ./bin/hello_c > /dev/null

それでは各言語覗いてみます。
細かいことは後ほど解説するので、ぼんやり眺めてください。

C
$ strace -c ./bin/hello_c > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 45.38    0.000530         530         1           execve
 16.70    0.000195          32         6           mmap
  6.51    0.000076          19         4           mprotect
  4.88    0.000057          19         3           munmap
  4.62    0.000054          27         2           openat
  4.37    0.000051          17         3           fstat
  3.51    0.000041          13         3           brk
  2.91    0.000034          17         2           close
  1.97    0.000023          23         1         1 faccessat
  1.37    0.000016          16         1           read
  1.20    0.000014          14         1           write
  1.20    0.000014          14         1           prlimit64
  1.11    0.000013          13         1         1 ioctl
  1.11    0.000013          13         1           set_robust_list
  1.11    0.000013          13         1           getrandom
  1.11    0.000013          13         1           rseq
  0.94    0.000011          11         1           set_tid_address
------ ----------- ----------- --------- --------- ----------------
100.00    0.001168          35        33         2 total
Go
$ strace -c ./bin/hello_go > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ------------------
 38.64    0.001335           7       190           mmap
 17.97    0.000621         621         1           execve
 15.46    0.000534           3       165         1 rt_sigaction
  9.81    0.000339           0       457           rt_sigprocmask
  4.25    0.000147           7        20         1 futex
  2.20    0.000076          15         5           munmap
  2.14    0.000074          12         6           brk
  1.51    0.000052          17         3           clone
  1.04    0.000036           7         5           mprotect
  0.98    0.000034          11         3           openat
  0.93    0.000032           2        16           rt_sigreturn
  0.69    0.000024          24         1           sysinfo
  0.67    0.000023           5         4           read
  0.64    0.000022           5         4           close
  0.61    0.000021           7         3           lseek
  0.49    0.000017          17         1           set_tid_address
  0.46    0.000016          16         1           uname
  0.29    0.000010           5         2           prlimit64
  0.26    0.000009           9         1         1 faccessat
  0.26    0.000009           9         1           madvise
  0.14    0.000005           5         1           getgid
  0.14    0.000005           2         2           getrandom
  0.12    0.000004           4         1           getuid
  0.12    0.000004           2         2           getegid
  0.09    0.000003           3         1           sched_getaffinity
  0.09    0.000003           1         2           geteuid
  0.00    0.000000           0         6           fcntl
  0.00    0.000000           0         1           write
  0.00    0.000000           0         2           readv
  0.00    0.000000           0         2           fstat
  0.00    0.000000           0         2           gettid
------ ----------- ----------- --------- --------- ------------------
100.00    0.003455           3       911         3 total
Rust
$ strace -c ./bin/hello_rust > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ------------------
 19.28    0.000139          19         7           mprotect
 12.07    0.000087           7        11           mmap
  9.43    0.000068          13         5           rt_sigaction
  7.35    0.000053          13         4           openat
  6.66    0.000048          12         4           read
  5.96    0.000043           7         6           munmap
  5.69    0.000041          13         3           sigaltstack
  4.85    0.000035           8         4           close
  4.16    0.000030          15         2           prlimit64
  3.88    0.000028           9         3           brk
  3.47    0.000025           6         4           fstat
  2.77    0.000020          20         1           ppoll
  2.50    0.000018          18         1           rseq
  2.22    0.000016          16         1           write
  2.22    0.000016          16         1           set_robust_list
  1.94    0.000014          14         1           set_tid_address
  1.94    0.000014          14         1           gettid
  1.94    0.000014          14         1           getrandom
  1.66    0.000012          12         1           sched_getaffinity
  0.00    0.000000           0         1         1 faccessat
  0.00    0.000000           0         1           execve
------ ----------- ----------- --------- --------- ------------------
100.00    0.000721          11        63         1 total
Python
$ strace -c python3 python/hello.py > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ------------------
 16.52    0.000352          12        29         8 openat
 15.91    0.000339          14        24           mmap
 11.22    0.000239          23        10           mprotect
  9.90    0.000211           8        24           read
  9.53    0.000203           9        21           close
  6.52    0.000139          11        12           munmap
  6.48    0.000138           5        26           fstat
  6.43    0.000137           2        49        18 newfstatat
  3.05    0.000065           6        10           brk
  2.72    0.000058           3        17         2 lseek
  1.88    0.000040          13         3           getrandom
  1.36    0.000029           3         8         3 ioctl
  1.17    0.000025          12         2           prlimit64
  0.94    0.000020          10         2           getcwd
  0.84    0.000018           3         6         4 readlinkat
  0.66    0.000014           1        10           getdents64
  0.66    0.000014          14         1           futex
  0.66    0.000014          14         1           set_robust_list
  0.61    0.000013          13         1           set_tid_address
  0.61    0.000013          13         1           gettid
  0.61    0.000013          13         1           rseq
  0.56    0.000012          12         1           sched_getaffinity
  0.28    0.000006           6         1           write
  0.23    0.000005           1         4           fcntl
  0.23    0.000005           0        66           rt_sigaction
  0.14    0.000003           3         1           getgid
  0.09    0.000002           2         1           getuid
  0.09    0.000002           2         1           geteuid
  0.09    0.000002           2         1           getegid
  0.00    0.000000           0         1         1 faccessat
  0.00    0.000000           0         1           execve
------ ----------- ----------- --------- --------- ------------------
100.00    0.002131           6       336        36 total
Node.js
$ strace -c node node/hello.js > /dev/null 
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ------------------
 17.95    0.000738          11        64           mmap
  9.41    0.000387           5        68           munmap
  8.49    0.000349          20        17         2 openat
  6.81    0.000280         280         1           execve
  5.47    0.000225           9        23           read
  5.40    0.000222          11        20           fstat
  5.04    0.000207           7        27           mprotect
  4.79    0.000197           3        63           rt_sigaction
  4.26    0.000175           8        21           close
  4.11    0.000169           4        37           futex
  3.43    0.000141           5        26        14 fcntl
  3.11    0.000128           4        27           madvise
  3.09    0.000127          18         7         3 statx
  2.65    0.000109          18         6           clone
  2.29    0.000094           3        27           getpid
  1.99    0.000082           4        17           brk
  1.34    0.000055           3        18         2 ioctl
  1.29    0.000053           3        15           capget
  1.26    0.000052           3        15           getegid
  1.14    0.000047           1        27           rt_sigprocmask
  1.12    0.000046           3        15           geteuid
  1.09    0.000045           3        15           getuid
  1.07    0.000044           2        15           getgid
  1.00    0.000041          41         1         1 faccessat
  0.46    0.000019           2         8           prlimit64
  0.36    0.000015           5         3           pipe2
  0.34    0.000014           7         2           eventfd2
  0.34    0.000014           7         2           epoll_create1
  0.22    0.000009           4         2         2 io_uring_setup
  0.15    0.000006           6         1           sched_getaffinity
  0.12    0.000005           5         1           gettid
  0.10    0.000004           4         1           prctl
  0.10    0.000004           2         2           getrandom
  0.07    0.000003           3         1         1 clone3
  0.05    0.000002           0         4           epoll_ctl
  0.05    0.000002           0         3           write
  0.02    0.000001           1         1           getcwd
  0.00    0.000000           0         3           epoll_pwait
  0.00    0.000000           0         1           readlinkat
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0         1           set_robust_list
  0.00    0.000000           0         1           rseq
------ ----------- ----------- --------- --------- ------------------
100.00    0.004111           6       610        25 total

ぱっと見で行数を比較すると、言語ごとにシステムコールの量が違うことがわかったと思います。
特に C が一番少なく、Node.js や Go が長いです。
また、今回着目している write システムコールの回数を見ると、Node.js 以外は全て 1回 であることがわかると思います。これは、Hello World を一回標準出力しているためです。
なぜ Node.js だけ多いかは次のセクションで解説します。

統計データの各列についての意味は以下になります。

項目 意味
time 全体の実行時間に対する割合、対象のシステムコールがどれだけの時間を使ったか。
seconds 実行にかかった合計時間(秒)、対象のシステムコールが費やした総時間。
usecs / call 1回あたりの平均時間(マイクロ秒)、対象のシステムコール1回の呼び出しにかかった時間
errors エラー回数、対象のシステムコールが失敗した回数
syscall システムコール数、実際に呼ばれたカーネル関数

これを踏まえてみると、数値としてシステムコール数の差に気づくと思います。

言語 calls seconds
C 33 0.001168 秒
Go 911 0.003455 秒
Rust 63 0.000721 秒
Python 336 0.002131 秒
Node.js 610 0.004111 秒

なんと、Hello World を出すだけなのに、C は 33回 、Go は 911回約28倍 の差があることがわかりました。
なぜ違いが生まれるのかは後ほど解説します。

各言語ごとの write システムコールを見比べる

strace-e オプションをつけることで指定したシステムコールのみ抽出することができます。
以下の場合は、C言語の実行ファイルから write システムコールのみ抽出しています。

strace -e write ./bin/hello_c > /dev/null

それでは、各言語ごとに出力していきます。

C
$ strace -e write ./bin/hello_c > /dev/null
write(1, "Hello World\n", 12)           = 12
+++ exited with 0 +++
Go
$ strace -e write ./bin/hello_go > /dev/null
write(1, "Hello World\n", 12)           = 12
+++ exited with 0 +++
Rust
$ strace -e write ./bin/hello_rust  > /dev/null
write(1, "Hello World\n", 12)           = 12
+++ exited with 0 +++
Python
$ strace -e write python3 python/hello.py > /dev/null
write(1, "Hello World\n", 12)           = 12
+++ exited with 0 +++
Node.js
$ strace -e write node node/hello.js  > /dev/null
write(5, "*", 1)                        = 1
write(1, "Hello World\n", 12)           = 12
write(12, "\1\0\0\0\0\0\0\0", 8)        = 8
+++ exited with 0 +++

なんと、どの言語でも Hello World の書き込みは同じでした。

write(1, "Hello World\n", 12)           = 12

なぜかというと、システムコールはカーネルが定義した窓口のようなもので、最終的にはどの言語も同じ窓口を通るためです。以下のようなイメージです。

スクリーンショット 2026-02-07 9.11.13

しかし、Node.js だけ追加の write が存在しています。
これは何なのかというと libuv(非同期I/O) の内部処理によるもので、深くなってしまうため割愛します。

なぜ違いが生まれるのか

各言語の統計を調べた時に出た疑問ですね。
どの言語も最終的には同じ write システムコールなのに、なぜ総コール数に差が出るのか。
それはランタイムの違いにあるようです。

ランタイムの違い

言語 ランタイムの特徴 結果
C 直接機械語にコンパイルされるため、余計な処理が発生しません。 最小限(33回)
Go Go はランタイム起動時に goroutine 管理のための準備を行うため、シグナル関連(rt_sigprocmask 457回、rt_sigaction 165回)やメモリ確保(mmap 190回)が多く発生しています。 シグナル設定が多い(911回)
Rust C と同様にランタイムは最小限。しかし標準ライブラリの初期化処理で mmap(メモリ確保)などが発生します。 C にほぼ近い(63回)
Python インタプリタ言語のため、実行前に CPython の起動と標準ライブラリの読み込みが必要。ファイル関連のシステムコールが多く発生しています。 ファイルアクセスが多い(336回)
Node.js V8エンジン(JavaScript 実行) と libuv(非同期I/O)で構成されている。V8起動時のメモリ確保、libuv のワーカースレッドの生成がおこなわれます。 メモリの確保が多い(610回)

※各言語のランタイムの詳細については割愛します。

まとめ

どの言語も最終的には同じシステムコール(write) を呼んでいる。
しかし、そこに至るまでのシステムコール数は、言語によって大きく異なり、この差が 「速い」 「軽い」 と言われる正体の一部だということがわかりました。

実務ではなかなか触れない領域ではありますが、低レイヤーを学ぶことで自分のコードが内部でどう動いているのかが見えて面白いです。ぜひ皆さんも strace で覗いてみてください。

サンプルリポジトリ
https://github.com/HasutoSasaki/linux-system-playground

参考資料

system call
https://note.com/minato_kame/n/ne3111314f67f

https://qiita.com/t_ymgt/items/7f13c89b08b889da2146

node.js
https://docs.libuv.org/en/v1.x/design.html

write でパイプに通知する実装コード
https://github.com/libuv/libuv/blob/v1.x/src/unix/async.c

この記事をシェアする

FacebookHatena blogX

関連記事