1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

truffle-config.js の最適化オプション runs の検証と Truffle v4 への懸念

Last updated at Posted at 2020-03-13

はじめに

Truffle 環境の設定をする truffle-config.js ですが、最適化ブロックで runs という指定が使えるようです。

はて、この値はなんなんでしょう?

Solidity公式ページの説明からするに、solc においての runs は下記のようなものらしいです。

デフォルトでは、オプティマイザは、コントラクトがそのライフタイムにわたって200回呼び出されると仮定して、コントラクトを最適化します。 イニシャルコントラクトのデプロイのガス代をより安く、その後の関数実行時のガス代をより高くしたい場合は、--optimize-runs=1 と設定してください。 多くのトランザクションを想定していて、デフォルトより高いデプロイコストとアウトプットサイズを気にしないのであれば、 --optimize-runs を大きな数に設定してください。

どうやら runs の設定は、**「値を小さくするとデプロイ時のガス代が下がって トランザクションのガス代が上がる、値を大きくするとデプロイ時のガス代が上がって トランザクションのガス代が下がる」**とのこと。

Truffle 環境ではコンパイラとして solcjs が使われているらしいので solc の機能がそのまま使えるとは限らないのですが、物は試しです。どのような効果があるのかテストしてみましょう(※こちらがテストプロジェクトとなります)。

テスト方法

ガス消費の計測用に、構造体を1つ作成して動的配列に追加する関数を持つ、シンプルなコントラクトを準備します(※せっかくなので CryptoKittiesKitty 構造体を拝借しました)。

テストコード(クリックで開閉します)
pragma solidity 0.5.12;

contract CompileOpt5{
  // クリプトキティの構造体を拝借
  struct Kitty {
    uint256 genes;
    uint64 birthTime;
    uint64 cooldownEndBlock;
    uint32 matronId;
    uint32 sireId;
    uint32 siringWithId;
    uint16 cooldownIndex;
    uint16 generation;
  }

  // ニャンコの配列
  Kitty[] internal kitties;

  // 要素数の取得
  function getTotalKitties() public view returns( uint256 ){
    return kitties.length;
  }

  // ニャンコの追加
  function createKitty( uint256 _val256 ) public{
    Kitty memory _kitty = Kitty({
      genes: _val256,
      birthTime: uint64(_val256),
      cooldownEndBlock: uint64(_val256),
      matronId: uint32(_val256),
      sireId: uint32(_val256),
      siringWithId: uint32(_val256),
      cooldownIndex: uint16(_val256),
      generation: uint16(_val256)
    });

    kitties.push( _kitty );
  }
}

上記コードを、runs の値が [1][200][2000] の設定でコンパイルしたコントラクトを、それぞれデプロイした際のガス消費量と、関数にアクセスした際のガス消費量で比較します。ついでに、最適化を無効にした状態もテストしてみましょう。

Truffle v5.1.2 (solc-js v0.5.12) でのテスト

各コントラクトをデプロイし、createKitty 関数へ200回以上アクセスをしてみました(※ひょっとしたら runs で指定した回数のトランザクションを超えるとコストが高くなったりするのかと思ったので多めにテスト)。
で、結果としては、すべてのコントラクトにおいて初回トランザクションだけガス消費が低く、2回目以降は一律の消費という結果になりました。

・最適化 有り(runs=1)
 デプロイ時のガス消費187,711
 初回のガス消費45,551
 2回目以降のガス消費68,963

truffle-config.js(クリックで開閉します)
module.exports = {
  // 〜 省略 〜

  // 最適化有り:runs=1
  compilers: {
    solc: {
      version: "0.5.12",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 1
        }
      }
    }
  }
}

・最適化 有り(runs=200)
 デプロイ時のガス消費190,327
 初回のガス消費45,503
 2回目以降のガス消費68,915

truffle-config.js(クリックで開閉します)
module.exports = {
  // 〜 省略 〜

  // 最適化有り:runs=200(デフォルト値)
  compilers: {
    solc: {
      version: "0.5.12",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 200
        }
      }
    }
  }
}

・最適化 有り(runs=2000)
 デプロイ時のガス消費245,294
 初回のガス消費45,419
 2回目以降のガス消費68,831

truffle-config.js(クリックで開閉します)
module.exports = {
  // 〜 省略 〜

  // 最適化有り:runs=2000
  compilers: {
    solc: {
      version: "0.5.12",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 2000
        }
      }
    }
  }
}

・最適化 無し
 デプロイ時のガス消費220,118
 初回のガス消費55,710
 2回目以降のガス消費79,122

truffle-config.js(クリックで開閉します)
module.exports = {
  // 〜 省略 〜

  // 最適化無し
  compilers: {
    solc: {
      version: "0.5.12",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: false
        }
      }
    }
  }
}

さて、各値を比較してみるに、runs の値が小さいほどデプロイ時のガス消費が抑えられているようです。
一方で、runs の値による、トランザクションのコスト変動は微々たるものです。デフォルトの runs=200 に対して、トランザクションを犠牲するはずの runs=1 でも、 優遇されるはずの runs=2000 においても、恩恵を実感できるほどの差が出ませんでした。

