Ethereumスマートコントラクトのガス節約

Ethereumでは、各トランザクションを完了するために、特定のエンティティである一定量のガスが必要です。 コストを削減するにはさまざまな方法があります。 それらのいくつかはすでに実装されています。 まず、スマートコントラクトを作成するコストを最適化する問題について説明します。







ユニークな契約のオーバーヘッド







ご覧のとおり、ガス消費量を大幅に削減し、コストを削減できます。 詳細に入る前に、プログラムを最適化する方法について説明しましょう。







オプティマイザーはどのように機能しますか?



次の簡単なCプログラムを見てみましょう。







最適化プログラムの初期バージョン







最適化せずにコンパイルされたプログラムは、実行に時間がかかります。 プログラムの最適化されたバージョンを実行すると、すぐに実行されます。 その理由は、コンパイラはmain()関数の変数xが後続のコードのどこでも使用されていないことを検出するため、 calculate()関数の呼び出しを完全に省略することができるためです。 最適化の結果は次のとおりです。







プログラムの最適化されたバージョン







main()関数の戻り値を次のように変更しましょう。







このプログラムでは自動最適化はできません。







これで、コンパイラーは最適化を支援できなくなります。 残っているのは手動の最適化だけです。







手動最適化



実際のプログラムには改善の余地があり、改善の余地があるため、手動最適化は創造的なプロセスです。 一方、プロファイラーを使用して、プログラムのボトルネックを見つけることができます。 そして、問題がローカライズされると、最適化、より効率的なアルゴリズムなど、多くのアプローチのいずれかを使用できます。







前の例のcalculate()関数を詳しく見てみましょう。 内部ループの各反復で、変数rは0から1に、またはその逆に変化します。 初期値は0なので、反復回数が偶数かどうかを知る必要があります。 パラメーターaまたはbの少なくとも1つ偶数の場合、偶数回の反復があるため、戻り値は0になります。したがって、次の最適化バージョンのcompute()関数を取得します。







手動で最適化された関数計算()







危険な最適化



最適化は、プログラムを高速化し、メモリ消費やI / O操作の回数などを削減するため、優れています。 同時に、最適化はパフォーマンスを低下させたり、危険な場合もあります。







問題の1つは、さまざまな環境とパラメーターに関連しています。 たとえば、プログラムはクラスターで実行するように最適化されており、別のコンピューターで使用しています。 または、プログラムの速度が最適化され、すべての入力ファイルがRAMに完全にロードされました。 最も可能性が高いのは、大きすぎるファイルのメモリ不足の問題です。







最適化がセキュリティの問題につながる場合があります。 (ここでは、SpectlerをMeltdownで呼び出すことができます。)多くのプログラムは、標準のmemset()関数を使用して、キーやパスワードなどの機密情報を持つ変数をクリアします。 しかし、変数の更新された値は将来使用されないため、コンパイラはしばしばこれらの呼び出しを単に削除します。 最近まで、OpenSSLプロジェクトのクリーンアップ関数は次のようになりました。







OpenSSLプロジェクトからのクリーンアップ関数(crypto / mem_clr.cファイル)







もちろん、 memset()関数の問題はルールの例外です。 オプティマイザーは正しいコードを生成し、使用状況によってエラーが発生する可能性があります。 しかし、不正なコードの原因は人です。







手動での最適化は非常に危険です。 以前は、 calculate()関数の最適化されたバージョンを示しましたが、それは誤った最適化でした。 それはすべて、本当の声明から始まりました。







パラメーターaまたはbの少なくとも1つ偶数の場合、反復の回数は偶数になるため、戻り値は0になります。

これは含意であり、したがって、偽の条件下では、結果は任意になります。 abの両方奇数である可能性はありますが、反復回数は偶数になりますか?







答えはイエスです。 aまたはbのいずれかが負の場合、反復はまったくありません。 したがって、正しい手動最適化は次のコードにつながります。







正しく最適化された計算()関数







イーサリアム仮想マシン



Ethereum Virtual Machine(EVM)は、Ethereumプラットフォームのコアハードウェアです。 簡略化した形式で、そのアーキテクチャを次の図に示します。







簡素化されたアーキテクチャEVMアーキテクチャ







口座の残高、契約コード(コード)、契約ストレージ(ストレージ)の3種類のメモリを区別できます。 各アカウント(個人ウォレットまたは契約)には、イーサリアム通貨(ETH)で独自の残高があります。 スマートコントラクトごとに、そのコード(EVMの実行可能プログラム)と変数を格納するための独自のメモリが格納されます。 契約コードは作成後も変更されません。







Ethereumブロックチェーンは、特定の順序の多数のブロックで構成されています。 各ブロックは、トランザクションとその実行(領収書)のセットです。 EVMの状態(メモリ)は、以前のトランザクションのセット全体によって完全に決定されます。 Nの時点でEVMの状態を取得するには、N-1の時点でEVMの状態を取得してから、ブロックNからすべてのトランザクションを実行する必要があります。したがって、すべてのブロックチェーントランザクションを知っているので、過去のいつでもEVMの状態を判断できます。 このプロセスを次の図に示します。







EVMステータスとブロックチェーン







