Deep EVM #2:メモリモデル — スタック、メモリ、ストレージ、Calldata
Engineering Team
データが存在できる4つの場所
EVMは4つの異なるデータロケーションを提供し、それぞれコスト、ライフタイム、アクセスパターンが根本的に異なります。間違ったものを選ぶことが、スマートコントラクトで過剰なガス消費の最も一般的な原因です。
| ロケーション | ライフタイム | 読み取りコスト | 書き込みコスト | サイズ |
|---|---|---|---|---|
| スタック | 現在のオペコード | 0(DUP: 3) | 0(PUSH: 3) | 1024ワード |
| メモリ | 現在のコールコンテキスト | MLOAD: 3* | MSTORE: 3* | 拡張可能 |
| ストレージ | 永続(ブロックチェーン) | SLOAD: 2100/100 | SSTORE: 20000/2900/100 | 2^256スロット |
| Calldata | 現在のコールコンテキスト | CALLDATALOAD: 3 | 読み取り専用 | トランザクション入力 |
*メモリコストは操作あたり3ガス+二次拡張コスト。
スタック:高速だが小さい
スタックはEVMのワーキングメモリです。すべての算術、比較、論理演算がスタックから読み書きします。最大1024要素、各32バイト幅です。
実際には、DUPとSWAPオペコードが16要素までしか到達できないため、16スロット以上を使用することはまれです。スタックアクセスは基本的に無料(PUSH/DUP/SWAP操作に3ガス)です。
メモリ:安価だが一時的
メモリはバイトアドレス可能な配列で、空から始まり必要に応じて拡張されます。現在のコールコンテキストの間のみ持続します。
フリーメモリポインタ
Solidityはメモリの最初の128バイトを特別な目的に予約します:
| オフセット | 目的 |
|---|---|
| 0x00-0x3f | スクラッチスペース(ハッシュに使用) |
| 0x40-0x5f | フリーメモリポインタ |
| 0x60-0x7f | ゼロスロット |
| 0x80+ | フリーメモリ(割り当てはここから開始) |
メモリ拡張コスト
メモリが高価になるポイントがここです。ガスコストはMSTORE/MLOADあたり3ガスだけでなく、現在の最高水準を超えてメモリにアクセスすると、二次拡張コストが課されます。
公式(イエローペーパーより):
memory_cost = (memory_size_words^2 / 512) + (3 * memory_size_words)
| 使用メモリ | コスト |
|---|---|
| 32バイト(1ワード) | 3ガス |
| 1 KB(32ワード) | 98ガス |
| 10 KB(320ワード) | 1160ガス |
| 100 KB(3200ワード) | 29600ガス |
| 1 MB(32000ワード) | 2,001,000ガス |
ストレージ:永続だが高価
ストレージはEVMの永続キーバリューストアです。各コントラクトは2^256個の32バイトスロットを持つ独立したストレージスペースを持ちます。
Solidityのストレージレイアウト
contract StorageLayout {
uint256 public x; // スロット0
uint256 public y; // スロット1
address public owner; // スロット2(パック:20バイト)
bool public paused; // スロット2(パック:1バイト、同スロット!)
mapping(address => uint256) public balances; // スロット3(ベース)
}
ストレージパッキング
Solidityは可能な場合、複数の小さな変数を1つの32バイトスロットにパックします。これは重要な最適化です。
Calldata:読み取り専用で安価
Calldataはトランザクションまたはコールと共に送信される入力データです。読み取り専用で、アクセスは安価(CALLDATALOADに3ガス)です。
一時ストレージ(EIP-1153)
EIP-1153(Cancunアップグレード、2024年3月)は、TSTOREとTLOADの2つの新しいオペコードを導入しました。一時ストレージは通常のストレージと似ていますが、トランザクション終了時に自動的にクリアされます。
- TLOADとTSTOREの両方に100ガス
- トランザクション完了後にクリア
- 同一トランザクション内のコールフレーム間でアクセス可能
リエントランシーロックに最適 — TSTOREでフラグを設定、TLOADでチェック。20000ガスのSSTOREコストが不要。
実践的最適化:適切なロケーションの選択
- スタックに置けるか? スタックを使用。マージナルコストゼロ。
- 16以上の値が必要? メモリを使用。
- 読み取り専用の入力データ? Calldataを使用。32バイトの読み取りあたり3ガス。
- トランザクション間で永続化が必要? ストレージを使用。新規エントリに20000+ガスを予算。
- コールフレーム間で永続化が必要だがトランザクション間では不要? 一時ストレージを使用。100ガス。
まとめ
EVMのメモリモデルは見かけ上シンプルです — 4つのロケーションに明確なトレードオフ。しかし、コストの差は膨大です:スタックのADDは3ガス、SSTOREは20000ガス。これらのコストを直感的レベルで理解することが、ガス効率の良いコントラクトとガスを浪費するコントラクトの違いです。