また、最適化無しの状態にくらべると、2回目以降のトランザクションのガス消費が**13%**ほど改善されています。

まとめ

truffle-config.js において runs の値は、特に気にしなくても良さそうです。とりあえず最適化しておくだけで、ガス消費も多少収まってくれるみたいですし、DApp のリリース時に最適化し忘れさえしなければ問題はなさそうですね。

Truffle v4 での最適化はどうなるのか?

さて、ある意味ここからが本題かもしれません。
最適化の指定は Truffle v4 環境でもちゃんと効果があるのでしょうか?

Truffle 関連の情報、特に書籍等では、v4 系の内容を取り扱ったものが多い印象があります。また、truffle console の使い勝手が、v4v5 で結構変わっているので、あえて v4 環境を使い続けている方も少なくないと思います。折角なので、v4 系でもテストしてみましょう。

Truffle v4.1.15 (solcjs v0.4.25) でのテスト

では、v4 向けに名前とコンパイラのバージョンを調整したコントラクトを用意して、テストの開始です。

テストコード(クリックで開閉します)
pragma solidity 0.4.25;

contract CompileOpt4{
  // クリプトキティの構造体を拝借
  struct Kitty {
    uint256 genes;
    uint64 birthTime;
    uint64 cooldownEndBlock;
    uint32 matronId;
    uint32 sireId;
    uint32 siringWithId;
    uint16 cooldownIndex;
    uint16 generation;
  }

  // ニャンコの配列
  Kitty[] internal kitties;

  // 要素数の取得
  function getTotalKitties() public view returns( uint256 ){
    return kitties.length;
  }

  // ニャンコの追加
  function createKitty( uint256 _val256 ) public{
    Kitty memory _kitty = Kitty({
      genes: _val256,
      birthTime: uint64(_val256),
      cooldownEndBlock: uint64(_val256),
      matronId: uint32(_val256),
      sireId: uint32(_val256),
      siringWithId: uint32(_val256),
      cooldownIndex: uint16(_val256),
      generation: uint16(_val256)
    });

    kitties.push( _kitty );
  }
}

・最適化 有り(runs=1)
 デプロイ時のガス消費225,602
 初回のガス消費55,701
 2回目以降のガス消費79,113

truffle-config.js(クリックで開閉します)
module.exports = {
  // 〜 省略 〜

  // 最適化有り:runs=1
  compilers: {
    solc: {
      version: "0.4.25",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 1
        }
      }
    }
  }
}

・最適化 有り(runs=200)
 デプロイ時のガス消費225,602
 初回のガス消費55,701
 2回目以降のガス消費79,113

truffle-config.js(クリックで開閉します)
module.exports = {
  // 〜 省略 〜

  // 最適化有り:runs=200(デフォルト値)
  compilers: {
    solc: {
      version: "0.4.25",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 200
        }
      }
    }
  }
}

・最適化 有り(runs=2000)
 デプロイ時のガス消費225,602
 初回のガス消費55,701
 2回目以降のガス消費79,113

truffle-config.js(クリックで開閉します)
module.exports = {
  // 〜 省略 〜

  // 最適化有り:runs=2000
  compilers: {
    solc: {
      version: "0.4.25",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: true,
          runs: 2000
        }
      }
    }
  }
}

・最適化 無し
 デプロイ時のガス消費225,602
 初回のガス消費55,701
 2回目以降のガス消費79,113

truffle-config.js(クリックで開閉します)
module.exports = {
  // 〜 省略 〜

  // 最適化無し
  compilers: {
    solc: {
      version: "0.4.25",
      parser: "solcjs",
      settings: {
        optimizer: {
          enabled: false
        }
      }
    }
  }
}

やや、すべての設定において、ガス消費量が完全に同じ値となりました…。

最初はコンパイル指定をミスったと思ったのですが、イーサスキャンでバイトコードを眺めてみると、各コントラクトともバイナリレベルで若干の差が出ています。仮に、同じバイナリデータであれば、イーサスキャンで表示した際、重複コントラクト扱いされるはずなので、個別に表示できている時点でコントラクト間で差分があるのは間違いなさそうです。

それなのに、イーサスキャン上でベリファイコードを登録しようとすると、「Optimization Enabled: No」でないと受け付けてくれません。

まとめると、最適化の設定によりバイナリデータに差が出ているにも関わらず、イーサスキャン上では最適化が認識されていないようなのです。

そして最大の問題が**「最適化有りでも無しでも、すべてのコントラクトのガス消費量が同じになっている」**という事実。

この結果が、私の環境や手順の間違いによるものであればよいのですが、仮に、 Truffle v4 環境が最適化の指定をコントラクトに反映してくれないのであれば、由々しき事態です。

もし Truffle v4 をお使いで Dapp のリリースを念頭に置いている方がいらっしゃるのであれば、お早めに、お使いの環境で最適化が機能するかテストしてみることをお勧めします。リリース間際になって「最適化が効かない!?」なんて状況は考えるだに恐ろしいです。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?