11.001.1.Benchmark1 - MCJE-Tech-Community/Datapack-WIki GitHub Wiki
何か配布マップなどで遊んでいるときや作っているとき、処理が重くなるとモブがカクカクと動くのを見たことがあるでしょうか。あれは1tick内で実行される処理に時間がかかりすぎて、1tickが間延びすることで起こる現象らしいです。コマンドで何かを作った時、特に意識せず処理の重たいコマンドを書いてしまうと「せっかく作ったゲームが重くて遊べない」という事になりかねません。特に内容の多い物を作るならなおさら、軽量化に気を付ける必要があるでしょう。
といっても、どんな物が重くて軽いかというのはコマンドをやってるだけでは判別するのも難しく、一度軽量化を意識し始めたコマンダー達は日々頭を悩ませているようです。そこで、その悩みを軽減させるため(あるいはさらに悩みを加速させるため)に、コマンドの負荷を測定して可視化してしまおうと思います。
コマンドを実行する状況というのは様々で、今回行う比較が必ずしも常に成り立つとは限らない点に留意してください。ワールドに存在するエンティティの数やデータの大きさ、スコアボードの量やワールドの位置など、あるいはコンピューターのスペックなどが影響すると考えられます。すべての状況を網羅して比較するには時間がかかりすぎるため、必要そうなところだけピックアップして比較を行っています。
加えて、マイクラに限った話ではないですが、今回測定した数値自体は事実として現れますが、そこから得られた考察を過信しないよう気を付けてください。プログラムもまた往々にして複雑な物なので、どんな要素がどの程度影響するかというのはこれだけでわかるものではないと思います。
今回はベンチマークによってコマンド同士の負荷を比較していきます。使用するのはintsucさんが作成したmchというベンチマークツールです。
使用方法などはこちらの記事で丁寧に解説してくださっています。
▼「mchを使ってデータパックのベンチマークを行う」
https://gist.github.com/intsuc/b1f83e30957087d2ad40e15456f8b153
検証したいfunctionを大量に準備しておき、pythonによるファイル準備からmch起動、結果の保存までをすべての検証単位で行うbatファイルを準備することで、まとめてベンチマークできるようにしています。測定に使用したfunctionなどと、実行結果はリポジトリのコードに置いてあります。
先述の解説に習い、空のfunctionであるbaselineも各条件ごとにベンチマークし、数値を記載しています(結構日を跨いでの検証なので)。
baseline
コメントすらない空の*function*
実行結果はfunction辺りの実行時間(us/op)で出力されます。またここに記入するスコアは小数点3桁でそろえています。(有効数字3桁で統一したら逆に見づらかったので)
mchの設定は、特に記載が無ければ以下の設定(mch_config.json)でベンチマークを行っています。初期設定ではtime:10,forks:5の設定になっていますが、時間短縮のため減らしています。検証結果の誤差は5%以内に収まっていたので、これで妥協することとします。
{
"$schema": "https://raw.githubusercontent.com/mcenv/mch/main/mch-config-schema.json",
"warmup_iterations": 5,
"measurement_iterations": 5,
"time": 5,
"time_unit": "us",
"forks": 3,
"mc_args": [
"nogui"
],
"execute_benchmarks": [
"debug:baseline",
"debug:A",
"debug:B",
...
]
} また、setup,teardown.trialタグには以下のコマンドを登録しています。
▼setup
scoreboard objectives add A dummy
scoreboard objectives add B dummy
team add A
team add B▼teardown.trial
scoreboard players reset * A
scoreboard players reset * B
execute as @e run data merge entity @s {DeathTime:0,DeathLootTable:"",CustomName:''}
execute as @e[type=slime] run data merge entity @s {Size:0}
kill @e
data remove storage _: A
data remove storage _: B mchには上記のほかにsetup.trial,setup.iteration,teardown.iterationのタグがあります。エンティティがいる状態での検証を行う場合は実行直前に召喚を実行する必要があるため、各条件毎にsetup.trialを設定する必要があります。以降の検証においても、条件に示しているコマンドはたいていsetup.trialに設定されています。これらのタグの仕様はmchのページで確認できます。
また今回使用しているCPUは Intel Core i7-12700 ,グラフィックボードは GeForceRTX 3070 です。
- エンティティ
- エンティティの総数とセレクタによる検索
- type引数の有無
- エンティティタイプとtag引数
- エンティティタイプとtype引数
- エンティティのNBT検索
- tag引数の個数
- x,y,z引数
- UUID検索
- distanceとdx,dy,dz
- distanceの大きさ
- distanceとtypeを併用したとき
- sort引数
- limit引数
- スコアのチェック
- NBTのチェック
- エンティティタイプ別のNBT取得
- NBT取得方法
- エンティティタイプ別のNBT操作
- NBT変更の方法
- データ型別のエンティティNBT操作
- エンティティタイプ別の召喚削除
- タグ,スコア,チームの付け消し
- テレポート
- Passengers持ちエンティティのテレポート
- on passengerでの紐づけ
- on originでの紐づけ
- on passengerとon originでの紐づけ
- on originの相手の有無
- マーカーを経由したPos取得
- マーカーを経由したPos代入
- スコアボード
- ストレージ
- typeとdistance、軽くなるのに条件はあれど割と付け得
- nbt引数はtagとかscoresの20倍くらい重い
- sort,xyzは付けても負荷の変動はほぼない
- 絶対座標ならpositionedよりx,y,z引数の方がちょっと軽い
- sortによる差はない
- limit引数は付け得
- if dataやif scoreよりif entityの方がちょっと軽い
- NBTの取得はNBT操作程大きな差はない
- データの移行はさすがにstoreよりdata modifyの方がいい
- NBTの操作の負荷はデータ量次第 marker軽い display割と重い 村人は特別にとても重い
- RotationやPosはストレージを経由した変更の方がよさそう
- 付け消しの軽さは team > tag > score
- tpの負荷はmarkerのデータ書き換え1回と同じくらい
- on passengerやon originはスコア紐づけより2~3倍軽い
- scoreboardコマンドでの計算はどれも同じくらいの負荷
- scoreの計算はmarkerへのデータ代入の数十倍は早い
- スコアホルダーやオブジェクトの数では計算速度にほとんど影響しない(ワイルドカードを除く)
- storeを使ってスコア操作の結果を複数のスコアに代入させるよりは分けて書いた方が若干軽い
- data getの結果を複数のスコアに入れるときはさすがに一列にした方がいい
検索したいエンティティが1体で、検索の範囲に含まれるエンティティの総数が1,10,100,1000体と数を変えるとき、セレクタによる検索の負荷がどのように増加するかを調べたい。
tagの対象内になるmobを1体、対象外のmobを0,9,99,999体召喚。計1,10,100,1000体のエンティティ下で実行。
# 対象内を1体
summon zombie ~ ~ ~ {Tags:[A]}
# 対象外を0,9,99,999体
summon zombie ~ ~ ~ {Tags:[B]}
×0,×9,×99,×999 の4種類で条件を変えて実行A
execute if entity @e[tag=A]| condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1体 | A | 0.536 ±0.005 |
| 10体 | A | 0.792 ±0.028 |
| 100体 | A | 3.150 ±0.051 |
| 1000体 | A | 42.246 ±1.262 |
| (1体) | baseline | 0.227 ±0.001 |
検索するエンティティの総数が増えれば増えるほど線形的に負荷が増えている。大量にエンティティが存在しうる場合は、tag以外の引数もしっかり指定して絞り込む必要がありそう。
▲戻る
tagでエンティティを指定するとき、type引数も指定したときとしないときでの負荷を比べたい。typeで絞られるエンティティの数を変えて検証。type引数のみのコマンドはそもそも実行結果が変わってしまうので条件1でのみ検証をしている。
tagで対象になるmobを1体、typeの対象になるmobを計1,10,100体の3条件で行い、エンティティの総計が100体になるように対象外のmobを召喚した。
条件1 (1体)
# tagの対象を1体
summon zombie ~ ~ ~ {Tags:[A]}
#対象外を99体
summon skeleton ~ ~ ~ {Tags:[B]}
×99条件2 (10体)
# tagの対象を1体
summon zombie ~ ~ ~ {Tags:[A]}
# typeの対象でtagの対象外を9体
summon zombie ~ ~ ~ {Tags:[B]}
×9
#対象外を90体
summon skeleton ~ ~ ~ {Tags:[B]}
×90条件3 (100体)
# tagの対象を1体
summon zombie ~ ~ ~ {Tags:[A]}
# typeの対象でtagの対象外を99体
summon zombie ~ ~ ~ {Tags:[B]}
×99A
execute if entity @e[tag=A]B
execute if entity @e[type=zombie]C
execute if entity @e[tag=A,type=zombie]D
execute if entity @e[type=zombie,tag=A]| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1体 | A | 3.338 ±0.033 |
| B | 0.813 ±0.019 | |
| C | 0.822 ±0.011 | |
| D | 0.842 ±0.023 | |
| 10体 | A | 3.291 ±0.071 |
| C | 1.092 ±0.050 | |
| D | 1.081 ±0.033 | |
| 100体 | A | 3.277 ±0.040 |
| C | 3.399 ±0.086 | |
| D | 3.363 ±0.035 | |
| (100体) | baseline | 0.235 ±0.003 |
対象のエンティティタイプが少ない場合(あるいは異なるエンティティタイプが多い場合)であれば、typeによる絞り込みは非常に効力が高い。逆に対象のエンティティタイプが多くなった時は、type無しによる検索と同程度になる。それでもだいたいtype付き≦type無しという結果になっているので、typeは付け得かも。
▲戻る
エンティティの種類によって検索の負荷が変わっている気がしたので検証しておきたい。
対象にするエンティティを1体、対象外にする同タイプのエンティティを9体召喚
条件1 (zombie)
summon zombie ~ ~ ~ {Tags:[A]}
summon zombie ~ ~ ~ {Tags:[B]}
×9条件2 (villager)
summon villager ~ ~ ~ {Tags:[A]}
summon villager ~ ~ ~ {Tags:[B]}
×9条件3 (armor_stand)
summon armor_stand ~ ~ ~ {Tags:[A]}
summon armor_stand ~ ~ ~ {Tags:[B]}
×9条件4 (item_display)
summon item_display ~ ~ ~ {Tags:[A]}
summon item_display ~ ~ ~ {Tags:[B]}
×9条件5 (marker)
summon marker ~ ~ ~ {Tags:[A]}
summon marker ~ ~ ~ {Tags:[B]}
×9条件6 (interaction)
summon interaction ~ ~ ~ {Tags:[A]}
summon interaction ~ ~ ~ {Tags:[B]}
×9A
execute if entity @e[tag=A]| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| zombie | A | 0.758 ±0.020 |
| villager | A | 0.774 ±0.020 |
| armor_stand | A | 0.777 ±0.006 |
| item_display | A | 0.582 ±0.011 |
| marker | A | 0.587 ±0.009 |
| interaction | A | 0.580 ±0.007 |
| baseline | 0.223 ±0.002 |
意外にも差がある。いわゆるLivingEntity+アマスタの類とそれ以外で分かれているように見えるけど、理由はわからない。(らすく)
LivingEntityがそれ以外より遅くなっているのはおそらく、@.eにデフォルトで付いてくる最初にチェックされる条件の「エンティティが生存しているかどうか」で、LivingEntityは共通の「削除されていないこと」に加えて「Healthが0.0fより大きいこと」もチェックされるのが原因だと思う。Healthの取得と比較だけで約200nsもかかっているのはおそらくHealthがロックが必要な同期データだからで、display系のNBT取得が遅いこととも関係していると思う。(intsuc)
▲戻る
検索するエンティティの種類によってtype検索の負荷が変わるかどうか、変わらないと予測されるが実際に検証して確かめておきたい。
計5種類のエンティティを召喚して検証
summon zombie ~ ~ ~
summon armor_stand ~ ~ ~
summon area_effect_cloud ~ ~ ~
summon item_display ~ ~ ~
summon marker ~ ~ ~A
execute if entity @e[type=zombie]B
execute if entity @e[type=armor_stand]C
execute if entity @e[type=area_effect_cloud]D
execute if entity @e[type=item_display]E
execute if entity @e[type=marker]| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.547 ±0.010 |
| B | 0.547 ±0.007 |
| C | 0.531 ±0.008 |
| D | 0.534 ±0.008 |
| E | 0.533 ±0.012 |
| baseline | 0.226 ±0.002 |
違いがあるようにも見えるが、誤差の可能性が高いかも?
▲戻る
一般にエンティティのNBT検索は重いと言われるけどどれくらい重いかを確認したい。
似たような役割がありえそうなtag,scores,nbt引数で比較する。それぞれの引数で対象になりうるエンティティを1体だけ召喚し、それ以外のエンティティを99体召喚した。NBTの複雑さの影響も気になるのでnbtは二種類検証。
# 対象内を1体
summon zombie ~ ~ ~ {Tags:[A],CanBreakDoors:1b,HandItems:[{id:"stick",Count:1b,tag:{A:1b}},{}]}
# 対象外を99体
summon zombie ~ ~ ~ {Tags:[B]}
×99
# スコアを設定
scoreboard players set @e[tag=A] A 1
scoreboard players set @e[tag=B] A 0A
execute if entity @e[tag=A]B
execute if entity @e[scores={A=1}]C
execute if entity @e[nbt={CanBreakDoors:1b}]D
execute if entity @e[nbt={HandItems:[{tag:{A:1b}}]}]| Function | Results v1.20 [us/op] |
|---|---|
| A | 3.136 ±0.044 |
| B | 5.832 ±0.102 |
| C | 118.806 ±2.064 |
| D | 123.591 ±1.986 |
| baseline | 0.226 ±0.002 |
NBTの検索、20倍~40倍くらい重いっぽい。typeやdistanceを使った絞り込みをしっかり行い、NBTを見るエンティティの数は極力減らすようにする必要がありそう。HurtTimeで攻撃されたモブを探ったりするときは、どうしても対象モブが多くなってこれくらいの負荷を出しそうなので、1回の実行に留めつつtagやスコアを併用した方がよさそう。
HandItemsのtag内を探る程度でも5%くらい負荷が上がっているように見えるので、NBTの深さの影響もそれなりにありそう。
スコアの検索もNBTほどではないがtagよりも負荷が高い。エンティティが持つタグの数とかが影響する可能性はあるので別で比較したい。
▲戻る
セレクタ内に入れるtag引数の数によってどれくらい負荷に変動があるかを調べたい
タグA,B,Cを持たせたzombieを1体召喚。
summon zombie ~ ~ ~ {Tags:[A,B,C]}A
execute if entity @e[tag=A]B
execute if entity @e[tag=A,tag=B]C
execute if entity @e[tag=A,tag=B,tag=C]| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.530 ±0.007 |
| B | 0.545 ±0.004 |
| C | 0.561 ±0.010 |
| baseline | 0.228 ±0.003 |
tagが増えるごとに線形的に増えていきそうな気がする。ひとつのtagにできるならひとつにした方がいいかも。
▲戻る
distanceやdx,dy,dzを利用するとき、始点を絶対座標に動かす方法としてx,y,z引数かexecute positionedがある。負荷の違いがあるか確認したい。
適当に検索対象になるエンティティを1体置いてコマンドを実行する
# 対象内を1体
summon marker ~ ~ ~ {Tags:[A]}A
execute if entity @e[tag=A]B
execute if entity @e[tag=A,x=8.0,y=72.0,z=8.0]C
execute if entity @e[x=8.0,y=72.0,z=8.0,tag=A]D
execute positioned 8.0 72.0 8.0 if entity @e[tag=A]| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.507 ±0.005 |
| B | 0.505 ±0.009 |
| C | 0.501 ±0.010 |
| D | 0.586 ±0.005 |
| baseline | 0.229 ±0.001 |
positionedの方がやや高い。ABCは1つのサブコマンドで済んでいるのでそこの差かも?
▲戻る
計算用や基準用として置いたエンティティを、軽くなりそうなセレクタで探したときとUUIDで直接指定したときの負荷を比較したい。
対象になるエンティティを1体、対象外になるエンティティを99体配置した計100体エンティティがいる状態で検索させた。
summon marker ~ ~ ~ {Tags:[A],UUID:[I;0,0,0,0]}
summon zombie ~ ~ ~ {Tags:[B]}
×99A
execute if entity @e[tag=A]B
execute if entity @e[tag=A,type=marker]C
execute if entity 0-0-0-0-0| Function | Results v1.20 [us/op] |
|---|---|
| A | 3.338 ±0.044 |
| B | 0.812 ±0.015 |
| C | 0.475 ±0.022 |
| baseline | 0.230 ±0.001 |
UUIDでの検索がなんだかんだだいぶ軽い。対象が1体ならセレクタ検索はtypeが一番絞り込みが早いっぽいので、Bはセレクタの中でもだいぶ軽い部類のはずだけど、それの半分くらいというのは結構強い。
▲戻る
distanceやdx,dy,dzも絞り込みができて軽くなるみたいだけど、具体的にどういう状況に適してるのか知りたい。マイクラのチャンクは水平方向に16×16で区切られてるけど、さらにそれが縦方向に16で区切られたエンティティセクションというのも存在する(F3+Gの青い線がそう)。エンティティをいろんなエンティティセクションに配置し、検索範囲によって負荷がどう変わるか比較したい。
サーバー実行点付近にある3つのエンティティセクションにおいて、それぞれの中心(8,72,8),(-8,72,8),(-8,72,-8)に検索対象外になるエンティティを99体配置。1つめと2つめのセクションの境界近くで1つめのセクション内に1体配置して、範囲を跨ぐ跨がないでの違いを調査。
条件1 対象と同じセクションに99体
summon marker 0.1 72.0 8.0 {Tags:[A]}
summon marker 8.0 72.0 8.0 {Tags:[B]}
×99条件2 対象の隣のセクションに99体
summon marker 0.1 72.0 8.0 {Tags:[A]}
summon marker -8.0 72.0 8.0 {Tags:[B]}
×99条件3 対象の隣でもないセクションに99体
summon marker 0.1 72.0 8.0 {Tags:[A]}
summon marker -8.0 72.0 -8.0 {Tags:[B]}
×99A
execute positioned 1.01 72.0 8.0 if entity @e[tag=A]B
execute positioned 1.01 72.0 8.0 if entity @e[tag=A,distance=..1]C
execute positioned 0.5 72.0 8.0 if entity @e[tag=A,distance=..1]D
execute positioned 0.1 71.5 7.5 if entity @e[tag=A,dx=0,dy=0,dz=0]E
execute positioned -0.1 71.5 7.5 if entity @e[tag=A,dx=0,dy=0,dz=0]| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1 | A | 1.324 ±0.025 |
| B | 0.966 ±0.014 | |
| C | 0.984 ±0.024 | |
| D | 0.972 ±0.006 | |
| E | 1.007 ±0.023 | |
| 2 | A | 1.300 ±0.029 |
| B | 1.004 ±0.022 | |
| C | 1.003 ±0.008 | |
| D | 1.013 ±0.008 | |
| E | 1.037 ±0.030 | |
| 3 | A | 1.322 ±0.034 |
| B | 0.644 ±0.009 | |
| C | 0.637 ±0.006 | |
| D | 0.658 ±0.011 | |
| E | 0.637 ±0.008 | |
| (3) | baseline | 0.226 ±0.005 |
条件3の時だけ、B~Eの全部で半分くらいの負荷になったので、エンティティが大量にいるセクションを検索してないのがわかる。ただ、条件2のBとDはエンティティが大量にいるセクションには範囲が重なってないはずなのにスコアが悪くなっているので、セクションを検索に入れるかどうかの判定は厳密に範囲が重なっているかで行っているわけではなさそう。そのあたりはまた別で検証したい。 エンティティセクションの位置関係で負荷が上がるといっても、tag単体より重くなるわけではなさそうなので、typeと同じく付け得かも。エンティティを大量に一か所に置いていたりすると位置絞り込みの効力は弱くなるけど、そうで無ければ結構軽くなりそう。
▲戻る
distanceの大きさによってどれくらい負荷が変わるか検証。
実行位置に対象を1体、101m上空に対象外を99体置いた。
summon marker ~ ~ ~ {Tags:[A]}
summon marker ~ ~101 ~ {Tags:[B]}
×99A
execute if entity @e[tag=A]B
execute if entity @e[tag=A,distance=..0.1]C
execute if entity @e[tag=A,distance=..1]D
execute if entity @e[tag=A,distance=..10]E
execute if entity @e[tag=A,distance=..100]| Function | Results v1.20 [us/op] |
|---|---|
| A | 1.246 ±0.027 |
| B | 0.561 ±0.016 |
| C | 0.565 ±0.010 |
| D | 0.562 ±0.008 |
| E | 1.078 ±0.047 |
| baseline | 0.224 ±0.008 |
B~Dを見るに、範囲の大きさは検索時の負荷に影響なさそう。Eだけスコアが悪いのは、ひとつ前の検証であったように大量にエンティティがいるセクションが検索対象に入ったからだと思われる。範囲が広ければその分エンティティのいるセクションと重なる可能性が高くなるので、間接的には範囲が広ければ広いほど負荷が高くなるといえそう。
▲戻る
typeとdistanceを付けることで絞り込みができ、検索が軽くなることが分かったが、併用したときはどうなのかを知りたい。
条件1
実行位置に対象を1体、3m上(同一のエンティティセクション)に対象外を100体設置
summon zombie ~ ~ ~ {Tags:[A]}
summon skeleton ~ ~3 ~ {Tags:[B]}
×100条件2
実行位置に対象を1体、100m上(異なるエンティティセクション)に対象外を100体設置
summon zombie ~ ~ ~ {Tags:[A]}
summon skeleton ~ ~100 ~ {Tags:[B]}
×100条件3
実行位置に対象を1体、3m上に同タイプの対象外を50体、異なるタイプの対象外を50体設置
summon zombie ~ ~ ~ {Tags:[A]}
summon zombie ~ ~3 ~ {Tags:[B]}
×50
summon skeleton ~ ~3 ~ {Tags:[B]}
×50A
execute if entity @e[tag=A]B
execute if entity @e[tag=A,distance=..1]C
execute if entity @e[tag=A,type=zombie]D
execute if entity @e[tag=A,type=zombie,distance=..1]E
execute if entity @e[tag=A,distance=..1,type=zombie]| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1 | A | 3.182 ±0.040 |
| B | 1.053 ±0.017 | |
| C | 0.790 ±0.019 | |
| D | 0.947 ±0.034 | |
| E | 0.949 ±0.039 | |
| 2 | A | 3.178 ±0.045 |
| B | 0.584 ±0.004 | |
| C | 0.780 ±0.017 | |
| D | 0.596 ±0.007 | |
| E | 0.597 ±0.008 | |
| 3 | A | 3.191 ±0.080 |
| B | 1.027 ±0.023 | |
| C | 2.049 ±0.039 | |
| D | 1.031 ±0.009 | |
| E | 1.055 ±0.029 | |
| baseline | 0.224 ±0.008 |
条件1はtype引数が最も効力を発揮する条件なのでtype単体を付けた時は早いけど、distanceを付けると逆に遅くなっている。一方で、条件2はdistanceが最も効力を発揮する条件で、こちらはtypeを付けても結果が変わっていない。実際の環境において同一エンティティタイプが1体しかいない、なんて事は少ないと思うので、distanceとtypeを併用する方が安牌のような気がする。
▲戻る
セレクタのsort引数を付けたときの負荷の違いはあるのか確かめたい。
対象にするmarkerを1体召喚、対象外のmarkerを100体召喚。
summon marker ~ ~ ~ {Tags:[A]}
summon marker ~ ~ ~ {Tags:[B]}
×100A
execute if entity @e[tag=A]B
execute if entity @e[tag=A,sort=arbitrary]C
execute if entity @e[tag=A,sort=nearest]D
execute if entity @e[tag=A,sort=furthest]E
execute if entity @e[tag=A,sort=random]| Function | Results v1.20 [us/op] |
|---|---|
| A | 1.233 ±0.012 |
| B | 1.254 ±0.031 |
| C | 1.242 ±0.023 |
| D | 1.228 ±0.010 |
| E | 1.242 ±0.017 |
| baseline | 0.229 ±0.004 |
差はほとんど誤差の範囲内に見える。エンティティの配置やlimitなどの併用で大きく結果が変わる可能性はある。(らすく)
sortで使われているソートアルゴリズムはTimSortというもので、入力コレクションがほぼソートされている場合ほぼ線形時間でソートが完了するという特徴がある。全てのエンティティが同じ座標にいる場合は既にソート済みということになるので、ほぼ線形時間でソートが完了する条件を満たしてしまう。検索対象のエンティティの座標がどの程度ソートされているかによってスコアも変わるのではないかと思う。(intsuc)
▲戻る
セレクタのlimit引数を付けたときの負荷の減り方を調べたい。sort無し(sort=arbitrary)で検証。最近のバージョンでarbitraryでも検索のうちどめをするようになったらしいので確認しよう。
条件1 (先)
対象を1体先に召喚、対象外を99体召喚。
summon marker ~ ~ ~ {Tags:[A]}
summon marker ~ ~ ~ {Tags:[B]}
×99条件2 (後)
対象を1体後に召喚、対象外を99体召喚。
summon marker ~ ~ ~ {Tags:[B]}
×99
summon marker ~ ~ ~ {Tags:[A]}A
execute if entity @e[tag=A]B
execute if entity @e[tag=A,limit=1]C
execute if entity @e[limit=1,tag=A]| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 先 | A | 3.128 ±0.064 |
| B | 0.523 ±0.007 | |
| C | 0.524 ±0.023 | |
| 後 | A | 3.196 ±0.079 |
| B | 3.196 ±0.136 | |
| C | 3.154 ±0.121 | |
| baseline | 0.228 ±0.003 |
sort=arbitaryは(基本)召喚順での検索をするらしいので、一番最初に検索対象を召喚した条件ではすぐにtag=Aが1体みつかり、そこで検索が止まることで処理が軽くなっているのが読み取れる。逆に一番最後に召喚したパターンではすべてのエンティティを探査した後に見つかるので、limit無しとほぼ同等の負荷が出ている。(らすく)
AABB無し(dx/dy/dzや最大値付きdistanceのいずれも持たない)セレクタの検索対象のエンティティコレクションには追加した順番が必ず保持されるものが使われているので、limit=1がある場合に条件を満たすエンティティが最初に追加されている場合の方が最後に追加されている場合より速いことには再現性があるはず。 AABB有りの場合はセレクタの検索方法が変わって順番がセクション座標依存になるのでまた違った結果になると思う。(intsuc)
▲戻る
executeでスコアを条件に入れるとき、if entity @s[scores=]とif score @sの方法がある。どっちの方がよさそうか確かめたい。また、失敗時と成功時や2つのスコアを見たいときとかでどう変わるかも確かめる。
対象にするmarkerを1体召喚。UUID検索を使ってasの負荷は最低限に。スコアもセット
summon marker ~ ~ ~ {UUID:[I;0,0,0,0]}
execute as 0-0-0-0-0 run scoreboard players set @s A 1
execute as 0-0-0-0-0 run scoreboard players set @s B 1A
execute as 0-0-0-0-0 if entity @s[scores={A=1}]B
execute as 0-0-0-0-0 if entity @s[scores={A=0}]C
execute as 0-0-0-0-0 if entity @s[scores={A=1,B=1}]D
execute as 0-0-0-0-0 if entity @s[scores={A=0,B=1}]E
execute as 0-0-0-0-0 if entity @s[scores={A=1,B=0}]F
execute as 0-0-0-0-0 if entity @s[scores={A=0,B=0}]G
execute as 0-0-0-0-0 if score @s A matches 1H
execute as 0-0-0-0-0 if score @s A matches 0I
execute as 0-0-0-0-0 if score @s A matches 1 if score @s B matches 1J
execute as 0-0-0-0-0 if score @s A matches 0 if score @s B matches 1K
execute as 0-0-0-0-0 if score @s A matches 1 if score @s B matches 0L
execute as 0-0-0-0-0 if score @s A matches 0 if score @s B matches 0| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.962 ±0.005 |
| B | 0.980 ±0.015 |
| C | 0.991 ±0.016 |
| D | 0.967 ±0.014 |
| E | 1.014 ±0.014 |
| F | 0.970 ±0.006 |
| G | 0.974 ±0.007 |
| H | 1.147 ±0.033 |
| I | 1.327 ±0.025 |
| J | 1.014 ±0.018 |
| K | 1.442 ±0.032 |
| L | 0.986 ±0.016 |
| baseline | 0.227 ±0.002 |
ちょっと多くてわかりづらいけど、全体的にif entityの方が軽そうに見える。サブコマンドのifは最後にあるか途中にあるかで挙動が変わり、最後に置かれたifはメッセージを生成するためやや負荷が上がると思われる。失敗時の負荷上昇はif scoreの時の方が大きいが、Lの負荷がHよりも低いのを見るに最後に置かなければ失敗時の負荷は大きくならなそう。ifの使い方としては途中に入れることの方が多いはずなので、実はそこまでifの失敗成功は気にしなくてもいいかもしれない。そうなると、すべて最後に置かれていながら負荷で勝っているif entityは途中に置いた時はもっと軽い可能性があるかも。また別で検証したい。(らすく)
/execute asやonやifのように分岐(fork)するコマンドは末尾のコマンドのように失敗することはなくて、単に条件を満たす対象がいないと分岐数が0になってコマンドを実行するソースがいなくなるので途中で終了するだけなので、末尾のコマンドの失敗時のペナルティ(例外メッセージの作成や例外のスロー)を払わなくて済む。例えば途中の/execute ifは条件を満たせば現在のソース1個、満たさなければ0個のソースに対して残りのコマンドを実行するコマンド。(intsuc)
▲戻る
executeでnbtを条件に入れるとき、if entity @s[nbt=]とif data entity @sの方法がある。どっちの方がよさそうか確かめたい。また、失敗時と成功時でどう変わるかも確かめる。
対象にするmarkerを1体召喚。UUID検索を使ってasの負荷は最低限に。
summon marker ~ ~ ~ {UUID:[I;0,0,0,0],data:{A:1b}}A
execute as 0-0-0-0-0 if entity @s[nbt={data:{A:1b}}]B
execute as 0-0-0-0-0 if entity @s[nbt={data:{A:0b}}]C
execute as 0-0-0-0-0 if data entity @s {data:{A:1b}}D
execute as 0-0-0-0-0 if data entity @s {data:{A:0b}}E
execute as 0-0-0-0-0 if data entity @s data{A:1b}F
execute as 0-0-0-0-0 if data entity @s data{A:0b}| Function | Results v1.20 [us/op] |
|---|---|
| A | 1.386 ±0.032 |
| B | 1.367 ±0.010 |
| C | 1.401 ±0.008 |
| D | 1.532 ±0.020 |
| E | 1.369 ±0.020 |
| F | 1.518 ±0.012 |
| baseline | 0.230 ±0.002 |
if entityの方がif data entityよりやや軽かった。あんまりはっきりとした理由がわからないけど、アウトプットの生成関連が原因だろうか。またif dataは失敗時の負荷上昇がそれなりにあるのと、{data:{}}よりdata{}の方がやや軽いのが興味深いポイント。失敗時の例外処理やエラー文作成などが原因で負荷が上がるという事があるようなので、これもそれが原因か。{data:{}}よりdata{}の方が軽いのは、前者はrootからサブデータを全部見させるのに対して、後者はdataからなのが要因かも?なるべく後者の指定方法を使った方がいいか。
▲戻る
NBTを取得するのもそこそこ負荷が高いというけど実際どれくらいなのか確かめたい
ゾンビ、村人、アマスタ、AEC、ディスプレイ3種、マーカー、データを持たせたマーカー、インテラクションの計10種で実行。アクセスの負荷をそろえるためにUUIDで指定。失敗することのないように2回づつ実行させている。エンティティの初期位置はPos:[0.0d,64.0d,0.0d]になる。
条件1 (zombie)
summon zombie ~ ~ ~ {UUID:[I;0,0,0,0]}
data modify storage _: A set value 0.0d条件2 (villager)
summon villager ~ ~ ~ {UUID:[I;0,0,0,1]}
data modify storage _: A set value 0.0d条件3 (armor_stand)
summon armor_stand ~ ~ ~ {UUID:[I;0,0,0,2]}
data modify storage _: A set value 0.0d条件4 (area_effect_cloud)
summon area_effect_cloud ~ ~ ~ {UUID:[I;0,0,0,3]}
data modify storage _: A set value 0.0d条件5 (block_display)
summon block_display ~ ~ ~ {UUID:[I;0,0,0,4]}
data modify storage _: A set value 0.0d条件6 (item_display)
summon item_display ~ ~ ~ {UUID:[I;0,0,0,5]}
data modify storage _: A set value 0.0d条件7 (text_display)
summon text_display ~ ~ ~ {UUID:[I;0,0,0,6]}
data modify storage _: A set value 0.0d条件8 (marker)
summon marker ~ ~ ~ {UUID:[I;0,0,0,7]}
data modify storage _: A set value 0.0d条件9 (marker+data)
summon marker ~ ~ ~ {UUID:[I;0,0,0,8],data:{0:"A",1:"A",2:"A",3:"A",4:"A",5:"A",6:"A",7:"A",8:"A",9:"A"}}
data modify storage _: A set value 0.0d条件10 (interaction)
summon interaction ~ ~ ~ {UUID:[I;0,0,0,0]}
data modify storage _: A set value 0.0dA
data modify storage _: A set from entity 0-0-0-0-0 Pos[1]
data modify storage _: A set from entity 0-0-0-0-0 Pos[0]| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| zombie | A | 3.534 ±0.026 |
| villager | A | 4.774 ±0.023 |
| armor_stand | A | 3.113 ±0.078 |
| area_effect_cloud | A | 2.183 ±0.073 |
| block_display | A | 4.702 ±0.097 |
| item_display | A | 4.805 ±0.351 |
| text_display | A | 5.582 ±0.037 |
| marker | A | 1.823 ±0.028 |
| marker+data | A | 2.476 ±0.080 |
| interaction | A | 2.030 ±0.059 |
| baseline | 0.229 ±0.003 |
データを持たないマーカーがこの中では一番軽いっぽい。インタラクションもずいぶん軽いが、実環境ではプレイヤーの動作を記録するとややデータが増えるのでそれによる影響も出うるかも。ゾンビや村人などのモブもそれなりに高いが、それよりもディスプレイエンティティの取得負荷が結構高いのは意外だった。(らすく)
▲戻る
NBTの取得において、用途が少し異なるのであれだがストレージに移す場合やスコアに取る場合、/data modifyやstore resultなどの方法がある。どれくらいの違いがあるのか知りたい。data modifyの方はすでに同じデータがあると失敗になるのでその場合の負荷も比較。
対象にするmarkerを1体召喚。UUID検索を使ってasの負荷は最低限に。
summon marker ~ ~ ~ {UUID:[I;0,0,0,0]}
data modify storage _: A set value 0.0dA
data modify storage _: A set from entity 0-0-0-0-0 Pos[1]
data modify storage _: A set from entity 0-0-0-0-0 Pos[0]B
data modify storage _: A set from entity 0-0-0-0-0 Pos[0]
data modify storage _: A set from entity 0-0-0-0-0 Pos[0]C
execute store result storage _: A double 1 run data get entity 0-0-0-0-0 Pos[0]
execute store result storage _: A double 1 run data get entity 0-0-0-0-0 Pos[1]D
execute store result score _ A run data get entity 0-0-0-0-0 Pos[0]
execute store result score _ A run data get entity 0-0-0-0-0 Pos[1]| Function | Results v1.20 [us/op] |
|---|---|
| A | 1.889 ±0.022 |
| B | 2.343 ±0.106 |
| C | 2.078 ±0.074 |
| D | 1.721 ±0.016 |
| baseline | 0.230 ±0.003 |
data modifyで失敗するときの負荷増加はそこそこ大きそう。data modifyとstore result storageを比較するとさすがにdata modifyの方が軽いみたい。
▲戻る
NBTの操作は結構負荷が高いというけど、エンティティの違いによってその負荷も違うのかどうかを確認したい。
ゾンビ、村人、アマスタ、AEC、ディスプレイ3種、マーカー、データを持たせたマーカー、インテラクションの計10種で実行。アクセスの負荷をそろえるためにUUIDで指定。失敗することのないように2回づつ実行させている。
条件1 (zombie)
summon zombie ~ ~ ~ {UUID:[I;0,0,0,0]}条件2 (villager)
summon villager ~ ~ ~ {UUID:[I;0,0,0,1]}条件3 (armor_stand)
summon armor_stand ~ ~ ~ {UUID:[I;0,0,0,2]}条件4 (area_effect_cloud)
summon area_effect_cloud ~ ~ ~ {UUID:[I;0,0,0,3]}条件5 (block_display)
summon block_display ~ ~ ~ {UUID:[I;0,0,0,4]}条件6 (item_display)
summon item_display ~ ~ ~ {UUID:[I;0,0,0,5]}条件7 (text_display)
summon text_display ~ ~ ~ {UUID:[I;0,0,0,6]}条件8 (marker)
summon marker ~ ~ ~ {UUID:[I;0,0,0,7]}条件9 (marker+data)
summon marker ~ ~ ~ {UUID:[I;0,0,0,8],data:{0:"A",1:"A",2:"A",3:"A",4:"A",5:"A",6:"A",7:"A",8:"A",9:"A"}}条件10 (interaction)
summon interaction ~ ~ ~ {UUID:[I;0,0,0,0]}A
data modify entity 0-0-0-0-0 Motion[1] set value 0.1d
data modify entity 0-0-0-0-0 Motion[1] set value 0.0d| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| zombie | A | 6.158 ±0.163 |
| villager | A | 52.481 ±0.669 |
| armor_stand | A | 5.915 ±0.124 |
| area_effect_cloud | A | 3.251 ±0.079 |
| block_display | A | 9.371 ±0.125 |
| item_display | A | 9.476 ±0.254 |
| text_display | A | 12.195 ±0.080 |
| marker | A | 2.267 ±0.017 |
| marker+data | A | 3.057 ±0.061 |
| interaction | A | 2.889 ±0.029 |
| baseline | 0.229 ±0.002 |
かなり差があるのが面白い。特に村人の負荷が高く、次点でディスプレイエンティティの負荷が高い。逆に軽いのはmarkerで、データを持たせたマーカーはわずかに負荷が高くなっている。NBT操作の負荷はおそらくもともと持っているNBTの量に強く依存していそう。取得の負荷もそのうち検証したい。(らすく)
villagerとzombie_villagerだけNBTからデータを読み取る際に脳の一新(全ての振る舞いの停止、脳の複製、目標の再登録)が行われるのが関係している気がする🔗。
display系が遅いのは、その中でも特にtext_displayが遅いことから推測すると、NBTに変換されるエンティティの同期データの数が多いことが原因の可能性がありそう。同期データの取得時にはロックによる排他制御が行われるので、これがボトルネックになっている可能性がある。(intsuc)
▲戻る
エンティティのNBTを直接操作するとそれなりに負荷が高いので、操作のかるいストレージに移してからまとめてエンティティに適用させるという方法がある。PosやRotationなどのリストなどでよく使われるため、試しにそれらでどれくらい軽くなるのかを検証したい。
ゾンビ、村人、アイテムディスプレイ、マーカーの4種を召喚。
条件1 (zombie)
summon zombie ~ ~ ~ {UUID:[I;0,0,0,0]}
scoreboard players set _ A 1
scoreboard players set _ B 0条件2 (vilalger)
summon vilalger ~ ~ ~ {UUID:[I;0,0,0,0]}
scoreboard players set _ A 1
scoreboard players set _ B 0条件3 (item_display)
summon item_display ~ ~ ~ {UUID:[I;0,0,0,0]}
scoreboard players set _ A 1
scoreboard players set _ B 0条件4 (marker)
summon marker ~ ~ ~ {UUID:[I;0,0,0,0]}
scoreboard players set _ A 1
scoreboard players set _ B 0A
execute store result entity 0-0-0-0-0 Rotation[0] double 1 run scoreboard players get _ A
execute store result entity 0-0-0-0-0 Rotation[1] double 1 run scoreboard players get _ A
execute store result entity 0-0-0-0-0 Rotation[0] double 1 run scoreboard players get _ B
execute store result entity 0-0-0-0-0 Rotation[1] double 1 run scoreboard players get _ BB
data modify storage _: A set value [0.0f,0.0f]
execute store result storage _: A[0] double 1 run scoreboard players get _ A
execute store result storage _: A[1] double 1 run scoreboard players get _ A
data modify entity 0-0-0-0-0 Rotation set from storage _: AC
execute store result entity 0-0-0-0-0 Pos[0] double 1 run scoreboard players get _ A
execute store result entity 0-0-0-0-0 Pos[1] double 1 run scoreboard players get _ A
execute store result entity 0-0-0-0-0 Pos[2] double 1 run scoreboard players get _ A
execute store result entity 0-0-0-0-0 Pos[0] double 1 run scoreboard players get _ B
execute store result entity 0-0-0-0-0 Pos[1] double 1 run scoreboard players get _ B
execute store result entity 0-0-0-0-0 Pos[2] double 1 run scoreboard players get _ BD
data modify storage _: A set value [0.0d,0.0d,0.0d]
execute store result storage _: A[0] double 1 run scoreboard players get _ A
execute store result storage _: A[1] double 1 run scoreboard players get _ A
execute store result storage _: A[2] double 1 run scoreboard players get _ A
data modify entity 0-0-0-0-0 Pos set from storage _: A
data modify storage _: A set value [0.0d,0.0d,0.0d]
execute store result storage _: A[0] double 1 run scoreboard players get _ B
execute store result storage _: A[1] double 1 run scoreboard players get _ B
execute store result storage _: A[2] double 1 run scoreboard players get _ B
data modify entity 0-0-0-0-0 Pos set from storage _: A| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| zombie | A | 12.894 ±0.126 |
| B | 7.346 ±0.283 | |
| C | 19.303 ±0.554 | |
| D | 9.999 ±0.292 | |
| villager | A | 103.936 ±1.035 |
| B | 8.390 ±0.141 | |
| C | 156.593 ±1.939 | |
| D | 56.498 ±1.276 | |
| item_display | A | 19.968 ±0.391 |
| B | 8.416 ±0.129 | |
| C | 29.681 ±0.866 | |
| D | 13.045 ±0.687 | |
| marker | A | 5.213 ±0.053 |
| B | 5.280 ±0.126 | |
| C | 7.757 ±0.208 | |
| D | 5.883 ±0.192 | |
| baseline | 0.232 ±0.004 |
村人はNBTの操作が極端に重いので極端な値が出たが、marker以外はおおよそ倍近くの軽量化になっている。逆にマーカーはNBT変更の負荷が少ないからか、Rotation(サイズ2のリスト)ではほぼ同程度、Posでは1.5倍程度の差になった。総評としてはやり得と言っていいかもしれない。
▲戻る
エンティティのNBT操作において、データの型によって違うのかどうか確認しておきたい。リストとコンパウンドは条件が複雑になりかねないので今回は除外。
byte,int,short,float,double,string型をdataに持たせたマーカーを1体召喚。失敗することのないように2回づつ実行させている。
summon marker ~ ~ ~ {UUID:[I;0,0,0,0],data:{byte:0b,int:0,short:0s,float:0.0f,double:0.0d,string:"0"}}A
data modify entity 0-0-0-0-0 data.byte set value 1b
data modify entity 0-0-0-0-0 data.byte set value 0bB
data modify entity 0-0-0-0-0 data.int set value 1
data modify entity 0-0-0-0-0 data.int set value 0C
data modify entity 0-0-0-0-0 data.short set value 1s
data modify entity 0-0-0-0-0 data.short set value 0sD
data modify entity 0-0-0-0-0 data.float set value 1.0f
data modify entity 0-0-0-0-0 data.float set value 0.0fE
data modify entity 0-0-0-0-0 data.double set value 1.0d
data modify entity 0-0-0-0-0 data.double set value 0.0dF
data modify entity 0-0-0-0-0 data.string set value "1"
data modify entity 0-0-0-0-0 data.string set value "0"| Function | Results v1.20 [us/op] |
|---|---|
| A | 2.890 ±0.073 |
| B | 2.883 ±0.070 |
| C | 2.936 ±0.059 |
| D | 2.873 ±0.047 |
| E | 2.975 ±0.070 |
| F | 2.782 ±0.093 |
| baseline | 0.229 ±0.002 |
ほとんど誤差の範囲内の差なので、おそらく違いはなさそう。コンパウンドやリスト何かになるとさすがに変わるかも?
▲戻る
マーカーをはじめとしたエンティティを召喚削除するときのコストを知りたい。
とくに追加する事前実行はなし。DeathTimeのあるエンティティは高頻度の召喚削除に向かないので今回は除外。
A
summon marker ~ ~ ~ {UUID:[I;0,0,0,0]}
kill 0-0-0-0-0B
summon item_display ~ ~ ~ {UUID:[I;0,0,0,0]}
kill 0-0-0-0-0C
summon block_display ~ ~ ~ {UUID:[I;0,0,0,0]}
kill 0-0-0-0-0D
summon text_display ~ ~ ~ {UUID:[I;0,0,0,0],alignment:"center"}
kill 0-0-0-0-0E
summon area_effect_cloud ~ ~ ~ {UUID:[I;0,0,0,0]}
kill 0-0-0-0-0F
summon interaction ~ ~ ~ {UUID:[I;0,0,0,0]}
kill 0-0-0-0-0| Function | Results v1.20 [us/op] |
|---|---|
| A | 2.648 ±0.047 |
| B | 3.399 ±0.027 |
| C | 3.335 ±0.040 |
| D | 3.703 ±0.052 |
| E | 2.980 ±0.057 |
| F | 2.974 ±0.020 |
| baseline | 0.234 ±0.007 |
召喚削除の繰り返しにおいてもmarkerが強い。interactionもそこそこ軽い。
▲戻る
tagやスコア、チームなど、何かしらフラグを与えることがあると思いますが、付け消しにどれくらいコストの差があるのか知っておきたい
ゾンビを1体だけ召喚して、付け消しで比較
summon zombie ~ ~ ~ {UUID:[I;0,0,0,0]}A
execute as 0-0-0-0-0 run tag @s add A
execute as 0-0-0-0-0 run tag @s remove AB
execute as 0-0-0-0-0 run scoreboard players set @s A 1
execute as 0-0-0-0-0 run scoreboard players set @s A 0C
execute as 0-0-0-0-0 run team join A
execute as 0-0-0-0-0 run team leave A| Function | Results v1.20 [us/op] |
|---|---|
| A | 1.651 ±0.022 |
| B | 1.754 ±0.036 |
| C | 1.359 ±0.042 |
| baseline | 0.223 ±0.009 |
大きな差はないが、teamの付け消しが一番軽いようで、次点がtagだった。
▲戻る
tp,teleport,Pos書き換えのうちどれがどのくらいの負荷なのか知りたい。
ゾンビ、アマスタ、アイテムディスプレイ、マーカー、インタラクションの5種を召喚して検証
条件1 (zombie)
summon zombie ~ ~ ~ {UUID:[I;0,0,0,0]}条件2 (armor_stand)
summon armor_stand ~ ~ ~ {UUID:[I;0,0,0,0]}条件3 (item_display)
summon item_display ~ ~ ~ {UUID:[I;0,0,0,0]}条件4 (marker)
summon marker ~ ~ ~ {UUID:[I;0,0,0,0]}条件5 (interaction)
summon interaction ~ ~ ~ {UUID:[I;0,0,0,0]}A
tp 0-0-0-0-0 1.0 64.0 0.0
tp 0-0-0-0-0 0.0 64.0 0.0B
teleport 0-0-0-0-0 1.0 64.0 0.0
teleport 0-0-0-0-0 0.0 64.0 0.0C
data modify entity 0-0-0-0-0 Pos set value [1.0d,64.0d,0.0d]
data modify entity 0-0-0-0-0 Pos set value [0.0d,64.0d,0.0d]| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| zombie | A | 3.129 ±0.046 |
| B | 3.058 ±0.025 | |
| C | 6.234 ±0.115 | |
| armor_stand | A | 3.155 ±0.020 |
| B | 3.137 ±0.176 | |
| C | 5.986 ±0.291 | |
| item_display | A | 3.027 ±0.036 |
| B | 2.999 ±0.024 | |
| C | 9.343 ±0.824 | |
| marker | A | 2.895 ±0.058 |
| B | 2.864 ±0.033 | |
| C | 2.462 ±0.100 | |
| interaction | A | 3.177 ±0.021 |
| B | 3.090 ±0.033 | |
| C | 3.001 ±0.017 | |
| baseline | 0.230 ±0.003 |
tp/teleportの負荷はエンティティの種類による違いはほとんどなさそう。心なしかディスプレイやマーカーは軽いかも?/tpコマンドは/teleportの短縮コマンドとしてあるので、若干本体よりも実行負荷が高いっぽい。そこまで大きな差ではないので、塵積程度だと思ってよさそう。
▲戻る
エンティティの上にエンティティを乗せて、下側のエンティティを動かすことで乗っているエンティティも一緒に動かすことができる。その際、乗っているエンティティの数に伴ってどれくらい負荷が増えるかを確認したい。
item_displayに0~10体のitem_displayを乗せて検証。
条件1 (0体)
summon item_display ~ ~ ~ {UUID: [I; 0, 0, 0, 0]}条件2 (1体)
summon item_display ~ ~ ~ {UUID: [I; 0, 0, 0, 0], Passengers: [{id: "item_display"}]}条件3 (10体)
summon item_display ~ ~ ~ {UUID: [I; 0, 0, 0, 0], Passengers: [{id: "item_display"}, {id: "item_display"}, {id: "item_display"}, {id: "item_display"}, {id: "item_display"}, {id: "item_display"}, {id: "item_display"}, {id: "item_display"}, {id: "item_display"}, {id: "item_display"}]}A
tp 0-0-0-0-0 1.0 64.0 0.0
tp 0-0-0-0-0 0.0 64.0 0.0| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 0体 | A | 3.044 ±0.069 |
| 1体 | A | 3.467 ±0.057 |
| 10体 | A | 5.877 ±0.067 |
| baseline | 0.229 ±0.003 |
やはり乗せれば乗せるほど負荷は増える。それでもtpを数コマンドとかよりは軽そう。
▲戻る
on passengerで乗っているエンティティの方に実行者を移すことができる。条件はあれど、これによって簡単に軽く複数のエンティティを結びつけることができるので結構重宝する。従来使われていたスコアでの紐づけからどれくらい軽くなりえるか確認したい。
アイテムディスプレイに1~10体のゾンビを乗せた物を2体召喚。交互に紐づけを行う。
条件1 (1体)
summon item_display ~ ~ ~ {UUID:[I;0,0,0,0]}
summon item_display ~ ~ ~ {UUID:[I;0,0,0,1]}
summon zombie ~ ~ ~ {Tags:[_,A]}
summon zombie ~ ~ ~ {Tags:[_,B]}
execute as @e[tag=A] run ride @s mount 0-0-0-0-0
execute as @e[tag=B] run ride @s mount 0-0-0-0-1
execute as 0-0-0-0-0 run scoreboard players set @s A 1
execute as 0-0-0-0-1 run scoreboard players set @s A 2
scoreboard players set @e[tag=A] A 1
scoreboard players set @e[tag=B] A 2条件2 (10体)
summon item_display ~ ~ ~ {UUID:[I;0,0,0,0]}
summon item_display ~ ~ ~ {UUID:[I;0,0,0,1]}
summon zombie ~ ~ ~ {Tags:[_,A]}
×10
summon zombie ~ ~ ~ {Tags:[_,B]}
×10
execute as @e[tag=A] run ride @s mount 0-0-0-0-0
execute as @e[tag=B] run ride @s mount 0-0-0-0-1
execute as 0-0-0-0-0 run scoreboard players set @s A 1
execute as 0-0-0-0-1 run scoreboard players set @s A 2
scoreboard players set @e[tag=A] A 1
scoreboard players set @e[tag=B] A 2A
execute as @e[tag=A,type=zombie] if entity @s
execute as @e[tag=B,type=zombie] if entity @sB
execute as 0-0-0-0-0 run scoreboard players operation _ A = @s A
execute as @e[tag=_,type=zombie] if score @s A = _ A if entity @s
execute as 0-0-0-0-1 run scoreboard players operation _ A = @s A
execute as @e[tag=_,type=zombie] if score @s A = _ A if entity @sC
execute as 0-0-0-0-0 on passengers if entity @s
execute as 0-0-0-0-1 on passengers if entity @s| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1体 | A | 1.794 ±0.031 |
| B | 5.383 ±0.160 | |
| C | 2.410 ±0.054 | |
| 10体 | A | 9.234 ±0.122 |
| B | 27.495 ±0.711 | |
| C | 8.754 ±0.072 | |
| baseline | 0.227 ±0.002 |
対象エンティティが増えるほど、セレクタでの検索の負荷が増えるので10体での実行では結構差が出ている。普通に2~3倍の差があるのは大きい。
▲戻る
on originではAECやアイテムなどのOwnerやThrowerに設定したエンティティに実行者を移すことができる。on passengerと違って複数体を紐づけることはできないが、座標の離れたエンティティを紐づけることができる点で重宝する。従来使われていたスコアでの紐づけからどれくらい軽くなりえるか確認したい。
アイテムに1体のゾンビを紐づけたのを2セット召喚。交互に紐づけを行う。
summon item ~ ~ ~ {UUID:[I;0,0,0,0],Thrower:[I;0,0,0,2],Item:{id:"stick",Count:1b}}
summon item ~ ~ ~ {UUID:[I;0,0,0,1],Thrower:[I;0,0,0,3],Item:{id:"stick",Count:1b}}
summon zombie ~ ~ ~ {Tags:[_,A],UUID:[I;0,0,0,2]}
summon zombie ~ ~ ~ {Tags:[_,B],UUID:[I;0,0,0,3]}
execute as @e[tag=A] run ride @s mount 0-0-0-0-0
execute as @e[tag=B] run ride @s mount 0-0-0-0-1
execute as 0-0-0-0-0 run scoreboard players set @s A 1
execute as 0-0-0-0-1 run scoreboard players set @s A 2
scoreboard players set @e[tag=A] A 1
scoreboard players set @e[tag=B] A 2A
execute as @e[tag=A,type=zombie] if entity @s
execute as @e[tag=B,type=zombie] if entity @sB
execute as 0-0-0-0-0 run scoreboard players operation _ A = @s A
execute as @e[tag=_,type=zombie] if score @s A = _ A if entity @s
execute as 0-0-0-0-1 run scoreboard players operation _ A = @s A
execute as @e[tag=_,type=zombie] if score @s A = _ A if entity @sC
execute as 0-0-0-0-0 on origin if entity @s
execute as 0-0-0-0-1 on origin if entity @s| Function | Results v1.20 [us/op] |
|---|---|
| A | 1.768 ±0.014 |
| B | 5.892 ±0.109 |
| C | 2.241 ±0.043 |
| baseline | 0.232 ±0.001 |
こちらも差としては2倍程度で大きい。
▲戻る
on passengerとon originを使って複数の離れたエンティティを検索したときはどうか。実際はエンティティ数が増えるのでそれによる影響もあるだろうが、今回は同じエンティティ存在条件のもとで試してる。
アイテムディスプレイに1~10体のアイテムを乗せ、さらにそのアイテムにゾンビを紐づけたのを2セット召喚。交互に紐づけを行う。
条件1 (1体)
summon item_display ~ ~ ~ {UUID:[I;0,0,0,0]}
summon item_display ~ ~ ~ {UUID:[I;0,0,0,1]}
summon item ~ ~ ~ {Tags:[A],Thrower:[I;0,1,0,0],Item:{id:"stick",Count:1b}}
summon item ~ ~ ~ {Tags:[B],Thrower:[I;0,2,0,0],Item:{id:"stick",Count:1b}}
summon zombie ~ ~ ~ {Tags:[_,A],UUID:[I;0,1,0,0]}
summon zombie ~ ~ ~ {Tags:[_,B],UUID:[I;0,2,0,0]}
execute as @e[tag=A,type=item] run ride @s mount 0-0-0-0-0
execute as @e[tag=B,type=item] run ride @s mount 0-0-0-0-1
execute as 0-0-0-0-0 run scoreboard players set @s A 1
execute as 0-0-0-0-1 run scoreboard players set @s A 2
scoreboard players set @e[tag=A,type=zombie] A 1
scoreboard players set @e[tag=B,type=zombie] A 2条件2 (10体) ゾンビのUUIDはそれぞれに別のを設定している
summon item_display ~ ~ ~ {UUID:[I;0,0,0,0]}
summon item_display ~ ~ ~ {UUID:[I;0,0,0,1]}
summon item ~ ~ ~ {Tags:[A],Thrower:[I;0,1,0,0],Item:{id:"stick",Count:1b}}
×10
summon item ~ ~ ~ {Tags:[B],Thrower:[I;0,2,0,0],Item:{id:"stick",Count:1b}}
×10
summon zombie ~ ~ ~ {Tags:[_,A],UUID:[I;0,1,0,0]}
×10
summon zombie ~ ~ ~ {Tags:[_,B],UUID:[I;0,2,0,0]}
×10
execute as @e[tag=A,type=item] run ride @s mount 0-0-0-0-0
execute as @e[tag=B,type=item] run ride @s mount 0-0-0-0-1
execute as 0-0-0-0-0 run scoreboard players set @s A 1
execute as 0-0-0-0-1 run scoreboard players set @s A 2
scoreboard players set @e[tag=A,type=zombie] A 1
scoreboard players set @e[tag=B,type=zombie] A 2A
execute as @e[tag=A,type=zombie] if entity @s
execute as @e[tag=B,type=zombie] if entity @sB
execute as 0-0-0-0-0 run scoreboard players operation _ A = @s A
execute as @e[tag=_,type=zombie] if score @s A = _ A if entity @s
execute as 0-0-0-0-1 run scoreboard players operation _ A = @s A
execute as @e[tag=_,type=zombie] if score @s A = _ A if entity @sC
execute as 0-0-0-0-0 on passengers on origin if entity @s
execute as 0-0-0-0-1 on passengers on origin if entity @s| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1体 | A | 1.828 ±0.059 |
| B | 5.329 ±0.220 | |
| C | 3.048 ±0.032 | |
| 10体 | A | 9.479 ±0.131 |
| B | 28.212 ±0.543 | |
| C | 15.074 ±0.104 | |
| baseline | 0.228 ±0.005 |
差は小さくなったが、それでもまだexecute onを利用した方が全然軽い。エンティティの総数が10体増えたくらいの時の負荷のオーダーは結構小さいので、10体程度であればonに軍配が上がりそうだが、数百体くらいエンティティ数が増えることになるとさすがにダメそう。
▲戻る
on originの相手がいるときといないときでon originの負荷が変わるのかどうか。
アイテムに1体のアイテムを紐づけたのを召喚。Throwerに設定する対象を変えて検証。
条件1
別のエンティティをThrowerに設定する
summon item ~ ~ ~ {UUID:[I;0,0,0,0],Item:{id:"stick",Count:1b}}
summon item ~ ~ ~ {UUID:[I;0,0,0,1],Thrower:[I;0,0,0,0],Item:{id:"stick",Count:1b}}条件2
自分をThrowerに設定する
summon item ~ ~ ~ {UUID:[I;0,0,0,0],Item:{id:"stick",Count:1b}}
summon item ~ ~ ~ {UUID:[I;0,0,0,1],Thrower:[I;0,0,0,1],Item:{id:"stick",Count:1b}}条件3
どのエンティティも設定しない
summon item ~ ~ ~ {UUID:[I;0,0,0,0],Item:{id:"stick",Count:1b}}
summon item ~ ~ ~ {UUID:[I;0,0,0,1],Item:{id:"stick",Count:1b}}A
execute as 0-0-0-0-1 on origin if entity @s| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1 | A | 1.430 ±0.015 |
| 2 | A | 1.154 ±0.022 |
| 3 | A | 0.876 ±0.020 |
| baseline | 0.233 ±0.001 |
失敗するケースが必ずしも重くなるというわけではないようで、例えばこれのようにexecuteの終端ではないサブコマンドであれば逆に軽くなるのかもしれない。あとなんでかわからないけど自己循環させた時の方が軽い点が面白い。(らすく)
/execute as 0-0-0-0-0 on origin …でoriginが自分自身の場合にそうでない場合より速くなったのはおそらくキャッシュの影響だと思う。まず/execute as 0-0-0-0-0でUUID 0-0-0-0-0を使ってエンティティの検索が行われてこの際にアクセスしたオブジェクトがキャッシュに載る。次に/execute on originで再びUUID 0-0-0-0-0を使ってエンティティの検索が行われるので、この際にアクセスするオブジェクトがキャッシュに載っている可能性が高い。(intsuc)
▲戻る
いろいろ測定してみると、エンティティの種類によってデータの取得や操作などに大きな差があり、もしかしたら種類によってはエンティティから直接Posとかを取得するよりマーカーを経由した方が軽くなるんじゃないか?と思ったので、テキストディスプレイで検証した。
2箇所にテキストディスプレイを配置。
summon text_display ~ ~ ~ {UUID:[I;0,0,0,0]}
summon text_display ~ 1 ~ {UUID:[I;0,0,0,1]}
summon marker ~ ~ ~ {UUID:[I;0,0,0,2]}A
data modify storage _: A set from entity 0-0-0-0-0 Pos
data modify storage _: A set from entity 0-0-0-0-1 PosB
tp 0-0-0-0-2 0-0-0-0-0
data modify storage _: A set from entity 0-0-0-0-2 Pos
tp 0-0-0-0-2 0-0-0-0-1
data modify storage _: A set from entity 0-0-0-0-2 Pos| Function | Results v1.20 [us/op] |
|---|---|
| A | 5.412 ±0.207 |
| B | 5.468 ±0.080 |
| baseline | 0.233 ±0.004 |
テキストディスプレイだとピッタリ同じくらい。前に検証したエンティティ別のnbt操作やtpのスコアから計算してもちょうどこれくらいなので妥当。テキストディスプレイはnbt取得や操作がそこそこ重たい方なのでこの結果になったが、軽いエンティティならマーカーを経由しない方が断然良い。ただ、プレイヤーなどの取得が重たそう(未検証)なエンティティから取る時はマーカーを経由した方がよい可能性がある。
▲戻る
前項と似たような理由で、Posで指定した座標にエンティティを設置したいとき、エンティティのPosに直接座標を入れるのとマーカーのPosに入れてからマーカーの位置にtpさせるのとでどれくらい変わりうるかを数値化しておきたい。nbt取得と違ってnbt変更はかなりエンティティによって差が大きく、ディスプレイエンティティとか村人が特に重たいことが分かっている。今回はなんとなくテキストディスプレイでやってみる。
テキストディスプレイとマーカーを配置。
summon text_display ~ ~ ~ {UUID:[I;0,0,0,0]}
summon marker ~ ~ ~ {UUID:[I;0,0,0,1]}A
data modify entity 0-0-0-0-0 Pos set value [0.0d,64.0d,0.0d]
data modify entity 0-0-0-0-0 Pos set value [0.0d,65.0d,0.0d]B
data modify entity 0-0-0-0-1 Pos set value [0.0d,64.0d,0.0d]
tp 0-0-0-0-0 0-0-0-0-1
data modify entity 0-0-0-0-1 Pos set value [0.0d,65.0d,0.0d]
tp 0-0-0-0-0 0-0-0-0-1| Function | Results v1.20 [us/op] |
|---|---|
| A | 12.480 ±0.229 |
| B | 5.946 ±0.034 |
| baseline | 0.228 ±0.002 |
そもそもテキストディスプレイとマーカーではnbt操作の負荷が6倍くらい違うので、やはりこの検証でもマーカー経由の移動の方が良い結果が出ている。ディスプレイエンティティはnbt変更が比較的重たい部類なのでここまで差が出たが、普通のエンティティなら差が縮まる、あるいは逆転すると思われる。逆に村人などの以上に負荷の高いエンティティが相手なら、マーカー経由がオススメできる。ただもちろんtpとPos直接変更で描画的な部分が変わってくる可能性もあるので、一概に代替できるとは言えない。
▲戻る
スコアボードの計算は何種類か用意されているが、どれがどれくらいの負荷になるのか調べたい。
スコアホルダー_にAとBをセットして計算。
scoreboard players set _ A 0
scoreboard players set _ B 1A
scoreboard players set _ A 1B
scoreboard players add _ A 1C
scoreboard players remove _ A 1D
scoreboard players operation _ A = _ BE
scoreboard players operation _ A += _ BF
scoreboard players operation _ A -= _ BG
scoreboard players operation _ A *= _ BH
scoreboard players operation _ A /= _ BI
scoreboard players operation _ A %= _ BJ
scoreboard players operation _ A < _ BK
scoreboard players operation _ A > _ BL
scoreboard players operation _ A >< _ B| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.305 ±0.010 |
| B | 0.313 ±0.010 |
| C | 0.309 ±0.010 |
| D | 0.333 ±0.017 |
| E | 0.344 ±0.010 |
| F | 0.345 ±0.023 |
| G | 0.325 ±0.003 |
| H | 0.352 ±0.016 |
| I | 0.354 ±0.022 |
| J | 0.340 ±0.010 |
| K | 0.341 ±0.017 |
| L | 0.370 ±0.009 |
| baseline | 0.241 ±0.004 |
baselineの値を差し引くと全体的に60~100ns程度の処理速度になる。前項で行っていたエンティティのデータ操作などが同程度のbaseline下で数千nsだったのと比べると相当軽いことがわかる。set,add,removeは単純なのでやはりoperationと比べると若干早く、operationの中だとLの入れ替えが一番遅いっぽい。2つのスコアを変更しているので妥当だと思う。
▲戻る
スコアホルダー(スコアを所有する名前)には、現存するエンティティやプレイヤーの名前以外でも適当に名前を与えることができる。そのスコアホルダーを増やしたときにどのように負荷に影響してくるのかを調べたい。また、ワイルドカード*を使うことですべてのスコアホルダーのスコアを操作することができるが、その場合の負荷も測定したい。
設定スコアホルダーの数を1,10,100,1000個にして検証。
条件1 (1個)
scoreboard players set _ A 1
scoreboard players set _ B 1条件2 (10個)
scoreboard players set _ A 1
scoreboard players set _ B 1
scoreboard players set #0001 A 0
×9条件3 (100個)
scoreboard players set _ A 1
scoreboard players set _ B 1
scoreboard players set #0001 A 0
×99条件4 (1000個)
scoreboard players set _ A 1
scoreboard players set _ B 1
scoreboard players set #0001 A 0
×999A
scoreboard players add _ A 1B
scoreboard players operation _ A += _ BC
scoreboard players add * A 1| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1個 | A | 0.305 ±0.009 |
| B | 0.358 ±0.005 | |
| C | 0.348 ±0.002 | |
| 10個 | A | 0.305 ±0.001 |
| B | 0.332 ±0.005 | |
| C | 0.501 ±0.007 | |
| 100個 | A | 0.304 ±0.016 |
| B | 0.346 ±0.017 | |
| C | 2.169 ±0.049 | |
| 1000個 | A | 0.308 ±0.009 |
| B | 0.369 ±0.006 | |
| C | 24.464 ±0.338 | |
| baseline | 0.233 ±0.004 |
スコアホルダーが増えてもA,Bはほとんど増加していないように見える。ワイルドカードを使っているCではスコアホルダーの数に比例して処理時間も伸びているのがわかる。特にワイルドカードを常時実行などで使っていないなら、スコアホルダーを増やしてもそこまで負荷に影響が出ることはなさそう?ただ何かを大量に置いたら計算速度以外の部分で影響が出てくる可能性もゼロではないと思うので、そこは留意が必要か。
▲戻る
スコアボードオブジェクトを増やしたときに計算速度には影響してくるのかを調べたい。おそらく影響しないと思っているが...
オブジェクトの数を1,10,100個にして検証。オブジェクトA,Bはもともと追加している。追加した大量のオブジェクトはteardownで削除している。
条件1 (1個)
scoreboard players set _ A 1
scoreboard players set _ B 1条件2 (10個)
scoreboard players set _ A 1
scoreboard players set _ B 1
scoreboard objectives add 02 dummy
×9条件3 (100個)
scoreboard players set _ A 1
scoreboard players set _ B 1
scoreboard objectives add 02 dummy
×99A
scoreboard players add _ A 1B
scoreboard players operation _ A += _ BC
scoreboard players reset _| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1個 | A | 0.310 ±0.007 |
| B | 0.354 ±0.022 | |
| C | 0.269 ±0.004 | |
| 10個 | A | 0.308 ±0.006 |
| B | 0.349 ±0.018 | |
| C | 0.273 ±0.005 | |
| 100個 | A | 0.306 ±0.009 |
| B | 0.344 ±0.016 | |
| C | 0.263 ±0.009 | |
| baseline | 0.233 ±0.004 |
オブジェクトが増えてもとくに増加は見られず。計算速度には影響していなさそう。(らすく)
スコアホルダーの検索とオブジェクティブの検索はどちらもハッシュ値が内部テーブル内で十分に分散していればO(1)になるものの、分散していないために全ての要素N個が同じビンに入るとO(logN)になる。オブジェクティブはhashCodeを実装していないのでそのハッシュ値を制御するのは難しいけど、スコアホルダーはStringなので比較的容易にハッシュ値を制御できる。
つまりスコアホルダーやオブジェクティブが増えるほどパフォーマンスが低下する確率が上昇していく。オブジェクティブについては制御不可能で、スコアホルダーについては意図的にハッシュ値を衝突させればパフォーマンスを低下させられて、分散させればパフォーマンスが低下しないようにできる🔗。(intsuc)
▲戻る
スコアに対して同じ定数を代入するとき、storeを使って1コマンドで完結させる方法と分けて書く方法が考えられるけど、どっちの方がよさそうなのか検討したい。まずはsetで確認。
10個のスコアホルダーに数値を持たせた。
scoreboard players set #1 A 0
scoreboard players set #2 A 0
scoreboard players set #3 A 0
scoreboard players set #4 A 0
scoreboard players set #5 A 0
scoreboard players set #6 A 0
scoreboard players set #7 A 0
scoreboard players set #8 A 0
scoreboard players set #9 A 0
scoreboard players set #10 A 0A
scoreboard players set #1 A 1
scoreboard players set #2 A 1B
execute store result score #2 A run scoreboard players set #1 A 1C
scoreboard players set #1 A 1
scoreboard players set #2 A 1
scoreboard players set #3 A 1D
execute store result score #2 A store result score #3 A run scoreboard players set #1 A 1E
scoreboard players set #1 A 1
scoreboard players set #2 A 1
scoreboard players set #3 A 1
scoreboard players set #4 A 1
scoreboard players set #5 A 1
scoreboard players set #6 A 1
scoreboard players set #7 A 1
scoreboard players set #8 A 1
scoreboard players set #9 A 1
scoreboard players set #10 A 1F
execute store result score #2 A store result score #3 A store result score #4 A store result score #5 A store result score #6 A store result score #7 A store result score #8 A store result score #9 A store result score #10 A run scoreboard players set #1 A 1| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.374 ±0.005 |
| B | 0.398 ±0.009 |
| C | 0.411 ±0.029 |
| D | 0.495 ±0.012 |
| E | 0.848 ±0.064 |
| F | 0.930 ±0.030 |
| baseline | 0.229 ±0.002 |
数が増えても逆転する気配はなく、全体的にstoreの方が重たいという結果になった。setではなくoperationやdata getなどだと逆転する可能性もあるので次で検証。
▲戻る
スコアに対して同じ定数を代入するとき、storeを使って1コマンドで完結させる方法と分けて書く方法が考えられるけど、どっちの方がよさそうなのか検討したい。まずはsetで確認。
3個のスコアホルダーに数値を持たせ、取得元をセット。
scoreboard players set _ A 1
data modify storage _: A set value 1
scoreboard players set #1 A 0
scoreboard players set #2 A 0
scoreboard players set #3 A 0A
scoreboard players operation #1 A = _ A
scoreboard players operation #2 A = _ AB
execute store result score #2 A run scoreboard players operation #1 A = _ AC
scoreboard players operation #1 A = _ A
scoreboard players operation #2 A = _ A
scoreboard players operation #3 A = _ AD
execute store result score #2 A store result score #3 A run scoreboard players operation #1 A = _ AE
execute store result score #1 A run data get storage _: A
execute store result score #2 A run data get storage _: AF
execute store result score #1 A store result score #2 A run data get storage _: AG
execute store result score #1 A run data get storage _: A
execute store result score #2 A run data get storage _: A
execute store result score #3 A run data get storage _: AH
execute store result score #1 A store result score #2 A store result score #3 A run data get storage _: A| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.414 ±0.016 |
| B | 0.480 ±0.028 |
| C | 0.573 ±0.014 |
| D | 0.572 ±0.012 |
| E | 0.617 ±0.006 |
| F | 0.523 ±0.017 |
| G | 0.803 ±0.028 |
| H | 0.594 ±0.024 |
| baseline | 0.236 ±0.002 |
operationでは3個になるとほぼ同じくらいになったので、このあたりで逆転が起きそうな気がする。data getの方は2個の時点でstoreの方に軍配が上がる。さすがにもともとstoreありきの方法なのでdata getを複数書いた方が重たいのは必然か。
▲戻る
データの型や大きさによってdata getの負荷が変わるかどうかの検証。
get予定のデータを全部セット
data modify storage _: A.byte set value 1b
data modify storage _: A.int set value 1
data modify storage _: A.short set value 1s
data modify storage _: A.float set value 1.0f
data modify storage _: A.double set value 1.0d
data modify storage _: A.string_1 set value "0"
data modify storage _: A.string_10 set value "0123456789"
data modify storage _: A.string_100 set value "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
data modify storage _: A.compound_1 set value {00:1b}
data modify storage _: A.compound_10 set value {00:1b,01:1b,02:1b,03:1b,04:1b,05:1b,06:1b,07:1b,08:1b,09:1b}
data modify storage _: A.compound_100 set value {00:1b,01:1b,02:1b,03:1b,04:1b,05:1b,06:1b,07:1b,08:1b,09:1b,10:1b,11:1b,12:1b,13:1b,14:1b,15:1b,16:1b,17:1b,18:1b,19:1b,20:1b,21:1b,22:1b,23:1b,24:1b,25:1b,26:1b,27:1b,28:1b,29:1b,30:1b,31:1b,32:1b,33:1b,34:1b,35:1b,36:1b,37:1b,38:1b,39:1b,40:1b,41:1b,42:1b,43:1b,44:1b,45:1b,46:1b,47:1b,48:1b,49:1b,50:1b,51:1b,52:1b,53:1b,54:1b,55:1b,56:1b,57:1b,58:1b,59:1b,60:1b,61:1b,62:1b,63:1b,64:1b,65:1b,66:1b,67:1b,68:1b,69:1b,70:1b,71:1b,72:1b,73:1b,74:1b,75:1b,76:1b,77:1b,78:1b,79:1b,80:1b,81:1b,82:1b,83:1b,84:1b,85:1b,86:1b,87:1b,88:1b,89:1b,90:1b,91:1b,92:1b,93:1b,94:1b,95:1b,96:1b,97:1b,98:1b,99:1b}
data modify storage _: A.list_1 set value [1,]
data modify storage _: A.list_10 set value [1,1,1,1,1,1,1,1,1,1,]
data modify storage _: A.list_100 set value [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,]
A
data get storage _: A.byteB
data get storage _: A.intC
data get storage _: A.shortD
data get storage _: A.floatE
data get storage _: A.doubleF
data get storage _: A.string_1G
data get storage _: A.string_10H
data get storage _: A.string_100I
data get storage _: A.compound_1J
data get storage _: A.compound_10K
data get storage _: A.compound_100L
data get storage _: A.list_1M
data get storage _: A.list_10N
data get storage _: A.list_100| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.337 ±0.007 |
| B | 0.333 ±0.007 |
| C | 0.327 ±0.001 |
| D | 0.332 ±0.012 |
| E | 0.339 ±0.013 |
| F | 0.340 ±0.015 |
| G | 0.347 ±0.014 |
| H | 0.341 ±0.011 |
| I | 0.331 ±0.006 |
| J | 0.340 ±0.012 |
| K | 0.337 ±0.022 |
| L | 0.348 ±0.015 |
| M | 0.330 ±0.005 |
| N | 0.337 ±0.009 |
| baseline | 0.236 ±0.002 |
全体的に差が誤差の範囲レベル。intsucさんのベンチマーク結果でも現れているように、1.20のプレリリースの段階で改善が入ったのでここはかなり軽いみたい。
▲戻る
データの型によってdata modify set valueの負荷が変わるかどうかの検証。
変更先になるデータを準備
data modify storage _: A set value {byte:0b,int:0,short:0s,float:0.0f,double:0.0d,string:"0",compound:{00:0b},list:[0]}A
data modify storage _: A.byte set value 1b
data modify storage _: A.byte set value 0bB
data modify storage _: A.int set value 1
data modify storage _: A.int set value 0C
data modify storage _: A.short set value 1s
data modify storage _: A.short set value 0sD
data modify storage _: A.float set value 1.0f
data modify storage _: A.float set value 0.0fE
data modify storage _: A.double set value 1.0d
data modify storage _: A.double set value 0.0dF
data modify storage _: A.string set value "1"
data modify storage _: A.string set value "0"G
data modify storage _: A.compound set value {00:1b}
data modify storage _: A.compound set value {00:0b}H
data modify storage _: A.list set value [1]
data modify storage _: A.list set value [0]| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.726 ±0.037 |
| B | 0.714 ±0.027 |
| C | 0.750 ±0.028 |
| D | 0.716 ±0.004 |
| E | 0.726 ±0.025 |
| F | 0.719 ±0.022 |
| G | 1.008 ±0.041 |
| H | 0.783 ±0.028 |
| baseline | 0.232 ±0.004 |
コンパウンドが特別重いように見える。次点でリストが若干重たいか?それぞれ要素ひとつの状態なので、要素の多さによっても変わるかどうかを次で検証。あと失敗と成功ケースでの変化も調べたい。(らすく)
/data modify … set value <value>は基本的に<value>をコピーする(新しいNBTインスタンスを作成する)必要があるので、<value>が大きくなるほど遅くなるはず。ただし<value>が値タグ(EndTag/ByteTag/ShortTag/IntTag/LongTag/FloatTag/DoubleTag/StringTag)の場合は例外的にコピーされず、<value>のNBTインスタンスが再利用される。(intsuc)
▲戻る
コンパウンドの大きさによってdata modify set valueの負荷が変わるかどうかを検証。1個と100個のときで検証。(あとなんとなく元データとの差もかえてみた。)
変更先を準備。
data modify storage _: A set value {00:0b}A
data modify storage _: A set value {00:1b}
data modify storage _: A set value {00:0b}B
data modify storage _: A set value {00:0b}
data modify storage _: A set value {00:0b}C
data modify storage _: A set value {00:1b,01:1b,02:1b,03:1b,04:1b,05:1b,06:1b,07:1b,08:1b,09:1b,10:1b,11:1b,12:1b,13:1b,14:1b,15:1b,16:1b,17:1b,18:1b,19:1b,20:1b,21:1b,22:1b,23:1b,24:1b,25:1b,26:1b,27:1b,28:1b,29:1b,30:1b,31:1b,32:1b,33:1b,34:1b,35:1b,36:1b,37:1b,38:1b,39:1b,40:1b,41:1b,42:1b,43:1b,44:1b,45:1b,46:1b,47:1b,48:1b,49:1b,50:1b,51:1b,52:1b,53:1b,54:1b,55:1b,56:1b,57:1b,58:1b,59:1b,60:1b,61:1b,62:1b,63:1b,64:1b,65:1b,66:1b,67:1b,68:1b,69:1b,70:1b,71:1b,72:1b,73:1b,74:1b,75:1b,76:1b,77:1b,78:1b,79:1b,80:1b,81:1b,82:1b,83:1b,84:1b,85:1b,86:1b,87:1b,88:1b,89:1b,90:1b,91:1b,92:1b,93:1b,94:1b,95:1b,96:1b,97:1b,98:1b,99:1b}
data modify storage _: A set value {00:0b,01:1b,02:1b,03:1b,04:1b,05:1b,06:1b,07:1b,08:1b,09:1b,10:1b,11:1b,12:1b,13:1b,14:1b,15:1b,16:1b,17:1b,18:1b,19:1b,20:1b,21:1b,22:1b,23:1b,24:1b,25:1b,26:1b,27:1b,28:1b,29:1b,30:1b,31:1b,32:1b,33:1b,34:1b,35:1b,36:1b,37:1b,38:1b,39:1b,40:1b,41:1b,42:1b,43:1b,44:1b,45:1b,46:1b,47:1b,48:1b,49:1b,50:1b,51:1b,52:1b,53:1b,54:1b,55:1b,56:1b,57:1b,58:1b,59:1b,60:1b,61:1b,62:1b,63:1b,64:1b,65:1b,66:1b,67:1b,68:1b,69:1b,70:1b,71:1b,72:1b,73:1b,74:1b,75:1b,76:1b,77:1b,78:1b,79:1b,80:1b,81:1b,82:1b,83:1b,84:1b,85:1b,86:1b,87:1b,88:1b,89:1b,90:1b,91:1b,92:1b,93:1b,94:1b,95:1b,96:1b,97:1b,98:1b,99:1b}D
data modify storage _: A set value {00:1b,01:1b,02:1b,03:1b,04:1b,05:1b,06:1b,07:1b,08:1b,09:1b,10:1b,11:1b,12:1b,13:1b,14:1b,15:1b,16:1b,17:1b,18:1b,19:1b,20:1b,21:1b,22:1b,23:1b,24:1b,25:1b,26:1b,27:1b,28:1b,29:1b,30:1b,31:1b,32:1b,33:1b,34:1b,35:1b,36:1b,37:1b,38:1b,39:1b,40:1b,41:1b,42:1b,43:1b,44:1b,45:1b,46:1b,47:1b,48:1b,49:1b,50:1b,51:1b,52:1b,53:1b,54:1b,55:1b,56:1b,57:1b,58:1b,59:1b,60:1b,61:1b,62:1b,63:1b,64:1b,65:1b,66:1b,67:1b,68:1b,69:1b,70:1b,71:1b,72:1b,73:1b,74:1b,75:1b,76:1b,77:1b,78:1b,79:1b,80:1b,81:1b,82:1b,83:1b,84:1b,85:1b,86:1b,87:1b,88:1b,89:1b,90:1b,91:1b,92:1b,93:1b,94:1b,95:1b,96:1b,97:1b,98:1b,99:1b}
data modify storage _: A set value {00:0b,01:0b,02:0b,03:0b,04:0b,05:0b,06:0b,07:0b,08:0b,09:0b,10:0b,11:0b,12:0b,13:0b,14:0b,15:0b,16:0b,17:0b,18:0b,19:0b,20:0b,21:0b,22:0b,23:0b,24:0b,25:0b,26:0b,27:0b,28:0b,29:0b,30:0b,31:0b,32:0b,33:0b,34:0b,35:0b,36:0b,37:0b,38:0b,39:0b,40:0b,41:0b,42:0b,43:0b,44:0b,45:0b,46:0b,47:0b,48:0b,49:0b,50:0b,51:0b,52:0b,53:0b,54:0b,55:0b,56:0b,57:0b,58:0b,59:0b,60:0b,61:0b,62:0b,63:0b,64:0b,65:0b,66:0b,67:0b,68:0b,69:0b,70:0b,71:0b,72:0b,73:0b,74:0b,75:0b,76:0b,77:0b,78:0b,79:0b,80:0b,81:0b,82:0b,83:0b,84:0b,85:0b,86:0b,87:0b,88:0b,89:0b,90:0b,91:0b,92:0b,93:0b,94:0b,95:0b,96:0b,97:0b,98:0b,99:0b}E
data modify storage _: A set value {00:0b,01:0b,02:0b,03:0b,04:0b,05:0b,06:0b,07:0b,08:0b,09:0b,10:0b,11:0b,12:0b,13:0b,14:0b,15:0b,16:0b,17:0b,18:0b,19:0b,20:0b,21:0b,22:0b,23:0b,24:0b,25:0b,26:0b,27:0b,28:0b,29:0b,30:0b,31:0b,32:0b,33:0b,34:0b,35:0b,36:0b,37:0b,38:0b,39:0b,40:0b,41:0b,42:0b,43:0b,44:0b,45:0b,46:0b,47:0b,48:0b,49:0b,50:0b,51:0b,52:0b,53:0b,54:0b,55:0b,56:0b,57:0b,58:0b,59:0b,60:0b,61:0b,62:0b,63:0b,64:0b,65:0b,66:0b,67:0b,68:0b,69:0b,70:0b,71:0b,72:0b,73:0b,74:0b,75:0b,76:0b,77:0b,78:0b,79:0b,80:0b,81:0b,82:0b,83:0b,84:0b,85:0b,86:0b,87:0b,88:0b,89:0b,90:0b,91:0b,92:0b,93:0b,94:0b,95:0b,96:0b,97:0b,98:0b,99:0b}
data modify storage _: A set value {00:0b,01:0b,02:0b,03:0b,04:0b,05:0b,06:0b,07:0b,08:0b,09:0b,10:0b,11:0b,12:0b,13:0b,14:0b,15:0b,16:0b,17:0b,18:0b,19:0b,20:0b,21:0b,22:0b,23:0b,24:0b,25:0b,26:0b,27:0b,28:0b,29:0b,30:0b,31:0b,32:0b,33:0b,34:0b,35:0b,36:0b,37:0b,38:0b,39:0b,40:0b,41:0b,42:0b,43:0b,44:0b,45:0b,46:0b,47:0b,48:0b,49:0b,50:0b,51:0b,52:0b,53:0b,54:0b,55:0b,56:0b,57:0b,58:0b,59:0b,60:0b,61:0b,62:0b,63:0b,64:0b,65:0b,66:0b,67:0b,68:0b,69:0b,70:0b,71:0b,72:0b,73:0b,74:0b,75:0b,76:0b,77:0b,78:0b,79:0b,80:0b,81:0b,82:0b,83:0b,84:0b,85:0b,86:0b,87:0b,88:0b,89:0b,90:0b,91:0b,92:0b,93:0b,94:0b,95:0b,96:0b,97:0b,98:0b,99:0b}| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.947 ±0.023 |
| B | 1.275 ±0.011 |
| C | 6.948 ±0.253 |
| D | 7.195 ±0.561 |
| E | 10.554 ±0.122 |
| baseline | 0.232 ±0.005 |
やはり長さが変わるほど処理時間も増加している。また失敗時の負荷増加もデータ量の多さに比例して増えているように見える。(らすく)
/data modify … set …はターゲットNBTとソースNBTが比較されるので、この比較の速さも全体的な速さの要因になるはず。例えばCompoundTagの場合、サイズが異なると内容を比較する前に異なると判定される。サイズが同じ場合は、ソースNBTのエントリ(キーと値)それぞれについてターゲットNBTに同じキーが存在するかどうか、存在するならそのソースの値とそのキーに対応するターゲットの値が再帰的に比較される。この際のエントリの順番は仕様上は保証されていないものの、実装上は順番を予測できるので、この順番を制御することでパフォーマンスが変わる可能性がある。(intsuc)
▲戻る
リストの長さによってdata modify set valueの負荷が変わるかどうかを検証。1個と100個のときで検証。(あとなんとなく元データとの差もかえてみた。)
変更先を準備。
data modify storage _: A set value [0,]A
data modify storage _: A set value [1,]
data modify storage _: A set value [0,]B
data modify storage _: A set value [0,]
data modify storage _: A set value [0,]C
data modify storage _: A set value [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,]
data modify storage _: A set value [0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,]D
data modify storage _: A set value [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,]
data modify storage _: A set value [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]E
data modify storage _: A set value [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]
data modify storage _: A set value [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.738 ±0.025 |
| B | 1.061 ±0.021 |
| C | 1.159 ±0.023 |
| D | 1.167 ±0.011 |
| E | 1.539 ±0.019 |
| baseline | 0.229 ±0.002 |
コンパウンドの時ほど差は大きくないが、負荷がやや増えている。またコンパウンドの時と違って失敗時の負荷増加は1個と100個で変わらないように見える。(らすく)
ListTagもCompoundTagと同様にサイズが異なる場合は内容を比較する前に異なると判定される。CompoundTagと異なるのは要素の比較順が分かりやすく(ただおそらく仕様にはどのような順番で比較すべきかは明記されていない?)、先頭から順に比較される。そのため、異なる要素がListTagの最後の方に来るとより遅くなると思う。(intsuc)
▲戻る
コンパウンドはmergeを使って変更することができる。mergeするデータの量でどれくらい負荷が変わるか確認したい。
マージされる側のデータも長さを変えて検証。
条件1(1)
data modify storage _: A set value {00:0b}条件2(10)
data modify storage _: A set value {00:0b,01:0b,02:0b,03:0b,04:0b,05:0b,06:0b,07:0b,08:0b,09:0b}条件3(100)
data modify storage _: A set value {00:0b,01:0b,02:0b,03:0b,04:0b,05:0b,06:0b,07:0b,08:0b,09:0b,10:0b,11:0b,12:0b,13:0b,14:0b,15:0b,16:0b,17:0b,18:0b,19:0b,20:0b,21:0b,22:0b,23:0b,24:0b,25:0b,26:0b,27:0b,28:0b,29:0b,30:0b,31:0b,32:0b,33:0b,34:0b,35:0b,36:0b,37:0b,38:0b,39:0b,40:0b,41:0b,42:0b,43:0b,44:0b,45:0b,46:0b,47:0b,48:0b,49:0b,50:0b,51:0b,52:0b,53:0b,54:0b,55:0b,56:0b,57:0b,58:0b,59:0b,60:0b,61:0b,62:0b,63:0b,64:0b,65:0b,66:0b,67:0b,68:0b,69:0b,70:0b,71:0b,72:0b,73:0b,74:0b,75:0b,76:0b,77:0b,78:0b,79:0b,80:0b,81:0b,82:0b,83:0b,84:0b,85:0b,86:0b,87:0b,88:0b,89:0b,90:0b,91:0b,92:0b,93:0b,94:0b,95:0b,96:0b,97:0b,98:0b,99:0b}A
data modify storage _: A merge value {00:1b}
data modify storage _: A merge value {00:0b}B
data modify storage _: A merge value {00:1b,01:1b,02:1b,03:1b,04:1b}
data modify storage _: A merge value {00:0b,01:0b,02:0b,03:0b,04:0b}C
data modify storage _: A merge value {00:1b,01:1b,02:1b,03:1b,04:1b,05:1b,06:1b,07:1b,08:1b,09:1b}
data modify storage _: A merge value {00:0b,01:0b,02:0b,03:0b,04:0b,05:0b,06:0b,07:0b,08:0b,09:0b}| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1 | A | 0.920 ±0.033 |
| 10 | A | 1.366 ±0.018 |
| B | 1.631 ±0.028 | |
| C | 1.899 ±0.109 | |
| 100 | A | 5.429 ±0.239 |
| B | 6.208 ±0.296 | |
| C | 6.035 ±0.316 | |
| baseline | 0.231 ±0.002 |
マージする要素やマージされる側の要素が多い程負荷が増えるのが確認できる。(らすく)
/data merge …や/data modify … merge …はマージ前のターゲットNBTをコピーしておいて、マージ後のNBTと比較することで成否判定を行う実装になっているので、ソースNBTのサイズだけでなくマージ前のターゲットNBTのサイズが大きくなるほど遅くなるはず。(intsuc)
▲戻る
リストに要素を追加するときの位置で操作の負荷が変わるらしいけど、どれくらい変わるのか確認したい。ベンチマークの方法的に追加だけでは厳しいので削除も行う。
操作される側のリストの長さを変えて検証。
条件1 (1)
data modify storage _: A set value [0,]条件2 (10)
data modify storage _: A set value [0,0,0,0,0,0,0,0,0,0,]条件3 (100)
data modify storage _: A set value [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]A
data modify storage _: A prepend value 0
data remove storage _: A[0]B
data modify storage _: A append value 0
data remove storage _: A[0]C
data modify storage _: A append value 0
data remove storage _: A[-1]| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1 | A | 0.848 ±0.011 |
| B | 0.763 ±0.013 | |
| C | 0.586 ±0.026 | |
| 10 | A | 0.882 ±0.038 |
| B | 0.726 ±0.029 | |
| C | 0.608 ±0.029 | |
| 100 | A | 0.885 ±0.026 |
| B | 0.735 ±0.034 | |
| C | 0.607 ±0.027 | |
| baseline | 0.229 ±0.002 |
リストが長くなることによる負荷の変動は無いように見える。代わりに、先頭を操作するか最後尾を操作するかでの負荷の変動が見られる。prependよりappendの方が軽く、[0]より[-1]の方が軽いのがこれでわかる。(らすく)
ListTagの末尾以外に要素を追加したり削除したりすると内部データ構造のメモリレイアウトの都合上それより後にある要素全てをずらす必要があるので、このときに後にある要素の分だけ遅くなるはず。ずらされるのはNBTインスタンスの参照でNBTインスタンス自体ではないので、要素の内容はパフォーマンスには影響しないはず。(intsuc)
▲戻る
コンパウンドをset fromでコピーするとき、データの大きさで負荷がどれくらい変わるか測定したい。
コピーするデータの長さを変えて検証。
条件1 (1)
data modify storage _: A set value {00:0b}
data modify storage _: B set value {00:0b}
data modify storage _: C set value {00:1b}条件2 (10)
data modify storage _: A set value {00:0b}
data modify storage _: B set value {00:0b,01:0b,02:0b,03:0b,04:0b,05:0b,06:0b,07:0b,08:0b,09:0b}
data modify storage _: C set value {00:1b,01:1b,02:1b,03:1b,04:1b,05:1b,06:1b,07:1b,08:1b,09:1b}条件3 (100)
data modify storage _: A set value {00:0b}
data modify storage _: B set value {00:0b,01:0b,02:0b,03:0b,04:0b,05:0b,06:0b,07:0b,08:0b,09:0b,10:0b,11:0b,12:0b,13:0b,14:0b,15:0b,16:0b,17:0b,18:0b,19:0b,20:0b,21:0b,22:0b,23:0b,24:0b,25:0b,26:0b,27:0b,28:0b,29:0b,30:0b,31:0b,32:0b,33:0b,34:0b,35:0b,36:0b,37:0b,38:0b,39:0b,40:0b,41:0b,42:0b,43:0b,44:0b,45:0b,46:0b,47:0b,48:0b,49:0b,50:0b,51:0b,52:0b,53:0b,54:0b,55:0b,56:0b,57:0b,58:0b,59:0b,60:0b,61:0b,62:0b,63:0b,64:0b,65:0b,66:0b,67:0b,68:0b,69:0b,70:0b,71:0b,72:0b,73:0b,74:0b,75:0b,76:0b,77:0b,78:0b,79:0b,80:0b,81:0b,82:0b,83:0b,84:0b,85:0b,86:0b,87:0b,88:0b,89:0b,90:0b,91:0b,92:0b,93:0b,94:0b,95:0b,96:0b,97:0b,98:0b,99:0b}
data modify storage _: C set value {00:1b,01:1b,02:1b,03:1b,04:1b,05:1b,06:1b,07:1b,08:1b,09:1b,10:1b,11:1b,12:1b,13:1b,14:1b,15:1b,16:1b,17:1b,18:1b,19:1b,20:1b,21:1b,22:1b,23:1b,24:1b,25:1b,26:1b,27:1b,28:1b,29:1b,30:1b,31:1b,32:1b,33:1b,34:1b,35:1b,36:1b,37:1b,38:1b,39:1b,40:1b,41:1b,42:1b,43:1b,44:1b,45:1b,46:1b,47:1b,48:1b,49:1b,50:1b,51:1b,52:1b,53:1b,54:1b,55:1b,56:1b,57:1b,58:1b,59:1b,60:1b,61:1b,62:1b,63:1b,64:1b,65:1b,66:1b,67:1b,68:1b,69:1b,70:1b,71:1b,72:1b,73:1b,74:1b,75:1b,76:1b,77:1b,78:1b,79:1b,80:1b,81:1b,82:1b,83:1b,84:1b,85:1b,86:1b,87:1b,88:1b,89:1b,90:1b,91:1b,92:1b,93:1b,94:1b,95:1b,96:1b,97:1b,98:1b,99:1b}A
data modify storage _: A set from storage _: C
data modify storage _: A set from storage _: B| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1 | A | 1.005 ±0.036 |
| 10 | A | 1.590 ±0.028 |
| 100 | A | 7.519 ±0.308 |
| baseline | 0.234 ±0.001 |
コピーするデータの量が増えるにつれ処理時間が増えているように見える。いろいろある操作の中でもこれは結構重たくなりえるということか。(らすく)
ListTagやCompoundTagの比較はサイズNについてO(N)、コピーはΘ(N)なのであくまでデータ量に対して線形的にしか遅くならないはず。(GCのコストまで含めるとどうなるかは分からない。)(intsuc)
▲戻る
リストをset fromでコピーするとき、データの大きさで負荷がどれくらい変わるか測定したい。
コピーするデータの長さを変えて検証。
条件1 (1)
data modify storage _: A set value [0,]
data modify storage _: B set value [0,]
data modify storage _: C set value [1,]条件2 (10)
data modify storage _: A set value [0,]
data modify storage _: B set value [0,0,0,0,0,0,0,0,0,0,]
data modify storage _: C set value [1,1,1,1,1,1,1,1,1,1,]条件3 (100)
data modify storage _: A set value [0,]
data modify storage _: B set value [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]
data modify storage _: C set value [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,]A
data modify storage _: A set from storage _: C
data modify storage _: A set from storage _: B| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 1 | A | 0.813 ±0.041 |
| 10 | A | 0.836 ±0.004 |
| 100 | A | 1.206 ±0.014 |
| baseline | 0.230 ±0.001 |
前に同じ。
▲戻る
リストの要素を沢山追加するとき、その数によってどれくらい負荷が変わるかを検証。valueで一個追加するときの負荷と比較。
リストの長さを変えて検証。
条件1 (1)
data modify storage _: A set value [0,]
data modify storage _: B set value [0,]条件2 (10)
data modify storage _: A set value [0,]
data modify storage _: B set value [0,0,0,0,0,0,0,0,0,0,]条件3 (100)
data modify storage _: A set value [0,]
data modify storage _: B set value [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,]A
data modify storage _: A append value 0
data modify storage _: A set value [0,]B
data modify storage _: A append from storage _: B[]
data modify storage _: A set value [0,]C
data modify storage _: A prepend from storage _: B[]
data modify storage _: A set value [0,]| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| A | 0.607 ±0.009 | |
| 1 | B | 0.906 ±0.042 |
| C | 0.990 ±0.028 | |
| 10 | B | 1.115 ±0.044 |
| C | 2.253 ±0.032 | |
| 100 | B | 2.529 ±0.054 |
| C | 13.702 ±0.059 | |
| baseline | 0.229 ±0.002 |
追加する要素の数に対してもそうだが、前と後ろどちらに追加するかでかなり負荷に差があるのが面白い。(らすく)
リストの追加削除の考察と同じ (intsuc)
▲戻る
最近になって文字列としてデータを取得するオプションが追加された。set value、from、set stringでどれがどの程度の負荷か比べたい。
コピー元の文字列をセット
data modify storage _: A set value "0"
data modify storage _: B set value "0"
data modify storage _: C set value "1"A
data modify storage _: A set value "1"
data modify storage _: A set value "0"B
data modify storage _: A set from storage _: C
data modify storage _: A set from storage _: BC
data modify storage _: A set string storage _: C
data modify storage _: A set string storage _: B| Function | Results v1.20 [us/op] |
|---|---|
| A | 0.690 ±0.017 |
| B | 0.777 ±0.033 |
| C | 0.797 ±0.028 |
| baseline | 0.232 ±0.004 |
この3通りだとset valueがさすがに一番軽く、BとCだとBが軽いみたい。全文コピーならさすがにfromで十分か。
▲戻る
コピーする文字列の長さで負荷に変化があるかどうか。
文字列の長さを変えて検証。
条件1 (10)
data modify storage _: A set value ""
data modify storage _: B set value "0123456789"
data modify storage _: C set value "9876543210"条件2 (100)
data modify storage _: A set value ""
data modify storage _: B set value "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
data modify storage _: C set value "9876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210"条件3 (1000)
data modify storage _: A set value ""
data modify storage _: B set value "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
data modify storage _: C set value "9876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210"A
data modify storage _: A set string storage _: C
data modify storage _: A set string storage _: B| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 10 | A | 0.795 ±0.041 |
| 100 | A | 0.805 ±0.039 |
| 1000 | A | 0.777 ±0.036 |
| baseline | 0.234 ±0.002 |
文字列が長くなっても対して負荷は変わってない。内部的にもそんな複雑なデータじゃないので負荷にはそこまで影響ないか。(らすく)
StringTagは値タグでコピーはO(1)で完了するので、文字列の長さはコピーのパフォーマンスには影響しないはず。一方で比較は文字列の長さNに対してO(N)なので、こちらは影響するはず。例えば
data modify storage _ a set value "0000000000"
data modify storage _ a set value "0000000001"
では文字列が同じかどうか比較するために最後の文字まで比較する必要がある。一方で
data modify storage _ a set value "0000000000"
data modify storage _ a from storage _ a
の場合は_ aには同じStringTagのインスタンスが入っているので、比較はアドレスを比較するだけのO(1)で完了する。(intsuc)
▲戻る
コピーする文字列の長さで負荷に変化があるかどうか。
文字列の長さを変えて検証。
条件1 (10)
data modify storage _: A set value ""
data modify storage _: B set value "0123456789"
data modify storage _: C set value "9876543210"条件2 (100)
data modify storage _: A set value ""
data modify storage _: B set value "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
data modify storage _: C set value "9876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210"条件3 (1000)
data modify storage _: A set value ""
data modify storage _: B set value "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
data modify storage _: C set value "9876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210987654321098765432109876543210"A
data modify storage _: A set string storage _: C 0 5
data modify storage _: A set string storage _: B 0 5B
data modify storage _: A set string storage _: C -5
data modify storage _: A set string storage _: B -5| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| 10 | A | 0.862 ±0.037 |
| B | 0.814 ±0.043 | |
| 100 | A | 0.831 ±0.050 |
| B | 0.826 ±0.036 | |
| 1000 | A | 0.803 ±0.008 |
| B | 0.843 ±0.048 | |
| baseline | 0.235 ±0.001 |
全体的にばらついていて、誤差のような差を示しているので、部分取得も元の長さが影響はしてないかも?(らすく)
/data modify … string …はソースNBTを文字列化してから部分文字列を取り出して新しいStringTagを作成し、ターゲットNBTに対して操作を行う。文字列化の際、ソースNBTがStringTagの場合はその文字列データが直接渡されるので、ソースNBTのStringTagの長さはパフォーマンスに影響しないはず。一方でStringTag以外の値タグの場合は文字列への変換が行われるので、ソースNBTの内容がパフォーマンスに影響すると思う。部分文字列の取り出しについては、元の文字列の一部をコピーするだけなので、元の文字列の長さはパフォーマンスには影響しないはず。ただ例外的に範囲が元の文字列と同じになる場合はコピーが行われないので、この場合は少し速くなるのではないかと思う。(intsuc)
▲戻る
概要
説明
A
B
C
| Condition | Function | Results v1.20 [us/op] |
|---|---|---|
| A | ||
| B | ||
| C | ||
| baseline |
それってあなたの
▲戻る