メインコンテンツへスキップ
ブロックチェーン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(チェック付き)~737,300ベースライン
Solidity(unchecked)~535,30027%
Yul~434,30041%

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でのループ最適化は巧妙なトリックではなく、ループ本体内のすべてのオペコードの正確なガスコストを理解し、無駄を体系的に排除することです。ストレージ読み取りをキャッシュし、タイトなループをアンロールし、早期終了し、イテレーションよりビット演算を優先してください。