ブロックチェーンMar 28, 2026
Deep EVM #7:Yulでのガス効率的なループと条件分岐
OS
Open Soft Team
Engineering Team
ループがガスの墓場である理由
ほとんどのスマートコントラクトで、最大のガス消費者はイテレーションです。100要素の配列をループする関数は、本体を100回実行します — そして本体内のすべてのオペコードが100倍されます。ループ内の不必要なSLOAD 1つで10,000〜210,000ガスのコストがかかります。
Yul Forループの解剖
for { let i := 0 } lt(i, 10) { i := add(i, 1) } {
// 本体
}
イテレーションあたりのループオーバーヘッド:
- 条件チェック:23ガス
- ポストイテレーション:6ガス
- ジャンプバック:11ガス
- 合計:イテレーションあたり40ガス
ベンチマーク結果(100要素)
| メソッド | イテレーションあたりガス | 合計(100要素) | Solidityとの節約 |
|---|---|---|---|
| Solidity(チェック付き) | ~73 | 7,300 | ベースライン |
| Solidity(unchecked) | ~53 | 5,300 | 27% |
| Yul | ~43 | 4,300 | 41% |
Switch vs If
Yulには2つの条件構文があります。正しいものを使用してください。
If(単一条件)
条件チェックに20ガス。
Switch(複数条件)
各ケースにチェック22ガス。N個のケースの場合、最悪ケースはN * 22ガス。
最適化:頻度順にケースを並べる。 最も呼び出される関数セレクタを最初にチェック。
Yulでのunchecked算術
Yulのすべての算術はデフォルトでuncheckedです。オーバーフローやアンダーフローのリバートはありません。
安全な場合:ループカウンタ、配列インデックス計算、比較後の減算。
オーバーフローチェックが必要な場合:
function safeAdd(a, b) -> c {
c := add(a, b)
if lt(c, a) { revert(0, 0) }
}
ループアンローリング
4倍アンローリングにより、要素あたりのオーバーヘッドが~13ガスに削減:
function sumUnrolled(offset, length) -> total {
let end := add(offset, mul(length, 0x20))
let endAligned := sub(end, mod(mul(length, 0x20), 0x80))
for { } lt(offset, endAligned) { offset := add(offset, 0x80) } {
total := add(total, calldataload(offset))
total := add(total, calldataload(add(offset, 0x20)))
total := add(total, calldataload(add(offset, 0x40)))
total := add(total, calldataload(add(offset, 0x60)))
}
for { } lt(offset, end) { offset := add(offset, 0x20) } {
total := add(total, calldataload(offset))
}
}
ループ内のスタック深度の最小化
ループ本体を浅く保つ。ヘルパー関数を使用してスタック深度を削減。
早期終了パターン
Yulのleave文は関数の即座の終了 — 検索ループの早期終了に使用。
ビット操作によるループ代替
ループをビット演算で置き換えられる場合があります(ポピュレーションカウントなど)。
まとめ
Yulでのループ最適化は巧妙なトリックではなく、ループ本体内のすべてのオペコードの正確なガスコストを理解し、無駄を体系的に排除することです。ストレージ読み取りをキャッシュし、タイトなループをアンロールし、早期終了し、イテレーションよりビット演算を優先してください。