皆さまこんにちは。初見の方は、初めまして。普段はマネジメントや別事業をしつつ、インフラからアプリまで何でもこなす現場のIT何でも屋です。
前回の記事(【Hike軽速化:序章】新言語「Hike」をシステムに採用したらバイナリ99.8%削減・ネットワークも実働処理も爆速になった話)では、予想以上の大反響と温かいコメントをいただき、本当にありがとうございました!(執筆時点 2026/09/19 で27,000PVを超え、多くの反響をいただき大変励みになっております)
本シリーズは、新言語「Hike」の圧倒的なフットプリント削減能力と低レイヤ制御力を活かして、Webフロントエンドの重量級処理を 「軽量化」かつ「高速化」する【Hike軽速化(けいそくか)】 をテーマにした実践記録です。今回はその第1弾として「バーコード・QRコード」をテーマにお届けします。
はじめに
前回の記事でも触れていた通り、現場のWeb業務システムにおいて、重厚なJSライブラリに依存しがちなフロントエンド機能の筆頭が 「QRコード処理」 でした。
社内システムやWebアプリでQRコードを生成する際、世界中のエンジニアに最も広く使われているのが、Kazuhiko Arase氏作の純JavaScriptエンジン qrcode-generator、およびそれをブラウザ描画用にラップした定番ライブラリ qrcode.js(またはその派生版)です。
非常に堅牢で素晴らしいライブラリですが、ファイルサイズは約55KB(バンドラ環境や派生版ではそれ以上)あり、大量のQRコードを一括生成する帳票出力やリアルタイムプレビューでは、JS特有のシングルスレッド負荷やGC(ガベージコレクション)によるカクつきが課題になる場面がありました。
そこで今回、新言語「Hike」を用いて、国際規格(ISO/IEC 18004)に完全準拠した超軽量・超高速のQRコード生成エンジン(gen_qr)を外部依存ゼロでフルスクラッチ実装しました。本記事はその設計、Hikeの新機能による最適化、およびベンチマークの検証記録です。
今回の成果ハイライト
- ファイルサイズ: 従来の約55.0KBから、約17.5KB へ約68%削減(Gzip転送時: 約6.5KB)
-
純粋アルゴリズム速度:
qrcode-generator比で約600倍〜1,280倍 高速化(1回あたり 2.50 µs、1秒間に約40万回生成) - 描画込みトータル速度: Wasm-JS境界(Interop)とCanvas/SVG描画を含めても約15〜35µsで完了(JS比 50〜100倍高速)
- 規格完全準拠: Version 1〜40完全対応、全4モード(数字・英数・8bit Byte・漢字)、全4誤り訂正レベル(L/M/Q/H)、8パターンの自動ペナルティマスク評価をフル内装(Micro QR / rMQRにも対応)
- 運用安全性: 既存コード修正0行のドロップイン互換 + 失敗時のみ元JSをオンデマンド取得する動的フォールバック
従来実装(qrcode.js)との比較
まずは、従来の標準純JS実装(qrcode-generator / qrcode.js)と、今回Hikeで実装したQR生成特化ターゲット(gen_qr)の詳細な比較です。
| 比較項目 | 従来リファレンスqrcode-generator (純JS) |
HIKE WASM QR生成特化エンジン ( gen_qr) |
|---|---|---|
| 役割・スコープ | QRコード生成専用 | QRコード生成専用(MicroQR/rMQR対応) |
| 対応規格・バージョン | ISO/IEC 18004 準拠 (Version 1 〜 40) |
ISO/IEC 18004 準拠 (Version 1 〜 40) |
| エンコードモード | 数字 / 英数 / 8-Bit Byte / 漢字 (全4種) | 数字 / 英数 / 8-Bit Byte / 漢字 (全4種) |
| 誤り訂正レベル | Level L / M / Q / H (全4種) | Level L / M / Q / H (全4種) |
| マスク評価 | 8パターン自動ペナルティ計算 | 8パターン自動ペナルティ計算 |
| 多重アライメント / BCH | 規格準拠 | 規格準拠 (多重アライメント / BCH(18,6)) |
| 外部依存 (Wasm Imports) | JSランタイム依存 | 0件 (完全自己完結・ブラウザAPI非依存) |
| ファイルサイズ (Raw) | 約 55.0 KB | 約 17.5 KB (約 68% 削減) |
| Gzip 転送サイズ (概算) | 約 16.5 KB | 約 6.5 KB (約 60% 削減) |
| コア生成処理速度 (V3: URL) | 1,500 〜 3,200 µs (1.5〜3.2 ms) | 2.50 µs (約 600 〜 1,280 倍 高速) |
| 秒間生成スループット | 約 600 ops/sec | 約 399,470 ops/sec (約40万回/秒) |
| 静的メモリ使用量 (Wasm Linear Memory) | 対象外 (全JSヒープ管理) | 約 64 〜 128 KB (静的固定バッファ 1〜2 Pages) |
| 生成ごとの動的メモリ確保 | 約 12 〜 28 KB (配列・オブジェクト) | 0 バイト (完全ゼロアロケーション) |
| ランタイム実行時メモリ | 約 8.5 〜 15.0 MB (V8ヒープ全体) | 約 1.2 〜 2.0 MB (Wasmインスタンス基盤のみ) |
| ガベージコレクション (GC) | 頻繁にマイナーGCが発生 (カクつき要因) | GC 発生回数 0 回 (Wasm内で完結) |
💡 補足:なぜ前回の「序章」からバイナリサイズが増加したのか?
前回の序章でお見せしたプロトタイプ(バイナリ単体で約3.1KB)は、あくまで「必要最小限のコア機能(バイナリ生成のみ)」に絞った簡易実装でした。
しかし今回の gen_qr では、実業務でそのままドロップイン置換できるよう、機能をフル装備しています。
プロダクション向けの堅牢性をすべて網羅した結果、トータルサイズは約17.5KBとなっていますが、それでも従来の純JS版(約55KB)から約68%の軽量化を達成しています。
(※なお、QR Model 2生成に極限まで特化した gen_qr_lite 構成であれば 約13.0KB までさらにスリム化可能です)
[NOTE]
※ 処理速度に関する注記:
上記テーブルの数値(2.50 µs / 約40万回/秒)は、QRコードのビット列構築・Reed-Solomon誤り訂正・マスク評価を完了するまでの 「コア・アルゴリズム純粋生成時間」 です。
実際のブラウザ環境で画面に表示する際は、Wasmからマトリクス配列を受け取る 「境界(JS-Interop)オーバーヘッド」 や、それをDOMやCanvasコンテキストへ描き出す 「描画コスト」 が別途上乗せされます(※これらを含めたトータルの実測内訳は後述のベンチマーク章にて詳しく検証しています)。
性能ベンチマーク対比(Ryzen 5 3500U / Node.js & V8 実測)
実機環境(AMD Ryzen 5 3500U / Windows 11 / Node.js v22.18 / WebAssembly MVP)にて、5,000回の反復生成ベンチマークを実施しました。
1. 純粋アルゴリズム生成速度(Encoding Benchmark)
| コード規格 | 入力データ例 | HIKE WASM (gen_qr) |
従来の qrcode-generator
|
速度優位比 (倍率) |
|---|---|---|---|---|
| QR Code (V1, 10文字) | "1234567890" |
1.95 µs | 1,250 µs | 約 640 倍 |
| QR Code (V3, URL) | "https://qiita.com" |
2.50 µs | 1,850 µs | 約 740 倍 |
| QR Code (V10, 210文字) | 長文テキスト (英数混在) | 8.40 µs | 5,200 µs | 約 619 倍 |
【純粋生成スループット比較 (ops/sec)】
HIKE gen_qr : [███████████████████████████████ ] 399,470 ops/sec
qrcode-generator : [ ] 600 ops/sec
従来のJavaScriptでは、文字列のパース、ビットストリーム構築、リードソロモン符号計算、マトリクス配置、そして8パターンのマスク評価によるペナルティ計算のたびに配列オブジェクトやクロージャが生成され、GCとインタープリタのオーバーヘッドが発生していました。
HikeによるWasm実装では、スタックおよび静的リニアメモリ上でビット演算と多項式演算をダイレクトに行うため、毎秒40万回という極限のスループットを叩き出しています。
2. 「Wasm境界(JS-Interop)とDOM/Canvas描画」を含めた実測コスト
実務でWebフロントエンドを組む際、鋭いエンジニアが気になるのは 「Wasmがいくら2.5µsで終わっても、Wasmからマトリクス配列をJSへ受け渡す境界(JS-Interop)コストや、Canvas/DOMに描き出す描画コストで相殺されるのではないか?」 という点です。
そこで、画面描画まで含めたパイプライン全体の実測内訳を計測しました:
| パイプライン処理ステージ | 従来の qrcode.js (純JS) |
HIKE WASM 構成 | 備考・最適化のポイント |
|---|---|---|---|
| ① QR行列計算(アルゴリズム) | 1,850 µs | 2.50 µs | Reed-Solomon多項式・マスク演算のWasm化 |
| ② Wasm $\rightarrow$ JS 境界データ転送 | 0 µs (JS同一メモリ) | 0.80 µs |
Uint8Array 共有メモリの直接参照 |
| ③ Canvas / SVG パス描画処理 | 350 〜 600 µs | 20 〜 30 µs | 単一 Path2D / ビットマップ一括転送 |
| 合計所要時間 (1フレーム描画) | 約 2,200 〜 2,500 µs | 約 23 〜 33 µs | 総合で約 70 〜 100 倍 高速化 |
💡 境界コストと描画を極小化する工夫
-
ゼロコピー共有ビュー: Wasmのリニアメモリ(
wasm.memory.buffer)をUint8Arrayでラップしてポインタ参照するだけで、JavaScript側への配列コピーを完全ゼロ(0バイト)に抑えています。 -
描画パイプラインの最適化: 従来のJSライブラリで頻見される「1モジュールごとにCanvasの
fillRectを呼ぶ」愚直な実装を廃止し、行ごとのビットマップ一括転送(putImageData)またはPath2Dの一括バッチ描画を採用することで、描画側のボトルネックも徹底的に排除しました。
なぜ軽速化しつつ「規格完全準拠」を実現できたのか?
1. 新機能によるコードの抽象化と保守性の向上
開発過程で直面した低レイヤアルゴリズムの記述課題をもとに、Hike言語の開発者であるkanryu氏へGitHub Issue上で機能提案を行いました。
これらを言語側で迅速にサポートしていただいたことで、バイナリサイズを最小限に保ったまま、クリーンで安全なコードベースを構築できました。
-
静的テーブルの要素数自動推論 (
[...]T):
QRコードの各バージョンで異なる誤り訂正ブロック数、RS係数、アライメントパターン座標などの静的ルックアップテーブルを定義する際、要素数を明示せずともコンパイラが自動補完。冗長な記述を排除しつつタイポや定義ズレを防止。 -
Const Generics を活用した行列展開 (
std/collections/matrix):
Version 1(21×21)からVersion 40(177×177)まで変動するグリッドの配列操作を、ヒープ動的確保を行わない固定長マトリクスとして型安全にカプセル化。 -
インラインアセンブラ (
__asm__) によるWasm命令の直接記述:
Reed-Solomon符号のガロア体($GF(2^8)$)多項式乗算・除算など、ホットループとなる箇所をインラインアセンブラで記述。抽象化の余分なオーバーヘッドを徹底排除。
2. 規格準拠のアルゴリズムを最小ステップで内装
- 全モード最適エンコード: 数字、英数字、8ビットバイト、漢字(Shift_JIS)の全モードに対応。入力データ長とモード指示子を自動選択。
- Reed-Solomon誤り訂正の最適化: ガロア体($GF(2^8)$)上の対数・真数テーブル(Log/Antilog Table)をインライン展開し、ブロック分割とインターリーブ処理をゼロコピーで実行。
- 8パターンペナルティの並列評価: マスクパターン(Pattern 0〜7)のペナルティ評価(同色連続、2×2同色ブロック、1:1:3:1:1比率パターン、明暗比率)を効率的なビットマスク走査で一括計算。
3. ランタイム不要のフットプリント(Go等のWasmではダメな理由は?)
「Wasmで高速化するなら、Go言語なども選択肢に入るのでは?」と思われるかもしれません。しかし、Goの標準Wasmコンパイル(GOOS=js GOARCH=wasm)は、ガベージコレクタ(GC)やゴルーチンスケジューラといった言語ランタイムを丸ごとバイナリに抱え込むため、 最小構成でも数MB(2MB〜10MB以上) に膨れ上がってしまいます(※TinyGoを用いても不要なランタイム機構を完全ゼロにするのは困難です)。
元々55KB程度のJSライブラリを置き換える用途において、数MBのWasmをブラウザにダウンロードさせるのは本末転倒です。
HikeはCやRustと同様に 不要なランタイムを持たないゼロオーバーヘッドの言語 であるため、余計なフットプリントを一切含まず、わずか約17.5KBの極小サイズで完結させることができました。
既存コードの変更ゼロで置き換える(Drop-in 互換)
社内レガシーシステムや既存のWeb画面を移行する際、最も重要なのは「既存コードに手を加えないこと」です。
本エンジン用の生成互換ラッパー(hike_qr_gen.js)は、既存の qrcode.js(および QRCode)のグローバル関数・プロトタイプを完全エミュレートします。
① スクリプトの差し替え
HTMLの <script> タグを差し替えるだけです。
<!-- 従来の記述 (約 55 KB): 削除 -->
<!-- <script src="js/qrcode.js"></script> -->
<!-- 新しい記述 (生成特化ラッパー + Wasm 約 17.5 KB): 1行追加 -->
<script src="js/hike_qr_gen.js"></script>
② 既存のJavaScriptコードは無修正でそのまま動作
// qrcode.js (Kazuhiko Arase氏 互換インターフェース)
const qr = qrcode(0, 'M');
qr.addData('https://hike-lang.org');
qr.make();
// HTMLタグの出力 (Table, Img, SVG等に対応)
document.getElementById('qr-container').innerHTML = qr.createImgTag(4, 4);
// QRCode (davidshimjs 互換インターフェース)
new QRCode(document.getElementById("qrcode"), {
text: "https://hike-lang.org",
width: 128,
height: 128,
colorDark : "#000000",
colorLight : "#ffffff",
correctLevel : QRCode.CorrectLevel.H
});
Node.jsやWebpack/Vite環境でも、そのまま require または import して使用可能です。
const { qrcode } = require('./hike_qr_gen.js');
「新言語のWasm化」に伴うリスク対策:動的フォールバック
新興言語であるHikeでコンパイルされたWasmを本番環境へ投入するにあたり、現場のマネジメント視点として最も警戒すべきは「万が一Wasmのロードや実行に失敗した場合のシステム停止リスク」です。
そこで、ラッパー層にオンデマンド動的フォールバック機構を組み込みました。
- 通常時は軽量な
gen_qr.wasm(約17.5KB)のみをロードして超高速処理。 - ブラウザのWasm未対応環境や、ネットワーク切断によるWasmフェッチ失敗、未知の例外が発生した瞬間にのみ、従来の
qrcode.jsを動的インポート(Dynamic Import)してシームレスに処理を肩代わり。 - フォールバック用のJSコードを最初から抱え込んで初期バンドルを肥大化させることなく、実質的なクラッシュリスクをゼロに抑えています。
「極小・爆速」が真価を発揮する具体的な現場ユースケース
今回のHike製Wasm版QR生成エンジン(gen_qr)が、従来のJSライブラリに代わって圧倒的なアドバンテージを発揮するのは、以下のような過酷なフロントエンド環境です。
1. ブラウザ側での「リアルタイム・大量一括生成」UI
- 想定現場: イベントの受付システム、大量のクーポン発行画面、在庫管理・出荷ラベルのリアルタイムプレビュー画面など
- メリット: 1つの画面上に数十〜数百個以上のQRコードを同時に動的表示・再描画するシーンにおいて、ユーザーのタイピングや操作のたびに一瞬(マイクロ秒)で再計算されるため、UIメインスレッドの負荷やGCによるカクつきを完全に排除できます。
2. エッジ(オフライン・低スペック端末)でのスタンドアロン動作
- 想定現場: 通信環境が不安定な倉庫・工場現場、安価なタブレット、古いハンディターミナルなど
- メリット: サーバーに頼らずローカル完結で高速処理しなければならない環境において、約17.5KBという極小バイナリにより初期ロードを極限まで削り、GC(ガベージコレクション)による画面停止のない安定したパフォーマンスを維持できます。
3. クライアントサイドでの動的ドキュメント(PDF・帳票等)生成
- 想定現場: ブラウザ上でユーザーの入力データをもとに、数ページ〜数十ページの請求書、チケット、証明書などのPDFをその場で構築・ダウンロードさせるシステム
- メリット: 複数の高品質なQRコードを埋め込む際の処理ボトルネックを解消し、ユーザーを待たせることなく一瞬でファイル出力を完了できます。
💡 TIP
通常のWebアプリで「たまに1個QRコードを表示するだけ」であれば既存のJSライブラリでも十分ですが、「数・頻度・速度」のハードルが極端に高いリアルタイム処理や、「サイズ・リソース」の制約が厳しいエッジ環境においては、HikeによるWasm化が非常に強力な武器となります。
今回のまとめ
qrcode.js を新言語 Hike の Wasm で再構築した結果:
- サイズ削減: 55KB $\rightarrow$ 約17.5KB(約68%削減 / Gzip時 約6.5KB)
- 速度向上: 1.5ms $\rightarrow$ 2.5µs(約600〜1,280倍高速、秒間40万回生成)
- 完全互換: 既存JSコードの修正0行で安全に移行可能
という、フロントエンドの極限最適化を達成できました。
なお、今回開発したモジュールは自社業務システム向けに設計・構築した成果物であるため、コード全体をOSS公開することはしていませんが、設計思想やベンチマークの知見をこうして共有させていただきました。
次回予告:次なる挑戦「カメラ読取」へ
QRコードの生成をわずか約17.5KB・秒間40万回の超高速で実現し、フロントエンドの生成ボトルネックは無事に解消されました。
しかし、現場のWeb業務システムには、まだ巨大なフロントエンド要件が存在します。
「カメラを用いたリアルタイムQRコード読み取り(スキャナー)」 です。
定番ライブラリである html5-qrcode は非常に便利ですが、数十フレーム/秒の映像処理に伴うガベージコレクション(GC)負荷や端末の発熱など、Webフロントエンド特有の課題を抱えています。
生成に続き、カメラ読取もWasmで極小化・ゼロGC化できるのか?
次回、『【Hike軽速化:バーコード_2】新言語「Hike」で定番 html5-qrcode をWasm化したら、~~した話(仮)』。
お楽しみに!
Hike軽速化 シリーズ一覧・まとめ
本シリーズの過去記事や今後の続編一覧は、以下のストックリストで一括管理しています。ぜひご活用ください!
👉 Hikeで軽速化シリーズ(Qiitaストック)
- 前回:【Hike軽速化:序章】新言語「Hike」をシステムに採用したらバイナリ99.8%削減・ネットワークも実働処理も爆速になった話
- 今回:【Hike軽速化:バーコード_1】新言語「Hike」で定番 qrcode.js をWasm化したら、サイズ68%削減・秒間40万回生成になった話
- 次回:『【Hike軽速化:バーコード_2】新言語「Hike」で定番 html5-qrcode をWasm化したら、~~した話(仮)』近日公開予定
番外編のお知らせ:Wasm難読化と開発秘話について
本編の裏側で直面した「Wasm難読化とクラッシュ問題」の解決過程や、Hike開発者kanryuさんとの即日実装の裏側について、単発の特別編として別記事にまとめました。ぜひご覧ください!
番外編: 【Hike言語】Wasm難読化の自作から初OSS、そして言語開発者の神対応で「クラッシュ防止機能」が即日実装された話
謝辞
Hike 言語の開発者である kanryu さんの温かいご支援、迅速かつ真摯な開発姿勢に心より深く感謝申し上げます。
今回のQRコード生成エンジン開発にあたり、低レイヤの最適化や記述性に関する機能要望を GitHub Issue で提案させていただいたところ、なんと提案からわずか4時間余りという驚異的なスピードで言語本体への機能実装とテストを完了していただきました。
個人の新興言語プロジェクトでありながら、現場の実務ニーズやフィードバックをこれほどまでの熱量と超人的なスピード感で即座に取り込んで進化させていく姿勢には、同じエンジニアとして深い感銘と敬意を抱かずにはいられません。
新興言語が目の前で急速に進化していく熱気とダイナミズムをリアルタイムに体験できたことは、エンジニア冥利に尽きる最高にエキサイティングな経験でした。この場を借りて、改めて最大の感謝をお伝えいたします。
なお、本記事の構成案の壁打ちやベンチマークデータの整理には生成AI(Gemini)を活用しています。
軽量なWasmバイナリを突き詰めたい方や、低レイヤのフロントエンド最適化に興味がある方は、ぜひ Hike を触ってみてください!