Month: June 2013

里々は何故遅いのか

 - by Don

「loop関数は毎回呼び出し先の名前探してるから遅いのでは」的な見解を目にしたので、それなら名前参照せず処理直打ちのtimesなどの関数は早かろう、と思って計測してみました。

実験

*loopテスト
:[開始](現在分):(現在秒)(loop,my_loop,1000000)
[終了](現在分):(現在秒)
@my_loop
【処理】

*timesテスト
:[開始](現在分):(現在秒)(times,1000000,【処理】)
[終了](現在分):(現在秒)

*whileテスト
:[開始](現在分):(現在秒)(while,(C0)< 1000000,【処理】)
[終了](現在分):(現在秒)

*forテスト
:[開始](現在分):(現在秒)(for,1,1000000,【処理】)
[終了](現在分):(現在秒)

【処理】のところは以下の3パターンを試しました。

  • 空白(空回し)
  • (nop,)
  • (set,hoge,)

結果がこちら。1000000ループ処理にかかった秒数。

ループ関数 空回し nop set
loop 0 86 140
times 0 35 87
while 104 138 191
for 0 36 88

考察

whileは空回し中もカウンタのインクリメントや条件式判定を行なっているので時間がかかっています。nopやsetは隣同士の差を見ると純粋な関数自身の処理コストが読み取れるっぽいですね。平均してnopは処理コスト35、setは処理コスト88くらい?

loopは空回しでは名前参照しないようですが処理が存在すれば名前参照のコストも含んでいるようです。大体50程度。

timesやforを使えばループ自体のコストはほぼ0と言えそうです。ただ内部関数の呼び出しにかかるコストそのものがloopで浪費していた名前参照のコストに比べて大きなウェイトを占めている印象を受けます。括弧の展開と内部関数のコールは同じくらいのコストと考えても良いのかもしれません。

それから通常ループでカウンタを使わないということはほぼあり得ないので結局はwhileと同じ程度の処理を自前で行うことになるでしょうし、1回のsetで済む単純なループ処理なども実用上は少ないでしょうから結局は内部関数の呼び出しコストが積もり積もって遅くなる、という結論です。塵も積もれば山となる。

里々は重い処理に向かないという今更なお話でした。

ファイル読み書きはどれくらい遅いのか(追記あり)

 - by Don

YAYAで測定

ファイルの読み書きは遅いというのは体感的に何となく知っているのですが、どのくらい遅いのか調べてみようと思いました。

実験で使用するSHIORIはYAYA。関数のFREADFWRITEをそれぞれ1,000,000回実行して掛かった時間を測定します。ループ自体に掛かる時間や代入処理に掛かる時間なども比較のために測定します。

ループだけの処理//2秒
{
  _n = 1000000

  _start = GETTIME[6]
  for _i = 0; _i < _n; _i++ {
    //何もしない
  }
  _end = GETTIME[6]

  '\0\s[0]\_qstart: ' + _start + '\nend: ' + _end
}

代入処理//4秒
{
  _n = 1000000

  _start = GETTIME[6]
  for _i = 0; _i < _n; _i++ {
    _hoge = _i //代入
  }
  _end = GETTIME[6]

  '\0\s[0]\_qstart: ' + _start + '\nend: ' + _end
}

読み//10秒
{
  _filename = 'xxx'
  _n = 1000000

  _start = GETTIME[6]
  if !FOPEN(_filename, 'r')
    return
  for _i = 0; _i < _n; _i++ {
    _line = FREAD(_filename) //読む
  }
  FCLOSE(_filename)
  _end = GETTIME[6]

  '\0\s[0]\_qstart: ' + _start + '\nend: ' + _end
}

書き//10秒
{
  _filename = 'xxx'
  _n = 1000000

  _start = GETTIME[6]
  if !FOPEN(_filename, 'w')
    return
  for _i = 0; _i < _n; _i++ {
    FWRITE(_filename, 'hoge') //書く
  }
  FCLOSE(_filename)
  _end = GETTIME[6]

  '\0\s[0]\_qstart: ' + _start + '\nend: ' + _end
}

ループだけでもカウンタ_iがインクリメントされるのでその処理時間が掛かっているように見受けられます(代入処理を加えるとちょうど2倍の時間になっています)。ファイルのオープン/クローズのオーバーヘッドの影響がこの例ではほぼ無視されてしまいますがそれでもそれなりに時間が掛かっているようです(ファイルの開け閉めを同じ回数だけ回すとさらに2倍くらいかかります)(←勘違い)

追記

やっぱりFOPEN/FCLOSEまで含めての実験が必要ですね。ということで

