Category:伺か’

里々は何故遅いのか

 - 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リクエストやファイルの読み書きとの比較も面白そうですが、また今度。

ソシレミでエラーが出た時

 - by Don

これは伺かアドベントカレンダー2012の24日目の記事です。

SoSiReMiアップローダー

手前で管理しているアップローダーの宣伝めいた記事になります。

SoSiReMiというアップローダーがあります。NARをアップロードすると、中のファイルを全部ZIP展開してサーバ内に配置します。また、updates2.dauを作成し、descript.txtにhomeurlを追記してZIPに詰め直すという、およそアップローダーらしからぬ処理を行います。

descript.txtのhomeurlを読み取るのはSSPの独自仕様なのでSSP専用となりますが、NARをアップロードするだけでネットワーク更新に対応できるという触れ込みで多くのゴーストマスタの方々にご利用頂いております。

ただ、複雑な処理を行うわりに設計が雑でバグも多く、さらに1日の転送量が1GBに達すると使えなくなるという制約があります。また、NARの中身に不十分な点があればエラーを返すようにしていますが、メッセージがわかりにくいという不親切な設計となっていて新規利用者の方々にご迷惑をお掛けしております。

エラーが起きたら

NAR-Station / issuesまでご報告お願いします。特に「500 Internal Server Error」というメッセージが出たら100%アップローダー側のバグです。その時アップロードしようとしたNARも添付して頂けると調査の際に再現できると思うので助かります。

その次に多いと思われるのが「install.txtが無い」旨のメッセージです。これは十中八九、ゴーストフォルダを右クリックしてZIPを作成したため、ディレクトリが一段深くなったものと思われます。NARの実態がZIPなのは確かですが、この方法によるNARの作成は推奨されません(前述のようなことが起こるため)。ゴーストにフォルダをDnDして作るか、SSPのゴーストエクスプローラから右クリックで「NAR作成」を選んで作成するのを推奨します。その際、developer_options.txtを含めておくと良いです。私自身はゴースト配布系自動化システムを使ってNARを作成しています。これが一番間違いがなく便利です。

参考

SoSiReMiの今後

ボロくなってきたので作り直しを考えていますが、無駄に複雑な処理を組んでいるので思うように改修が進まず途方に暮れています。現状しばらくは新規利用も問題ないですが、容量の大きいNARが新規にアップされると転送量の増大に繋がってよく落ちるので、容量の削減にご協力頂ければ幸いです。

SATORI as MAKOTO

 - by Don

これは伺かアドベントカレンダー2012の23日目の記事です。

里々をMAKOTOとして使う

昨日の記事で里々はSAORI以外のプロトコルには対応していないと言ったな。あれは嘘だ。

よく調べたらMAKOTOにもなるって書いてありました。ごめんなさい。

MAKOTOを使った多機能な追加シェル

GHOST側でMAKOTOを使う理由は今となってはほとんどありません。OnTranslateイベントを利用すればいいのですから。

このため廃れていく運命にあったMAKOTOですが、思わぬところで日の目を見ることとなりました。追加シェルでの利用です。

MAKOTOについてでも紹介されている通り、MAKOTOには語尾や口調を変えるといった王道的利用法の他に、SHELL側で時刻を見てサーフィスを切り替える「時間帯によって外見が変化するシェル」や、「Osuwari」を使ってウィンドウに座らせる追加シェルの作成が可能となります。いずれもYAYA as MAKOTOを使用していますが、里々もMAKOTOになるのであればSATORI as MAKOTOとして機能的で反則的な追加シェルを作ることもできるはず。というわけで作ってみます。

shell/masterをコピーしてshell/another_shellというディレクトリ名の追加シェルとし、適宜descript.txtを書き換えます。satori.dllをanother_shellディレクトリ内に放り込みます。makoto.dllとリネームしてもいいのですが、今回はdescript.txtに以下の一行を加えることでMAKOTOを認識させることにします。

makoto,satori.dll

ついでにssu.dllも同ディレクトリ内に放り込んで、satori_conf.txtを作成して以下のように記述します。

