hasItemスピードテスト(コマンド VS ScriptAPI)

スポンサーリンク

※テストコードに誤りがあったので、修正しております。
ご指摘していただいたさば2号様、ありがとうございます。

前談

「commandよりscriptの方が速い」アドオン開発をしているとたびたび目にするこの言葉。
実際どのくらい早いのか? 気になったのでテストしてみることにしました。

注意

・このテスト方法がどれほど有効であるかは各自の判断にお任せします
・実行環境はウィンドウズ11にupdateできない程古いPCのため、十分に高いスペックの端末であれば差が縮まる可能性はあります

検証

検証内容(R)

後述のダイヤの所持を検出する処理速度検証用Scriptを、ダイヤの位置を変えたり所持していない状態などで実行
inventoryはダイヤ以外は乱雑なアイテムで埋まっている

検証結果(R)

おおむね、commandの勝利
ダイヤ:左上      command : 1775 / script 6219
ダイヤ:右下      command : 1771 / script 22368
ダイヤ:ホットバー左端 command : 1730 / script 613
ダイヤ:なし      command : 1076 / script 22429

テスト用コード

world.beforeEvents.chatSend.subscribe( async(ev) => {
  if(ev.message === "!hasItemSpeedTest"){
    system.runJob( testSpead(ev) );
    return;
  }
});

function* testSpead(ev){
  let command_time=0;
  let script_time =0;
  for( let i =0; 100> i;i++ ){ // それぞれ100*100回実行(10万回は多すぎた)
    command_time = command_time + testHasItemCommand(ev.sender);
    script_time = script_time + testHasItemScript(ev.sender);
    yield;
  }
  world.sendMessage("command : "+ command_time + " / script : "+ script_time);
}

function testHasItemCommand(player){ // command処理時間取得
  const start_time = Date.now();
  for(let i=0; 100 > i; i++){
    player.runCommand("execute if entity @s[hasitem={item=minecraft:diamond}] run say 1")
  }
  const end_time = Date.now();
  return end_time - start_time;
}

function testHasItemScript(player){ // Script処理時間取得
  const start_time = Date.now();
  const inventory = player.getComponent("minecraft:inventory");
  for(let i=0; 100 > i; i++){
    for(let j=0; inventory.inventorySize > j;j++){
      if(inventory.container.getItem(j)!=undefined&&inventory.container.getItem(j).typeId == "minecraft:diamond"){
        break;
      }
    }
  }
  const end_time = Date.now();
  return end_time - start_time;
}

後談

必ずしもscriptの方がcommandよりも高速・軽量であるわけではないことがわかりました。
もしかしたら、「早い」というのは処理速度の話ではなく、記述難度の話なのかもしれません。

パフォーマンスを最優先する場合、実際どのような実装が最適であるのかはよく検証したほうがよさそうです。

◇◆◇以下、修正前の内容◇◆◇

検証1

後述の「ダイヤ所持を検出する」処理速度検証用Scriptを、ダイヤ有り無しそれぞれで実行
ダイヤ以外は乱雑なアイテムで埋まっている

結果

Scriptの圧勝
▼ダイヤ持ってない状態で実行
結果1回目: command : 11787 / script 99
結果2回目: command : 11293 / script 76
結果3回目: command : 11243 / script 76
結果4回目: command : 11167 / script 67

▼インベントリ左上にダイヤ
結果1回目: command : 16323 / script 68
//持っている側では明らかにrunが足かせになった
//再検証する方はもっと軽いコマンドに変更したり、Script側にも同等の処理を付けることをおすすめします

検証内容2

同検証用Scriptの
「execute if entity @s[hasitem={item=minecraft:diamond}] run say 1」を
「tag @s[hasitem={item=minecraft:diamond}] add have_diamond」に置き換えることで軽量化できるか
※この方法だと最初の一回しかタグをつけれないので、本来であればタグを削除する処理が必要なため、検証としてはチートといえる

結果

結果1回目: command : 11258 / script 69

解散!

以下、検証用Scriptコード

world.beforeEvents.chatSend.subscribe( async(ev) => {
  if(ev.message === "!hasItemSpeedTest"){
    system.runJob( testSpead(ev) );
  }
});

function* testSpead(ev){
  let command_time=0;
  let script_time =0;
// それぞれ100*1000回実行
for( let i =0; 1000> i;i++ ){
    command_time = command_time + testHasItemCommand(ev.sender);
    script_time = script_time + testHasItemScript(ev.sender);
    yield;
  }
  world.sendMessage("command : "+ command_time + " / script : "+ script_time);
}
// command処理時間取得
function testHasItemCommand(player){
  const start_time = Date.now();
  for(let i=0; 100 > i; i++){
    player.runCommand("execute if entity @s[hasitem={item=minecraft:diamond}] run say 1")
  }
  const end_time = Date.now();
  return end_time - start_time;
}
// Script処理時間取得
function testHasItemScript(player){
  const start_time = Date.now();
  const container = player.getComponent("inventory").container;
  for(let i=0; 100 > i; i++){
    for(let j=0; container.inventorySize > j;j++){
      if(container.getItem(j).typeId == "minecraft:diamond"){
        break;
      }
    }
  }
  const end_time = Date.now();
  return end_time - start_time;

※なお他にもアドオン多数入ってるし、環境によって変動する可能性も十分にあります……
 気になった方は各自検証の上、結果をコメントで教えてくださいな

※投稿記事に含まれるファイルやリンクにより発生した被害についてクラフターズコロニーは責任を取りません
投稿通報

コメント

  1. hasitemとscriptAPIもそうですが、player.jsonのenvironment_sensorもまた違うタイミングでイベントを発生させている気がします

  2. このブログを参考にして私もスピードテストを作成したところ、コマンドとスクリプトの実行速度を比較した結果、コマンドの方が優勢でした。しかし、ブログに掲載されているコードをそのまま使用した場合、作者とほぼ同じ結果が得られました。

    そこで、なぜ結果が異なるのかを調べてみたところ、testHasItemScript()関数でプレイヤーの最大スロット数を取得する際に使用されているinventorySizeが存在せず、undefinedとなっていました。
    その結果、インベントリ内を巡回するforループが1回も実行されず、高速で実行できてしまっていました。

    自作スピードテストの実行結果 (10000回):
    command: 269 / script: 899

    • 追加検証ありがとうございます。
      なるほど、私が早とちりしてテストコードのミスに気づいていなかったのですね。ご指摘いただき感謝しています。
      こちらでもコードを修正してテストを行ったところ、今回の処理内容であればcommand の方が script よりも 4~40 倍程度高速に処理できることを確認しました。

コメント通報