【Solidity】複数データを配列とmappingで持つ場合それぞれのガス代の違い

Solidity

お疲れさまです。
次世代ビジネス推進部の荻原です。

最近、業務でスマコン開発を行うことになったのですが、その実装においてSolidityならではとも言える、頭を悩ませることになった仕様がありました。
それは、コントラクト内で複数のデータを持ちたいときに配列とmappingのどちらを利用するかというものです。

それらの実装方法は計算量が異なりTransaction処理にかかるGasに差が出てくるので、どのくらい違いがあるのか調べてみました。
その内容について紹介します。

目次

  1. Solidityの配列とmapping
  2. 検証の内容
  3. 検証結果
  4. ソースコード
  5. 参考

1. Solidityの配列とmapping

Solidityの配列は他の多くの言語と同様に長さがあり、配列ごと取得したりfor文で回したいときに便利です。

一方、mappingはkeyとvalueのペアでデータを保存します。
他の言語で言う辞書のようなものでhashテーブルのように動作しますが、長さを持たずどのようなkeyを指定しても初期値としてvalueの型の初期値が返ります。(例えばvalueがuintであれば0、stringであれば空文字が返ります。)
そのため、keyからvalueの検索は速いですがvalueからkeyを探すことはできません。また、keyが存在しているかを判定するというようなこともできません。

補足ですが、実装の工夫次第ではmappingに追加した要素数やmappingに追加したkeyの一覧をコントラクト内で持っておくというようなことは可能です。(参考

2. 検証の内容

許可をしたEOAのみが実行できる関数を考えます。
許可したEOAのaddressを

  • 配列で持つ
  • keyがaddress、valueがboolのmappingで持つ

それぞれの場合について違いを見てみます。

execute()という関数は許可されたEOA(operator)しか実行することができません。

isOperator()は指定したaddressがoperatorかどうかをboolで返します。
この isOperator()の実装が配列とmappingで変わります。

配列

配列ではfor文を用いて最大で配列の要素全件を線形探索する必要があります。

mapping

mappingはハッシュ探索なのでkeyを指定するだけです。

その他にも例えば配列を使っているとoperatorのEOAがわからなくても配列をそのまま返すことで確認ができますが、mappingではそのような実装はできません。

3. 検証結果

operatorとして1個、10個、20個のaddressが保存されているそれぞれの場合で、 execute()の実行で消費されたGasがどの程度異なるかを配列とmappingで比較しました。
配列はaddressの配置によってfor文のループ回数が変わり、Gasが変わるので最大値として配列の末尾に探索対象のaddressが入っていることを想定しました。
また、 execute()_flagfalseから trueにする場合とその反対の処理をする場合でもGasが変わるので falseから trueにする場合で統一しています。

ネットワークはGanacheを用い、Truffleを利用してスマコンメソッドの実行を行いました。

実装方法/operator数 1 10 20
mapping 28772 28772 28772
配列 31055 54059 79619

20fc5c21d5ca2ac10ce1c5b34b3d5c77.png

当然ですが、mappingはoperatorがいくつになってもGasは一定です。
一方配列は見事にoperator数に比例してGasが増えています。

今回のoperator数では考慮するほどではないかもしれませんが、ガス代や処理速度を重視するようであればmappingで実装する方が良さそうです。

4. ソースコード

非常に簡易的な実装ですが、今回検証に使用した全ソースコードです。

配列

mapping

5. 参考

Solidity