0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

自作OSベンチマークをGrade SSまで引き上げた話 — NOVE OS v17.0「CONTAINER MASTER」全解説

0
Posted at

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の測定には bpftoolip 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

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?