@SAORI
replace,ssu.dll,replace

最後にdic_xxx.txtというファイルを作成して、以下の記述をして完成です。

@OnMakoto
(replace(バイト値,1)(R0)(バイト値,1)。(バイト値,1)にゅ。)

このシェルを使用した時に限り、語尾が「にゅ。」となります。

冒頭でも述べた通り、MAKOTOの真髄は語尾変化のみならず音声再生や時刻に応じたサーフィス切り替えなど、工夫次第でインパクトのある演出が可能な点にあります。今まで「YAYAわからないし…」と断念していた方は、上記の通り里々でも同じ事ができますので、チャレンジしてみてはいかがでしょうか。

里々でプラグインを作る

 - by Don

これは伺かアドベントカレンダー2012の22日目の記事です。

YAYA as PLUGIN + SATORI as SAORI

YAYAは大変カスタマイズ性が高く、SAORI(YAYA as SAORI)にも、MAKOTO(YAYA as MAKOTO)にも、PLUGIN(YAYA as PLUGIN)にもなります。一方、里々はSAORI以外のプロトコルには対応していません(たぶん)。

しかし、SAORIとして呼び出せるのであれば、入り口はYAYAにまかせて、里々をSAORIとして呼んでもらって、中身は里々で書いてしまう、という手もあります。

以下、第弐版仮想麦酒飛散拡張(バーチャルビールかけ)をテンプレートとして、里々をSAORIとして呼び出すよう改造してみます。

satoriディレクトリを作成してその中にsatori.dllを入れ、同ディレクトリ内にdic_xxx.txtという名前のテキストファイルを作成して以下のような関数を定義します。

@MyFunc
\h\s[5]すぽんっ!\w9\nぶしゅー。\w5ばしゃー。\w9\nどぼどぼどぼどぼ。\w9\w9\u\s[11]…\w5…\w5…\w5…\w5…。\e

そしてメインのSHIORIであるYAYAからSAORIとしてコールします。以下は改造後のyaya_plugin_main.txtの一部です。

OnMenuExec
{
  res_event = 'OnBeerShower'

  SAORI('satori/satori.dll', 'MyFunc')
}

これで改造前と等価なPLUGINとなります。後は好きなように、mciaudior.dllを使って音を鳴らすとか、httpc.dllを使ってWebから情報を持ってくるとか、機能を足していくことができます。SAORIとして呼ばれた里々も通常通りsatori_conf.txtにSAORIを登録することでSAORIをコールすることができます。必要に応じてssu.dllも含めておくと良いです。

SATORI as SAORI

 - by Don

これは伺かアドベントカレンダー2012の21日目の記事です。

里々はSAORIにもなる

里々はSHIORIだけでなく、SAORIにもなります。YAYAをSAORIとして使うYAYA as SAORIというモジュールがありますが、原理はあれと同じです。

以下、里々から里々をSAORIとして呼んでみるサンプルです。saoriディレクトリ内にsatori.dllをコピーし、同ディレクトリ内にdic_xxx.txtという名前のテキストファイルを作成して以下のような関数を定義します。

@MyFunc
はろー

そしてメインのSHIORIである里々から呼ぶためにsatori_conf.txtにSAORIを登録しておきます。

@SAORI
satori,saori/satori.dll

後は、以下のような感じで呼び出せます。

*里々をSAORIとして呼ぶサンプル
:(satori,MyFunc)

何がウレシイのか

高機能なYAYAの力を借りるために里々からYAYAをSAORIとして利用することはあっても、他のSHIORIから里々をSAORIとして利用する機会なんてないじゃん。と思われるかもしれませんが、そんなことはありません。里々にだけできて、他のSHIORIにはできないことだってあります。例えば、SAORI-basicをコールすること。他のSHIORIがSAORI-basicをコールするためにはProxy DLLを経由しなければなりませんが、里々は自分だけの力でコールできてしまいます。里々をProxy DLL代わりに利用することができるわけです。

他にも里々は同時起動中のゴーストのウインドウハンドルとかサクッと取得できちゃうし、わりと難しいことをするための仕組みが用意されていたりするので侮れません。