読み//202秒
{
  _filename = 'xxx'
  _n = 1000000

  _start = GETTIME[6]
  for _i = 0; _i < _n; _i++ {
    if !FOPEN(_filename, 'r')
      return
    _line = FREAD(_filename) //読む
    FCLOSE(_filename)
  }
  _end = GETTIME[6]

  '\0\s[0]\_qstart: ' + _start + '\nend: ' + _end
}
書き//1510秒?(100,000回ループの値を10倍しました)
{
  _filename = 'xxx'
  _n = 1000000

  _start = GETTIME[6]
  for _i = 0; _i < _n; _i++ {
    if !FOPEN(_filename, 'w')
      return
    FWRITE(_filename, 'hoge') //書く
    FCLOSE(_filename)
  }
  _end = GETTIME[6]

  '\0\s[0]\_qstart: ' + _start + '\nend: ' + _end
}

他のSHIORIとの比較

前回の里々での実験においてループ回数を揃えて並べれば、SHIORIの処理能力の比較ができそうな気がします。

*nop関数
:[開始](現在秒)(loop,loop_nop,1000000)
[終了](現在秒)#82秒

*set関数
:[開始](現在秒)(loop,loop_set,1000000)
[終了](現在秒)#138秒

@loop_nop
(nop,)

@loop_set
(set,hoge,)

遅いですね。びっくりです。

ついでに華和梨でも調べてみます。

# ループのみ(1秒)
sample1: \0\s[0]\_qstart: $(date %S)$(loop 1000000 "")\nend: $(date %S)\e

# エコー関数(6秒)
sample2: \0\s[0]\_qstart: $(date %S)$(loop 1000000 $(echo ""))\nend: $(date %S)\e

# 代入関数(8秒)
sample3: \0\s[0]\_qstart: $(date %S)$(loop 1000000 $(setstr foo bar))\nend: $(date %S)\e

ループ自体では関数呼び出し1回のみで他の関数呼び出しが無い分高速のようです。代入などの関数をコールすればそれなりの時間を要するみたいです。

まとめ

表にすると見やすいかもしれない。

処理内容(1,000,000回) 時間(秒)
YAYA ループのみ(for) 2
YAYA 代入 4
YAYA ファイル読み(FREAD) 10
YAYA ファイル書き(FWRITE) 10
YAYA ファイル読み(FOPEN,FREAD,FCLOSE) 202
YAYA ファイル書き(FOPEN,FWRITE,FCLOSE) 1510(?)
里々 何もしない関数(nop) 82
里々 代入関数(set) 138
華和梨 ループのみ 1
華和梨 エコー関数(echo) 6
華和梨 代入関数(setstr) 8

ファイル読み書きの遅さの検証のはずが里々の遅さにびっくりする結果となりました。ファイル読み書きが逆に高速に見えてしまいます。こんなはずでは。

複数のファイルを開け閉めするのはそれなりに時間がかかるようですが、1つのファイルに何万行読み書きしてもそれ自体はあまり時間はかからないようです。

でも実用上1,000,000回もループする処理なんて普通のゴースト開発ではまずあり得ないですね。

SAORI呼び出しはどれくらい遅いのか

 - by Don

里々で測定

内部関数 V.S. SAORI

SAORI呼び出しは遅いというのは体感的に何となく知っているのですが、どのくらい遅いのか調べてみようと思いました。

実験で使用するSHIORIは里々。内部関数のwhenとSAORIであるssu.dllのifをそれぞれ同じ回数実行して掛かった時間を測定します。里々で時間を取得するために(現在秒)を使います。本当はミリ秒まで見たいけど仕方ない。

*
:[開始](現在秒)(loop,loop_when,100000)
[終了](現在秒)#9秒

*
:[開始](現在秒)(loop,loop_if,100000)
[終了](現在秒)#17秒

@loop_when
(when,1,)

@loop_if
(if,1,)

環境にもよるのでしょうが100000回だとwhenで9秒、ifで17秒ということで大体2倍程度の違いが見られます。

ssu.dll V.S. YAYA as SAORI with ssu.aym

応答速度はSAORIにもよります。ssu.dllのsplitとssu.aymのsplitで比較してみます。

*
:[開始](現在秒)(loop,loop_split,30000)
[終了](現在秒)#6秒

*
:[開始](現在秒)(loop,loop_split_yaya,30000)
[終了](現在秒)#77秒

@loop_split
(nop,(split,))

@loop_split_yaya
(nop,(aya,split,))

nopが含まれているので正確ではないですが30000回だとssu.dllで6秒、ssu.aymで77秒と10倍近く違います。

10回程度のループ処理をYAYAに投げるくらいならssu.dllを10回呼び出してもさほど変わらないようです。

他に時間のかかることとの比較

HTTPリクエストやファイルの読み書きとの比較も面白そうですが、また今度。