#前提
バッチファイルでは、行うバッチ処理の対象が広くなれば広くなるほど処理にかかる時間が増していきます。
対象が1台のPCと100台のPCでは後者の方が多くの時間を必要とするのは自明ですね。
そこで少しでもバッチファイルの実行時間を短くするために、ループ処理にかかる
時間を計測し、考察します。
#計測する処理の対象
batでループ処理をする方法は、
①変数の値を増加・減少させながら同じラベルを複数回実行する「goto」
②特定の変数の値の範囲を設定し、その条件を満たしている限り後続の処理を実行する「for」
が挙げられます。
今回は比較の対象として次の4つの構文を用います。
いずれも2500個の変数に1を代入するものです。
配列表記していますが、この配列はBASE1(要素の先頭が[1])です。
[50][50]及び[2500]をforとgotoでそれぞれ設定する4通りです。
##対象①:一次元配列goto
:GT1
set n1=0
:GT1_1
set /a n1+=1
if %n1% == 2501 exit /b
set num[%n1%]=1
goto GT1_1
##対象②:二次元配列goto
:GT2
set n1=0
:GT2_1
set /a n1+=1,n2=0
if %n1% == 51 exit /b
:GT2_2
set /a n2+=1,n3=0
if %n2% == 51 goto GT2_1
set num[%n1%][%n2%]=1
goto GT2_2
##対象③:一次元配列for
:F1
for /l %%i in (1,1,2500) do set num[%%i]=1
exit /b
##対象④:二次元配列for
:F2
for /l %%i in (1,1,50) do for /l %%j in (1,1,50) do set num[%%i][%%j]=1
exit /b
#所要時間実測その1:それぞれの処理の時間
まずは、単体の時間を計ってみます。
時間の計測にはすべて同じバッチラベルを使用しており、時間はそれぞれ3回測った平均です。
処理 | 時間(ミリ秒) |
---|---|
goto[2500] | 252 |
goto[50][50] | 288 |
for[2500] | 37 |
for[50][50] | 38 |
・for文は要素数が同じ場合、時間に差異は生じない。(双方36ms~39ms)
・それに対して、gotoでは同じ要素数にもかかわらず0.3秒という大きい違いがあり、誤差とは判断できない。
2つのgoto処理の違いを考えると、目に付くのは経過するラベルの数の違い(≒goto数の違い)、処理の回数の違い。
そこで、2つのgotoが行っている処理の回数をまとめようとしましたが、その前に
・setで変数を代入した場合とset /a で変数を計算した場合の時間
・if文の時間
・goto文の時間
を調べる必要があります。
#所要時間実測その2:setとset /a
以下の計算式ををそれぞれ100000回ずつ実行、時間を測定します。
処理 | 時間(ミリ秒) | 時間2500回換算(÷50) |
---|---|---|
set n=1 | 269 | 5.4 |
set /a n=1 | 302 | 6.0 |
set /a n+=1 | 316 | 6.3 |
set /a n+=1,m=1 | 416 | 8.3 |
set /a n+=1,m+=1 | 415 | 8.3 |
・代入でも、/aのあるなしでは若干だが時間は変わってくる模様。(上ふたつ)
・また、/aを使っているならば代入だろうが加算だろうがさほど時間は変わらないようだ。(2,3番目)
・意外だったのは、2つの変数をまとめて計算する場合。2倍弱の時間がかかるという予想だったが、実際は1.3倍程度に収まった。(3,4番目)
・2つの計算のうち1つを代入にしても時間は変化しなかった。(下ふたつ)
#所要時間実測その3:if
4種類のif文に対して100000回実行した時間を計測。当然、ifが真になる回数は同じになるようにしています。
処理 | 時間(ミリ秒) | 時間2500回換算(÷40) |
---|---|---|
if 変数==定数 | 50 | 1.25 |
if 変数==変数 | 50 | 1.25 |
if 変数geq定数 | 50 | 1.25 |
if 変数geq変数 | 50 | 1.25 |
あまりにも同じだったので間違っているのかと思った。同じでした。
せっかくなので、例では使用しない遅延展開についても検証。
処理 | 時間(ミリ秒) |
---|---|
if 変数==遅延変数 | 84 |
if 遅延変数==遅延変数 | 108 |
数値の記載はしませんでしたが、遅延と通常変数が前後しても時間は変わりません。 |
#所要時間実測その4:goto
if文で同じラベルをループ。ループ回数は可変。
ifとset /a +=を使用しているので、結果を減算する必要があります。
処理 | 時間(ミリ秒) |
---|---|
50ループ | 2 |
250ループ | 11 |
2500ループ | 109 |
25000ループ | 1148 |
-7.6して約101ms。1回あたりの時間は0.025ms程度です。
#所要時間実測その5:ラベルの読み込み
次は、再帰ではなく新しいラベルを経由するとき。
ループの前後にたくさんのダミーのラベルを並べて時間を見ます。
:a1
:a2
:a3
:
:
:a100
:loop
set /a loop+=1
if %loop% leq 2500 goto loop
処理 | 時間(ミリ秒) | 基準との差 | ラベル1個当たり |
---|---|---|---|
前に1000個 | 1124 | 995 | 0.99 |
前に100個 | 237 | 108 | 1.08 |
前に10個 | 144 | 15 | 1.50 |
前に1個(基準) | 129 | ||
後に10個 | 141 | 12 | 1.20 |
後に100個 | 228 | 99 | 0.99 |
後に1000個 | 1117 | 988 | 0.99 |
ラベルがループの前後にあることによって生じた差は、前後関係なくラベル1個当たり約1ms。
前後が関係なく、かつ一定の時間を要するということは、新しいラベルを読み込むときは、1msかかるという推測ができます。
#その1の数値を計算
それを踏まえて、その1で出た疑問を解消するためにgotoの2つの処理回数をまとめてみます。
処理 | n= | n+= | n+=,m+= | if | goto | ラベル数 | 累計時間(ミリ秒) |
---|---|---|---|---|---|---|---|
[2500] | 2501 | 2501 | 0 | 2501 | 50 | 2 | 252 |
[50][50] | 2501 | 0 | 2601 | 2550 | 2550 | 3 | 288 |
差 | 0 | -2501 | 2601 | 49 | 2500 | 1 | 36 |
差(ミリ秒換算) | 0 | -6.3 | 8.6 | 0.025≒0 | 100 | 1 | 102.4 |
!?
36と102.4、その差66.4。誤差だと信じたいですが多分gotoが悪いです。全処理時間のうち99%もgotoだけで占めるわけないんだよなぁ…。
まあそれでもたった60ms違ったところで感はあるんですが。
#まとめ
①setコマンドは1回あたり約0.0026ms。400回程度で1ms必要になる。
②set /aで1つの変数を計算すると約0.003ms。330回程度で1ms必要になる。
③set /aで2つの変数をまとめて計算すると約0.0041ms。250回程度で1ms必要になる。
④ifは遅延環境変数を使用しなければ、共通して1回0.0005ms。2000回程度で1ms必要になる。
⑤ifで遅延環境変数を使った場合、1つ使うと0.00084ms。通常の1.7倍程度の時間を必要とする。
⑥ifで両辺が遅延だと0.0011ms。通常の2倍程度の時間を必要とする。
⑦gotoは謎挙動過ぎて分からん。**今後の課題。**他の調査結果には影響していないからgotoの調査はなかったことに。