Webから何か持ってくる

 - by Don

これは伺かアドベントカレンダー2012の18日目の記事です。

画像を持ってくる

デスクトップに閉じこもっているよりも、外の世界から何か持ってこれたほうが夢が広がります。今回はとりあえずSiReFaSoの最近更新されたゴーストの立ち絵をバルーンに表示することを目標にします。

Webから何か持ってくるSAORIといえばhttpc.dllです。このSAORIはかなり多機能なのですが、まずは引数を3つ指定して画像のリンクアドレスを取得してみます。

*シレファソの外部画像リンクを列挙する
:\_qhttp://(httpc,http://sirefaso.appspot.com/,<img src="http://,")
(loop,foo,0,(calc,(Sの数)-1))
@foo
ttp://(S(fooカウンタ))\n

<img src=" と " で挟まれているのが画像リンクのURLになります。相対リンクのサイト内画像は無視するために http:// で始まるリンクのみ抽出し、後で http:// を付け足しています。

URLが分かったので、次は画像をダウンロードします。 httpc.dll を再度使うか、 \![execute,http-get,...] で取得してもいいのですが、最終的にバルーンに表示すればよいので、ここで裏ワザを使います。バルーンに画像を表示するには \_b[(ファイルパス)] ですが、実はURLを指定するとSSPは画像をダウンロードして表示してくれます。なので、たったこれだけ。

*シレファソの最新ゴーストの立ち絵を表示する
:\b[2]\_b[http://(httpc,http://sirefaso.appspot.com/,<img src="http://,"),centerx,centery]

結構手軽に外の世界と繋がれることが実感できます。

DirectSSTPでプロパティシステムを使う

 - by Don

これは伺かアドベントカレンダー2012の17日目の記事です。

単一イベント内で処理を完結する

前回バルーンのサイズを取得するで紹介したプロパティシステムですが、\![get,property,...]を使うとイベントを発生させなければならず、単一イベント内で処理を完了できないのであまりスマートではありません。実際にゴーストに組み込む時には、バルーンサイズの取得などは関数として定義して簡単に利用できるようにしておきたいものです。

こんな時はDirectSSTPを使ってプロパティシステムにアクセスするとcoolです。里々なら以下のような感じです。

*ベースウェアバージョンを知る
:(HandUtil,DSSTPSend,(FMO0hwnd),result,EXECUTE SSTP/1.1,Charset: Shift_JIS,Sender: SATORI,Command: GetProperty[baseware.version])
(S0)
(S1)

ここではDirectSSTPを送信するためにHandUtil.dllを使います。Executeリクエストを使用してベースウェアのバージョンを取得するサンプルです。

ただ、残念なことにバルーンサイズであれば縦と横の2つを取得する必要がありますが、DirectSSTPでは1つずつしか取得することができません。\![get,property,...]は複数の値が一度に取得できるのに比べて、スマートではないですね。

しかしSakuraScriptを使わずに取得できればSHIORIログが汚れないですし(代わりにSSTPログが汚れるけど)、PLUGINなどから利用するにもGHOSTにSakuraScriptを再生させずに済むので私はDirectSSTPによるアクセスを好んで利用しています。場面によって使いやすい方を使えるとよいですね。

Unicodeの使い方

 - by Don

これは伺かアドベントカレンダー2012の13日目の記事です。

バルーンに特殊文字を表示する

バルーンに ♥ とか ★ とか出したいですよね。 ☀ とか ☂ とかも実用的です。

そんな時は \_u[0x0000] というSakuraScriptを使います。 ♥ は \_u[0x2655], ★ は \_u[0x2605], ☀ は \_u[0x2600], ☂ は \_u[0x2602] です。

この謎の数字の対応はどこでわかるかというと、Unicode表というものがあるので、ここからお目当ての記号を探して対応する番号を\_u[]に突っ込んでやるだけです。私はチェックボックス ☑ とかよく使います。

SSPではさらにフォントを自在に操ることができるので、様々な演出が可能になります。ここぞというイベントでぜひお試しください。