ガスを節約するためにコード スキルを向上させる Solidity のヒントをいくつか目にしたことがあるかもしれませんが、今日は、Ethereum 仮想マシンを理解することで、スマート コントラクトのガス コストを効果的に節約できる方法に焦点を当てたいと思います。
これからイーサリアムについて詳しく説明するので、オペコードのガスコストを規定するイエローペーパーの一部をここに残しておきます。記事の中ではそれを参照します。
ヒント 1: コールド アクセス VS ウォーム アクセス
コールドロード: 2100ガス
グウォームアクセス: 100 ガス
最初の OPCODE があります。最初の OPCODE は、変数に初めてアクセスするとき (コールド アクセス) にかかるコストを指定し、2 番目の OPCODE は、変数に 2 回目以降 (ウォーム アクセス) にアクセスするときにかかるコストを指定します。ご覧のとおり、価格の差はかなり大きいため、これを理解することで、スマート コントラクトのトランザクションのコストに大きな違いが生じる可能性があります。例を見てみましょう。
Solidity の関数内でデータをキャッシュすると、より多くのコード行が必要な場合でも、ガス使用量を削減できます。この場合、配列の場所を切り替えて、ストレージから使用してループのたびにコールド アクセスする代わりに、アクセスコストが低いメモリに配列を保存します。
ヒント #2: ゼロ値と非ゼロ値およびガス払い戻し
Gsset = 20,000ガス
Rsclear = {実行価格の割引}
Ethereumブロックチェーン上で値を0から非ゼロに変更することは、Gssetの価格からもわかるようにコストがかかりますが、値を非ゼロから0に変更すると、オペコードRsclearに従ってガス値の払い戻しを受けることができます。払い戻しを利用しないために、最大で総取引コストの20%までしか払い戻されないことが確立されています。
このようなシナリオは、スマート コントラクトのアドレス残高を更新するという、ブロックチェーン上の非常に一般的なシナリオで見つけることができます。それぞれの例を見てみましょう。
最初の例のZeroToNonZero契約では、非ゼロから非ゼロ(5,000ガス*)+ゼロから非ゼロ(20,000ガス)= 25,000ガス
2 番目の例 NonZeroToZero 契約では、非ゼロからゼロ (5,000 ガス*) + ゼロから非ゼロ (20,000 ガス) — 払い戻し (4,800 ガス) = 21,200 ガス
*2,100 (Gcolssload) + 2,900 (Gsreset) = 5,000ガス
ヒント3: 状態変数の順序は重要
ストレージは、Solidity スマート コントラクトの状態変数値を保持するキー値データ構造のようなものです。
ストレージを配列として考えると、これを視覚化するのに役立ちます。このストレージ「配列」内の各スペースはスロットと呼ばれ、32 バイト (256 ビット) のデータを保持します。スマート コントラクトで宣言された各状態変数は、宣言位置とタイプに応じてスロットを占有します。
すべてのデータ型が各スロットの 32 バイトすべてを使用するわけではありません。それより少ないバイト数を使用するデータ型 (bool、uint8、アドレスなど) もあります。
ここでのポイントは、2 つ、3 つ、またはそれ以上の変数を合わせても 32 バイト以下である場合、Solidity のコンパイラはそれらを 1 つのスロットにまとめようとしますが、これらの変数は互いに隣接して定義する必要があることです。
ここでは、データ型 bool (1 バイト)、address (20 バイト)、uint256 (32 バイト) を使用しています。したがって、これらの変数のサイズがわかれば、TwoSlots コントラクトの最初の例では、bool と address が一緒にあるため (1 + 20 = 21 バイト、32 バイト未満)、1 つのスロットを占有することが簡単にわかります。ThreeSlots コントラクトでは、bool と uint256 は同じスロットに存在できないため (1 + 32 = 33 バイト、スロット容量より大きい)、合計で 3 つのスロットを使用します。
さて、なぜこれがそれほど重要なのでしょうか?
SLOAD オペコードは 2100 ガスかかり、ストレージ スロットからの読み取りに使用されるため、変数をより少ないスロットに保存できれば、ガスをいくらか節約できます。
ヒント #4: uint256 は uint8 よりも安価です
ヒント#3で、uint256 (256 ビット = 32 バイト) はそれ自体でスロットを占有し、uint8 は 32 バイト未満であることも学びました。では、8 ビットが 256 ビットより小さいのは明らかなのに、なぜ uint256 の方が安いのでしょうか?
これを理解するには、変数がスロット全体を埋めず、このスロットが他の変数によって埋められていない場合、EVM は残りのビットの残りを「0」で埋めて操作できるようにするということを知っておくことが重要です。
EVM によって実行されるこの「0」の追加にはガスがかかります。つまり、トランザクション ガスを節約するには、uint8 ではなく uint256 を使用する方が適切です。
__________________
スマート コントラクトのガス コストを削減するためのこれらのヒントを知ることで、EVM の仕組みについても少し理解していただけたと思います。
__________________
Twitter @TheBlockChainer では、スマート コントラクト、Web3 セキュリティ、Solidity、スマート コントラクトの監査などに関する毎日の最新情報をご覧いただけます。
__________________