あらすじ
以前、Rust で漫画ビューアアプリ「RustMangaReader」を作った時、
読み込む速度が一番重要視しているので、どの言語が一番早いか試しました。
漫画ビューアの方に興味ある方はこちら
せっかくなので、その時のベンチマークをシェアしようと思います。
言語前提する時の条件
ですが、後で AI 機能拡張などするならこれでもいいかなーと
- C++ 多分一番早いですが、書きにくすぎる
- Python ものすごい書きやすいし、今の仕事でずっと使っていますが、遅い。。。
- Javascript 他の OS に展開しやすいのが魅力。Native のライブラリーも多いので、遅くないでは?と期待しました。
- Rust これが前からずっと気になって勉強してみたいと思っていましたが、触ったことがないので本当に早いか?と疑問が残っている
なので、各言語を簡単なプログラムを書いて、速度計ることにしました。
C++ は面倒そうなので、書いてなかった。。。すみませんでした。
準備
作ろうとした漫画ビューアアプリの主な機能は ZIP ファイルから開封せず画像直接読み込むなので、それをプログラムにしました。
画像がいっぱい入っている ZIP ファイルが必要になるので、用意した。
ChatGPT に適当な高解像度画像(4k)を生成して 10 枚複製して、ZIP に入れました。

画像1枚のサイズは 7.17MB で ZIP ファイルは 71.7MB です。
マシンスペック
ベンチマークするマシンのスペックはこんな感じ
CPU : AMD Ryzen 9 7900
RAM : G.Skill F56000J3036G 32G DDR5x2
NVME : SAMSUNG 990 Pro 4TB
各言語のコード
Rust
use std::env;
use std::fs::File;
use std::io::{Read, Seek};
use std::time::Instant;
fn parse_arg(args: &[String], key: &str, default: &str) -> String {
args.iter()
.position(|a| a == key)
.and_then(|i| args.get(i + 1))
.cloned()
.unwrap_or_else(|| default.to_string())
}
fn main() -> anyhow::Result<()> {
let zip_path = "./benchmark_images.zip";
let iters: usize = 10;
// Warmup + timed iterations
let mut best_ms = f64::INFINITY;
let mut last_stats = (0usize, 0u64);
for iter in 0..iters {
let file = File::open(&zip_path)?;
let mut archive = zip::ZipArchive::new(file)?;
let start = Instant::now();
let mut count = 0usize;
let mut total_bytes: u64 = 0;
// Iterate entries in zip
for i in 0..archive.len() {
let mut f = archive.by_index(i)?;
let name = f.name().to_string();
let mut buf = Vec::with_capacity(f.size() as usize);
f.read_to_end(&mut buf)?;
total_bytes += buf.len() as u64;
// Decode to pixels (forces actual image parsing)
let img = image::load_from_memory(&buf)?;
// Force pixel materialization
let _rgba = img.to_rgba8();
count += 1;
}
let elapsed = start.elapsed().as_secs_f64() * 1000.0;
last_stats = (count, total_bytes);
// skip first run as warmup-ish if you want, but we’ll just keep best
if elapsed < best_ms {
best_ms = elapsed;
}
eprintln!("iter {}: {:.2} ms", iter + 1, elapsed);
}
let (count, total_bytes) = last_stats;
let secs = best_ms / 1000.0;
let mb = total_bytes as f64 / (1024.0 * 1024.0);
println!("zip: {}", zip_path);
println!("images: {}", count);
println!("bytes read: {} ({:.2} MiB)", total_bytes, mb);
println!("best time: {:.2} ms", best_ms);
if secs > 0.0 {
println!("throughput: {:.2} images/s", count as f64 / secs);
println!("throughput: {:.2} MiB/s", mb / secs);
}
Ok(())
}Python
import time
import zipfile
from io import BytesIO
from PIL import Image
def run_once(zip_path: str) -> tuple[float, int, int]:
t0 = time.perf_counter()
count = 0
total_bytes = 0
with zipfile.ZipFile(zip_path, "r") as zf:
for info in zf.infolist():
data = zf.read(info) # read entry into memory (no extracting)
total_bytes += len(data)
# Decode image fully (force pixel load)
with Image.open(BytesIO(data)) as im:
im.load()
count += 1
t1 = time.perf_counter()
return (t1 - t0), count, total_bytes
best = float("inf")
last = (0, 0)
for i in range(10):
sec, count, total_bytes = run_once("benchmark_images.zip")
last = (count, total_bytes)
best = min(best, sec)
print(f"iter {i+1}: {sec*1000:.2f} ms")
count, total_bytes = last
mib = total_bytes / (1024 * 1024)
print(f"zip: benchmark_images.zip")
print(f"images: {count}")
print(f"bytes read: {total_bytes} ({mib:.2f} MiB)")
print(f"best time: {best*1000:.2f} ms")
print(f"throughput: {count/best:.2f} images/s")
print(f"throughput: {mib/best:.2f} MiB/s")Javascript (Node.js)
import fs from "node:fs";
import yauzl from "yauzl";
import sharp from "sharp";
function hrNowSec() {
return Number(process.hrtime.bigint()) / 1e9;
}
async function readEntryToBuffer(zipFile, entry) {
return new Promise((resolve, reject) => {
zipFile.openReadStream(entry, (err, stream) => {
if (err) return reject(err);
const chunks = [];
let total = 0;
stream.on("data", (c) => { chunks.push(c); total += c.length; });
stream.on("end", () => resolve({ buf: Buffer.concat(chunks, total), bytes: total }));
stream.on("error", reject);
});
});
}
async function runOnce(zipPath, mode) {
const t0 = hrNowSec();
let count = 0;
let totalBytes = 0;
const zipFile = await new Promise((resolve, reject) => {
yauzl.open(zipPath, { lazyEntries: true }, (err, zf) => {
if (err) return reject(err);
resolve(zf);
});
});
const done = new Promise((resolve, reject) => {
zipFile.readEntry();
zipFile.on("entry", async (entry) => {
const { buf, bytes } = await readEntryToBuffer(zipFile, entry);
totalBytes += bytes;
// Force actual decode to pixels
// raw().toBuffer() makes sharp decode the image data
await sharp(buf).raw().toBuffer();
count += 1;
zipFile.readEntry();
});
zipFile.on("end", () => resolve());
zipFile.on("error", reject);
});
await done;
zipFile.close();
const t1 = hrNowSec();
return { sec: (t1 - t0), count, totalBytes };
}
async function main() {
const zip = "benchmark_images.zip"
const iters = 10;
let best = Number.POSITIVE_INFINITY;
let last = { count: 0, totalBytes: 0 };
for (let i = 0; i < iters; i++) {
const r = await runOnce(zip);
last = r;
best = Math.min(best, r.sec);
console.log(`iter ${i + 1}: ${(r.sec * 1000).toFixed(2)} ms`);
}
const mib = last.totalBytes / (1024 * 1024);
console.log(`zip: ${zip}`);
console.log(`images: ${last.count}`);
console.log(`bytes read: ${last.totalBytes} (${mib.toFixed(2)} MiB)`);
console.log(`best time: ${(best * 1000).toFixed(2)} ms`);
if (best > 0) {
console.log(`throughput: ${(last.count / best).toFixed(2)} images/s`);
console.log(`throughput: ${(mib / best).toFixed(2)} MiB/s`);
}
}ベンチマーク結果
| - | Rust | Python | Javascript |
|---|---|---|---|
| iter 1 | 557.73 ms | 1056.06 ms | 811.30 ms |
| iter 2 | 556.50 ms | 1034.25 ms | 819.17 ms |
| iter 3 | 555.70 ms | 1036.28 ms | 803.66 ms |
| iter 4 | 556.82 ms | 1034.54 ms | 813.86 ms |
| iter 5 | 555.07 ms | 1035.30 ms | 816.00 ms |
| iter 6 | 557.37 ms | 1036.74 ms | 793.65 ms |
| iter 7 | 556.83 ms | 1033.83 ms | 792.46 ms |
| iter 8 | 555.69 ms | 1035.08 ms | 774.49 ms |
| iter 9 | 564.42 ms | 1035.40 ms | 771.28 ms |
| iter 10 | 554.54 ms | 1034.46 ms | 734.43 ms |
| bytes read | 75252870 (71.77 MiB) | 75252870 (71.77 MiB) | 75252870 (71.77 MiB) |
| best time | 554.54 ms | 1033.83 ms | 734.43 ms |
| throughput | 18.03 images/s | 9.67 images/s | 13.62 images/s |
| throughput | 129.42 MiB/s | 69.42 MiB/s | 97.72 MiB/s |
やっぱ想定通りに、Rust が一番早かった。
と思ってましたが、Javascript もぶっちゃけ悪くないですね。
そして、Python ですが、 AI 翻訳など追加する場合、別途で API 作るかなーと思います。
さすがに Python でこのタスクは遅すぎますね。
なかなか面白くない結果ですが、ご参考になれば嬉しいです。

コメント