TL;DR
Rocky Linux上で動かしている自作OSベンチマーク「NOVE OS」が
v17.0のリリースで 2879.86/3000(96.0%)Grade SS を達成した。
初回スコアは 1774.70。累積改善幅は +1105点(+62.3%)。
v17.0では主に3つの問題を潰した。
-
Docker:
docker run --rmをやめてdocker execに変える - Podman: nginx keep-aliveによるconnection resetを根本解決
- Spark: JVMを毎回起動していたコストを「1回だけ」に変える
どれも派手ではない。でも数字は正直だった。
スコア推移(全履歴)
バージョン スコア グレード
──────────────────────────────────────────
初回 1774.70 C
v13.2 2269.01 (+494) B+
v13.2 2442.50 (+173) A
v13.2 2568.96 (+127) A+
v13.2 2711.06 (+142) S 🏆
v13.2 2722.71 (+12) S
v13.2 2749.15 (+26) S
v13.2 2814.68 (+66) SS 🏆🏆
v13.2 2827.62 (+13) SS
v17.0 2879.86 (+52) SS 🏆🏆 ← 現在
ベンチマーク構成
NOVE OSベンチマークは以下の30カテゴリで構成されている。
| カテゴリ群 | 主な測定項目 |
|---|---|
| システム基盤 | CPU、メモリ、ディスクI/O、ネットワーク |
| コンテナ | Docker、Podman、ストレージドライバ |
| 分散処理 | Apache Spark(RDD操作、JVM起動時間) |
| ネットワーク高速化 | eBPF、XDP、io_uring |
| データストア | Redis、分散ストレージ |
| AI/ML | モデル推論、量子シミュレーション |
| セキュリティ | SELinux、auditd、パスワードポリシー |
| その他 | WASM、GPU、MPI、gRPC など |
v17.0が「CONTAINER MASTER」と名付けられた理由は、
今回の改善の中心がコンテナ(Docker/Podman/Spark)だったからだ。
問題1: Dockerスコア 70 → 99
何が起きていたか
ストレージドライバのベンチマークで、毎回こういうコードを書いていた。
result = subprocess.run(
["docker", "run", "--rm", "alpine",
"dd", "if=/dev/zero", "of=/tmp/test", "bs=1M", "count=100"],
capture_output=True,
timeout=60
)
duration = time.time() - start
throughput = 100 / duration # MB/s
一見正しそうに見える。でも実際に測定しているのは
「コンテナ起動時間 + dd実行時間 + コンテナ終了時間」 の合計だ。
docker run --rm alpine ... は毎回コンテナをゼロから起動する。
alpine のイメージキャッシュがあっても、コンテナ初期化のオーバーヘッドは避けられない。
さらに計算式にもバグがあった。
throughput = 100 / duration # ← 100MBと計算しているが...
実際には bs=4M count=100 = 400MB に変更したあと、計算式だけ直し忘れていた。
どう直したか
1. docker run -d + docker exec に変更
# Before: 毎回コンテナを起動してddを実行
result = subprocess.run(
["docker", "run", "--rm", "alpine",
"dd", "if=/dev/zero", "of=/tmp/test", "bs=1M", "count=100"],
...
)
# After: コンテナを起動したまま exec でddを実行
container = subprocess.run(
["docker", "run", "-d", "--name", bench_container, "alpine", "sleep", "60"],
capture_output=True, timeout=30
)
result = subprocess.run(
["docker", "exec", bench_container,
"dd", "if=/dev/zero", "of=/tmp/test", "bs=4M", "count=100"],
capture_output=True, timeout=60
)
duration = time.time() - start
2. ブロックサイズと計算式を修正
# Before
"bs=1M", "count=100"
throughput = 100 / duration
# After
"bs=4M", "count=100"
throughput = 400 / duration # 400MB書き込みに対応
結果
Before: 244 MB/s → score 70
After: 1000+ MB/s → score 99
コンテナ起動オーバーヘッドを測定から切り離しただけで、
スループットが 約4倍 になった。
問題2: Podmanスコア 62 → 98
何が起きていたか
Podmanのネットワークベンチマークで、コンテナ内のnginxに
subprocess curl 経由でHTTPリクエストを送っていた。
このとき nginx はHTTP/1.1のkeep-aliveで接続を維持しようとする。
しかし subprocess curl は1リクエストごとにプロセスを起動・終了するため、
接続が突然切断される → nginx側で connection reset が発生していた。
スタートアップ時間も問題で、デーモンレスなPodmanは
Docker に比べてコンテナ起動に時間がかかる。実測で 8秒 かかっていた。
podman_score = startup(50%) + storage_driver(50%)
startup: 8秒 → score 28
storage_driver: score 96
podman_score: (28 + 96) / 2 = 62
どう直したか
connection reset問題の解決
# Before: subprocess curl(プロセスごとに接続が切れる)
result = subprocess.run(
["curl", "-s", f"http://localhost:{port}/"],
...
)
# After: HTTP/1.0 + Pythonソケット直接接続(keep-aliveなし)
import socket
def _http_get(host, port, path="/"):
"""HTTP/1.0で1リクエスト1接続(connection reset回避)"""
with socket.create_connection((host, port), timeout=5) as s:
req = f"GET {path} HTTP/1.0\r\nHost: {host}\r\n\r\n"
s.sendall(req.encode())
response = s.recv(4096)
return response
HTTP/1.0は接続後に自動でcloseするため、
nginx側でconnection resetが発生しない。
スタートアップ改善
コンテナイメージのpull・warmupを事前に実施し、
ベンチマーク本番ではキャッシュ済みイメージのみを使うよう変更した。
結果
Before: startup 8秒 → score 28、podman_score 62
After: startup 0.5秒 → score 98、podman_score 98
ネットワーク: 1273 req/s 達成。
問題3: Sparkスコア 87 → 97
何が起きていたか
Sparkのベンチマークは2つのメソッドで構成されていた。
def benchmark_spark_session(self) -> Dict:
"""SparkSession起動時間測定"""
spark = SparkSession.builder.getOrCreate()
startup_time = ...
spark.stop() # ← ここでSparkを終了!
return result
def benchmark_rdd_operations(self) -> Dict:
"""RDD操作性能測定"""
spark = SparkSession.builder.getOrCreate() # ← また起動!
rdd = spark.sparkContext.parallelize(range(100000))
result = rdd.map(lambda x: x * 2).reduce(lambda a, b: a + b)
spark.stop()
return result
run_full_benchmark() でこの2つを順番に呼んでいたため、
JVMが2回起動していた。
JVMの起動には数秒かかる。しかもi5-8250UのようなモバイルCPUでは、
JVM起動のウォームアップ中にサーマルスロットリングが起きることもある。
benchmark_spark_session: JVM起動(3-5秒) + startup測定
benchmark_rdd_operations: JVM起動(3-5秒) + RDD操作測定
↑ ここが無駄
どう直したか
1セッション共有方式に変更
def run_full_benchmark(self) -> Dict:
"""1つのSparkセッションでstartup測定とRDD操作を両方実施"""
# JVM起動(1回のみ)
start_time = time.time()
spark = SparkSession.builder \
.appName("RockyBenchmark") \
.master("local[*]") \
.config("spark.driver.memory", "1g") \
.config("spark.ui.enabled", "false") \
.config("spark.serializer",
"org.apache.spark.serializer.KryoSerializer") \
.getOrCreate()
startup_time = time.time() - start_time
# startup スコア計算
startup_score = 100 if startup_time < 2 else ...
# 同じJVM上でRDD操作(ウォームアップ済み)
rdd_start = time.time()
rdd = spark.sparkContext.parallelize(range(100000))
result = rdd.map(lambda x: x * 2).reduce(lambda a, b: a + b)
rdd_duration = time.time() - rdd_start
# RDD スコア計算
rdd_score = 100 if rdd_duration < 2 else ...
# セッション終了(1回のみ)
spark.stop()
return {
"startup": startup_score,
"rdd_ops": rdd_score,
"total_score": (startup_score + rdd_score) / 2
}
ウォームアップ済みJVMでRDD操作を行うため、
実測では 0.5〜1秒 で完了するようになった。
結果
Before: JVM 2回起動 → Spark score 87
After: JVM 1回のみ → Spark score 97
Javaバージョンも重要だった
sudo 環境では PATH が変わり、システムのJava(Java 26)が選ばれてしまっていた。
PySpark 3.xはJava 17を推奨しており、Java 26では互換性の問題が出る。
# 問題: sudo だとシステムJava(Java 26)が使われる
$ sudo java -version
openjdk version "26" ... # ← Sparkが失敗する
# 解決: JAVA_HOMEを明示的に指定
sudo env JAVA_HOME="/home/user/.sdkman/candidates/java/current" python3 benchmark.py
v17.0以前の改善: Grade SS ブレークスルー
v17.0の前に、v13.2でGrade SSへの壁を突破した改善も紹介する。
eBPF・XDP・io_uring: スコアが出ない問題
eBPF: 68 → 100
XDP: 34 → 100
io_uring: 70 → 100
原因は単純だった。sudo 実行時にPATHから /usr/sbin と /sbin が消えていた。
# 問題
$ sudo python3 benchmark.py
# PATH = /usr/local/bin:/usr/bin:/bin ← /usr/sbin がない
# /usr/sbin/bpftool, /usr/sbin/ip などが見つからない → score 0
# 解決
sudo env PATH="/usr/sbin:/sbin:/usr/local/bin:/usr/bin:/bin" python3 benchmark.py
eBPFやXDPの測定には bpftool や ip link が必要で、
これらは /usr/sbin にある。PATHが通っていなければ当然0点になる。
Redis: 84 → 100
AOF(Append Only File)が原因でスループットが出ていなかった。
# Before: AOF有効(毎書き込みでfsync発生)
appendonly yes
# After: AOF無効 + ポーリング頻度を上げる
appendonly no
hz 100
Before: ~24,000 ops/sec → score 84
After: ~100,000+ ops/sec → score 100
ブラウザが開いているとスコアが大幅低下する
ベンチマーク中にChromeを開いたままにすると、
i5-8250UのL3キャッシュ(6MB)をChromeが占有する。
Chrome開いたまま: CPU処理 2.67s → 5.74s(2.15倍遅い)
Neuromorphic: 97 → 73 (-24pt)
AI/ML: 97 → 78 (-19pt)
合計低下: 約 -88pt
Grade SSを狙うなら、ブラウザ・npmプロセスはすべて閉じてから実行すること。
現在の主要スコア(v17.0)
カテゴリ スコア
──────────────────────────────
eBPF 100
io_uring 100
XDP 100
Redis 100
WASM 100
Docker 99
Podman 98
Spark 97
Cloud & AI 99
Distributed Storage 90
Monitoring 87
Container Overall 99
総合: 2879.86 / 3000 (96.0%) Grade SS 🏆🏆
次のターゲット: 残り120点
| カテゴリ | 現在 | 目標 | 難易度 |
|---|---|---|---|
| Distributed Storage | 90 | 100 | ★★★ |
| Monitoring | 87 | 100 | ★★ |
| GPU (Intel UHD 620) | 0 | - | ★★★★★ |
GPUは Intel UHD 620(Gen9.5)が OpenCL 非対応のため、
ハードウェア限界として現状では打つ手がない。
Distributed StorageとMonitoringの改善で、
2950点(98%+) が現実的な次の目標になる。
まとめ
v17.0「CONTAINER MASTER」で学んだことをまとめる。
1. 何を測定しているか常に確認する
docker run --rm でストレージを測定すると、コンテナ起動時間も含まれてしまう。
本当に測りたいのは何かを問い続ける。
2. 計算式のバグは数字が教えてくれる
「400MB書き込んだのに100MBとして計算していた」
こういうバグはスコアの伸び悩みで発見できる。
3. JVMは起動コストが高い。共有できるなら共有する
測定のたびにJVMを起動していたのは明らかな無駄だった。
4. PATHとJavaバージョンはsudo環境で必ず確認する
eBPFが0点だった原因は bpftool の場所(/usr/sbin)がPATHにいなかっただけだった。
5. ブラウザを閉じてからベンチマークを実行する
L3キャッシュの競合は実測で2倍以上の差を生む。
環境を整えることも最適化のうちだ。
初回1774点から始まったNOVE OSベンチマークは、
地道なデバッグと測定の積み重ねで96%まで来た。
v18.0では残り4%(120点)に挑む。
NOVE OS v17.0 "CONTAINER MASTER"
Rocky Linux 10.1 / Python 3.12 / Intel Core i5-8250U