今回の記事は、はてな記法ではなく、Markdownで書いてみた。
(2014/02/20、表内のデータについて転写ミスを発見したので修正。結論は変わりません。)
タートルの採掘速度を調査してみた
少しでも早く採掘できるとうれしいのではないかと、 採掘関連ではどのような関数を使うとどのくらい遅くなるのか確認してみました。
調べた関数は以下の2つ。
- turtle.detect()
- turtle.dig()
以下のコードのどちらが早いかを確認するために行いました。
-- もし、目の前にブロックがあったらdig()する。なかったらdig()しない。 if turtle.detect() then turtle.dig() end turtle.forward()
-- 目の前にブロックがあろうがなかろうが、dig()。
turtle.dig()
turtle.forward()
つまりブロックがないときでも、turtle.detect()を使ってまでturtle.dig()の実行を回避すべきかどうかという検証です。
なんとなくイメージですが、実際に行動しようとするturtle.dig()
よりも、
ただ目の前にブロックがあるかどうか調べるだけのturtle.detect()
の方が、
圧倒的に処理が軽そう(必要時間が少なそう)ではありませんか?
もしそうならば、turtle.detect()
を事前に使うことで、
目の前にブロックがないときにはturtle.dig()
を実行しないようにした方が良いことになります。
以下、検証になります。
例によって、途中は長いので、結論だけ知りたい人は最後のまとめだけ読んでもらえればOKです。
検証方法
検証場所の画像
-
検証用プログラム
テンプレート
自動実行のため「startup」というファイル名で保存。
print(os.clock()) for i=1, 10 do --(ここに条件ごとに異なるコードを入れる) turtle.forward() end print(os.clock())
検証条件と挿入するコード
以下をfor文で10回繰り返し、経過時間を数える(os.clock()を利用)。
- 条件1: detect+dig(+forward)
- 使用するコード:
if turtle.detect() then turtle.dig() end
- 使用するコード:
- 条件2: detect(+forward)
- 使用するコード:
if turtle.detect() then end
- 使用するコード:
- 条件3: dig(+forward)
- 使用するコード:
turtle.dig()
- 使用するコード:
- 条件4: なし(+forward)、つまりforwardのみ。
- 使用するコード: なし
試行結果
結果表1(単位は秒)、for文による10回繰り返しの経過時間
- (2014/02/20、書き留めたデータから転写ミスをしていたので、修正)
- *注1)ブロックなしのときはIF文でdig()回避されるので、ブロックなし条件2とほぼ同等
条件1(detect+dig) | 条件2(detect) | 条件3(dig) | 条件4(fowardのみ) | |
---|---|---|---|---|
ブロックなし | 4.15(*注1) | 4.15 | 4.15 | 3.65 |
ブロックあり | 8.15 | - | 7.65 | - |
表からすぐわかることは、10歩前進で3.65秒ということですね。 単純計算すると1歩前進で0.365秒です。
それ以上についてはこのままだと見づらいので、ブロックなしの条件4(forwardのみ)の値である3.65(秒)との差を求めることで正規化しました。 加えて、これらの値はfor文による10試行の結果なので、それぞれ10で割ることで1試行あたりの経過時間を求めました。結果が以下の表です。
結果表2(単位は秒)、1試行あたりの経過時間
- (2014/02/20、書き留めたデータから転写ミスをしていたので、修正)
- *注1)ブロックなしのときはIF文でdig()回避されるので、ブロックなし条件2とほぼ同等
条件1(detect+dig) | 条件2(detect) | 条件3(dig) | 条件4(なし) | |
---|---|---|---|---|
ブロックなし | 0.05(*注1) | 0.05 | 0.05 | 0 |
ブロックあり | 0.45 | - | 0.4 | - |
たとえば、条件2のブロックなし条件の経過時間0.05(秒)は、純粋にdetect()を1回実行したときの経過時間を意味しています(厳密にはIF文の処理時間など考えなくてはなりませんが、ここでは省略)。
結果考察
表1からわかること
1歩移動(turtle.forward())するのに0.365秒かかる
なお、後進(turtle.back())、上昇(turtle.up())、下降(turtle.down())も全て同じタイムでした。 もしかしたらと思い、前進から後進、上昇から下降など、途中で動く向きを切り替えましたが、切り替えのタイムラグはありません(つまり現実世界のように前進から後進に急に切り替えても慣性ははたらかない)。
もう一つおまけに、右回転(turtle.turnRight())、左回転(turtle.turnLeft())も試しましたが、1回あたり同じ0.365秒になります。 右回転から左回転など途中で回転方向を切り替えても(現実世界のように慣性による)タイムラグはありません。
表2からわかること
dig()で1個のブロックを破壊するのに0.35秒かかる
条件3に注目し、ブロックあり/なしの差を取ると0.45-0.1 = 0.35秒です。
このことから、dig()を使ってブロックを壊すのに0.35秒必要であることがわかります。
turtle.detect()によるdig()実行回避は無駄かも
条件1と条件3を比較すると、ブロックがないときは同じ時間、ブロックがあるときには条件1の方が時間がかかっています。つまりこれは最初の目的であった、
-- もし、目の前にブロックがあったらdig()する。なかったらdig()しない。 if turtle.detect() then turtle.dig() end turtle.forward()
というコードが時間の無駄であることがわかります。シンプルイズベスト!
Turtle API関数1個あたり0.05秒必要?
またブロックなし条件において、条件2/条件3を比較することで、 「もしかして、Turtle API系の関数を1回実行するのに一律0.05秒必要なのでは?」という予想がつきます。
さてここで、「TutleAPI」「0.05秒」というキーワードで思いつくことはありませんか?
- ヒント1
- os.sleep(時間)関数で、指定できる時間の最小単位は0.05秒です。
- また、os.sleep(時間)関数は、"timer"イベントを利用して実装されています。
- ヒント2
- Turtle API中のほとんどの関数は、内部で"turtle_response"イベントを使うことで実行の成否を確認しています。
おそらくですが(個人的にはほぼ確信していますが)、
Turtle APIなど、CCのイベント(Event)を内部で用いている関数は最低でも0.05秒の時間が必要なのでしょう。これは、イベント処理についてのシステム的な制限だと思われます。
イベント処理とはすなわちコルーチン(coroutine.yeild)のことであって、コルーチンによって、Lua→Java→Luaと処理が移っていることを以前に説明しました。
この処理の移り変わりに、必ず0.05秒(以上)の時間が必要なのでしょう。
ということで、タートルプログラミングで処理速度をシビアに考えるならば、イベントを用いている関数はできるだけ使うのを避けたほうが良いことがわかります。とはいえ、全く使わないとなると今度は「Too long without yielding」エラーとなってしまいますので、効率を考えるならば次のように覚えておけば良いでしょう。
- 処理時間を考えるならば、イベント処理を内包するTurtleAPIのような関数群の利用は必要最小限にすべし。ただし全く使わないのもダメ。
もっとも、0.05秒をケチらなくてはいけないようなシビアなプログラムを組むことがそうあるとは思えませんが:P
検証のまとめ
タートルの行動について
- 前進・後進・上昇・下降・回転など、一つの行動をするのに0.365秒かかる
- turtle.dig()で1個のブロックを破壊するのに0.35秒かかる
その他のTurtleAPIについて
- turtle.detect()など、Turtle APIのイベント処理を内包する関数は一律0.05秒の時間がかかる。
- そのため処理時間だけで考えるならば
if turtle.detect() then turtle.dig() end
は無駄。シンプルにturtle.dig()
だけでよい。
- そのため処理時間だけで考えるならば