Mの時点でEVMの状態を修正すると、つまり、ブロックMの後に実行されたトランザクションに関係なく、EVMの状態は将来達成できなくなることに注意してください。







TとUの2つのトランザクションセットを考えます。ブロックNでトランザクションTを処理すると、同じブロックNでTではなくUを処理するEVMが「同一」状態になる場合、これらのトランザクションを「同等」と呼びます。トランザクションセットTとUの間のガスコストの違いによるブロックNの送信者とマイナーの残高の違い。







時間とメモリのコストはすべてガスのコストに含まれているため、最適化の主な目標はガスのコストを削減することです。 最適化のアプローチの1つは、ガスコストの低い「同等の」トランザクションの検索です。 冗長なコードを削除することについて話しますが、アルゴリズムなどを変更することはしません。 上記のCalculate()関数を使用した最初の例と同様です。







スマートコントラクトの作成



トランザクションには2つの異なるタイプがあります。 次に、スマートコントラクトを作成するためのトランザクションのみを説明します。 契約作成トランザクションは、EVMで2つの主要なアクションを実行します。契約ストアを初期化し、バイトコードを保存します。 リポジトリの初期化は、移行パラメーターを使用してコントラクトのコンストラクターを呼び出した結果です。 他のすべてのコントラクトメソッドはコードに保存されます。 このプロセスを次の図に示します。







スマートコントラクトの展開(作成)







契約コードの最適化の問題は、今後の記事に残されます。 今日は、契約展開コードの最適化に焦点を当てます。







契約展開コードの最適化



前のセクションでは、最適化の明確な目標であるガス消費の最小化について説明しました。 同時に、最適化されたトランザクションが元のトランザクションと「同等」であることを確認する必要があります。







Ethereumブロックチェーンから入手可能なソースコントラクトバイトコードを使用しました。 このタスクにソースコードは必要ありません。 その後、コントラクトデプロイメントの実行をトレースし、必要なコードのみを残しました。 展開コードを最適化するプロセスは、次のように表すことができます。







展開コードの最適化







前の例は単純化されていますが、使用されるアプローチはより複雑なトランザクションに適用できる場合があります。







低学年



EVMでの個々の命令の実装には、ガスの量に独自のコストがかかります。 同じ結果を得るには多くの方法がありますが、簡単に低いスコアを取得できます。 これを行うには、次の数値を合計します。







  1. 契約展開コードデータの支払い。
  2. 契約を作成するための料金;
  3. ストア内のさまざまな変数の数に、SSTOREオペレーターのコストを掛けたもの。
  4. ワード単位のコントラクトバイトコードのサイズに、メモリへの書き込みコストとRETURN命令のコストを掛けたもの。
  5. イベントの数に対応するコストを掛けたもの。


この下限は、最適化の基礎および目標として使用できます。







ここでは、契約のバイトコードが展開トランザクションデータからコピーされると想定しました。 オンザフライのバイトコード生成の状況は例外です。







統計と結果



ブロック番号4841148でイーサリアムブロックチェーンのスナップショットを撮りました。 その瞬間、ブロックチェーンには119041944のトランザクションがあり、そのうち1022020だけが契約を作成するトランザクションでした。 これらのトランザクションへの入力を比較すると、111,806個の一意の契約展開コードが見つかりました。







入力準備







固有の展開コードはそれぞれGanache CLI(以前のTestRPC)で起動され、実行の受信と契約バイトコードを受け取りました。 同時に、彼は素朴な最適化を実行し、低い評価も計算しました。 最適化されたコードはローカルブロックチェーンでテストされ、その後、結果がソースコードと比較されました。 このプロセスを次の図に示します。







単一トランザクションの最適化と検証







処理中に、彼は他の契約を作成した誤ったトランザクションと契約を無視しました。 オプティマイザーは、スタックで以前に受け取った値を無視し、毎回新しい値を作成するため、このオプティマイザーを呼び出します。







処理されたトランザクションごとに、初期処理コスト、最適化されたバージョンのコスト、およびより低い見積もりの​​3つの数値を受け取りました。 すべての値は、操作の現在の値で計算されました。 次の結果が得られました。







最適化結果







表から、単純な最適化でもガスを節約できることがわかります。 残念ながら、その量はそれほど多くありません。 一方、理論的には、契約の10%でのみ10%以上のガスを節約できます。 実際には、単純な最適化により、わずか0.3%の契約で10%の節約を達成することができました。







これらすべての数字を考えると、契約の作成は一度限りのプロセスであるため、スマートコントラクトの展開の最適化はイーサリアムにとって重要な問題ではないと結論付けることができます。 私は将来この問題に戻ると確信しており、今では契約自体のバイトコードに関連する多くの興味深い最適化問題があります。







人的要因



小さな間違いは、以前のすべての努力の結果を打ち消す可能性があります。 たとえば、トランザクショントランザクション0xdfa1..7fbbに2131132単位のガスが費やされました 。 これは、必要なガスよりも23%多いガスです。 誰かが送信する前に契約展開コードを複製しました。 その結果、6Kbのデータはまったく使用されませんでした。







次は?



スマートコントラクトを展開する際の最適化の問題は、副産物として浮上しています。 このトピックは理解するのに十分単純なので、私はそれから始めることにしました。







継続するには...








All Articles