もう使わないので置いておく
高速化のアイデアはいずれもどの程度速くなるのか調べていない。可読性とのトレードオフ
oomSESTのソースをデコンパイルするとある時期から@tmp
という接尾辞がついた変数が使われるようになったことがわかる。これは次のルールで運用していた。
@tmp
がついた変数が有効なうちはgosub
、goto
、ユーザー定義関数の呼び出しをおこなってはならない。逆に言えば、それらを呼んだ後は@tmp
変数の値が変更されている可能性がある。
このルールを守ることで擬似的にローカル変数を使うことが可能になり、ネストした関数呼び出しの中で変数名がかぶってバグる可能性を減らせる。
変数のスワップで出てくるこのイディオム
z = x
x = y
y = z
は一つ問題を抱えている。それは、もし別の場所でz
という変数を定義していると意図せずしてz
が書き換わることである。これを避けるには他で使われていない一意な名前をつけるか上書きしても問題ない変数を使う必要がある。前者は変数がいたずらに増えてしまう。後者は上書きしていいかどうかを調べるコストが高くつく(その上今は上書きしていい変数だったとしても将来においてそうとは限らない)。この問題の解決のためスワップ専用の変数をあらかじめ用意しておき、常にそれを使うようにする。
#define swap(%1, %2) swap@tmp = %1: %1 = %2: %2 = swap@tmp
swap x, y
これは@tmp
の典型的な利用例でもある。当然この変数はこのマクロを通さずして参照してはならない。
カスタムファイルの読み込みは高コストな処理である。起動時間を速めるため、一度パースしたあとのファイルをバイナリにデシリアライズしてファイルにダンプしておく。もしもカスタムファイル読み込み時にキャッシュが見つかればそちらを変わりに読む。うまくすれば10倍は速くなると踏んでいる。なお、大元のファイルが変更されたときはたとえキャッシュがあってもそれを使ってはならない。元のファイルの更新時間を取得してキャッシュファイルのそれと比較するか、元のファイルのハッシュ値をキャッシュファイルに埋め込んでおきハッシュ値が変わっていないことを確認するかすればよい。
ほとんどの処理でバブルソートが使われているが、まともなソートに置き換える。バブルソートは安定なソートなので不安定なものにするとまずいかもしれない。
高速化のためfactionを文字列でなく数値で保持する。読み込みと保存の直後/直前に文字列に変換すればいい。
アイテムやキャラクタの情報を参照する際現状はif文の羅列により処理しているがこれを二分探索するように変える。O(n)からO(log n)。これらを手で書くのは狂気の沙汰であるから、適当にスクリプトを書いて生成する。当然データを配列に格納してインデックスでアクセスすればO(1)になるが使える場所はやや限られる。
HSPのswitchはマクロで実現されている。このマクロはfall-throughをサポートしているが、fall-throughしない大多数のswitch文では単に無駄な処理が行われるだけになる。fall-throughしたい典型的な利用例は
switch x
case 1
case 2
// 1、2で共通の処理
swend
だが(というよりこれ以外でfall-throughするのは99%バグ)、これは単に|
を使えばいい。
if (x == 1 | x == 2) {
}
なお、xが複雑な式だったり副作用を持つ式だったりしたときは直接参照するのではなく先に適当な変数に代入しておくべきである。
また、if文のelse節を使えばもう少し効率を上げられる。
if (x == 1) {
}
if (x == 2) {
}
if (x == 3) {
}
↓
if (x == 1) {
} else: if (x == 2) {
} else: if (x == 3) {
}
これを簡略化するため次のようなマクロが使える。
#define elseif else:if
信仰する宗派によってはelseif
の代わりにelif
やelsif
を使いたいかもしれない。
ランダムに生成されるアイテムやキャラで何を選ぶかはいくつかのパラメータと乱数で決まっているが、このパラメータのいくつかはほぼ特定のパターンしか使われない。更にいくつかのアイテムやキャラはランダム生成されることがない(ほとんどのユニークと固定AF)。flttype
ごとに生成テーブルを分けるといった最適化が考えられる。
lang
を連続して何度も呼んでいる箇所はif (jp)
で分岐した方が速い
a = lang("あ", "A")
b = lang("い", "B")
c = lang("う", "C")
↓
if (jp) {
a = "あ"
b = "い"
c = "う"
} else {
a = "A"
b = "B"
c = "C"
}
// もしもa、b、cが連続した配列ならさらに速くできる。array(0)、array(1)、array(2)にそれぞれ代入するよりこの方が速い。
if (jp) {
array = "あ", "い", "う"
} else {
array = "A", "B", "C"
}
HSPでは一つの文は一行に書かねばならず、改行する手段はない。これはある程度大きな配列を代入したいとき問題になる。
array = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14
エディタで折り返しを設定していても視認性は悪い。もし各要素が文字列であればさらに悲惨になる。これは次のようなイディオムで改善できる。
array(0) = 0, 1, 2, 3, 4
array(5) = 5, 6, 7, 8, 9
array(10) = 10, 11, 12, 13, 14
これは同じ結果を生む。しかしながらこの書き方だと配列のインデックスを書く必要がある。もしインデックスを間違えてもコンパイラは何も言わないためバグの原因になる。これを解決するための案を次に示す。
#define ARRAY \
0, 1, 2, 3, 4, \
5, 6, 7, 8, 9, \
10, 11, 12, 13, 14
array = ARRAY
#define
マクロの定義部は幸運なことに改行できる。実用上はARRAY
のようなマクロをまとめた別ファイルを用意して#include
するのがいいだろう。当然名前空間を汚染しないよう注意深く名付ける必要がある。例えば称号の日本語名であればST_TITLE_NAME_JA
のようにする(ST
はoomSESTで利用しているマクロ用のプレフィクス。名前のコンフリクト防止)。
HSPの&
や|
はショートサーキットしない。もしそうしたいならifをネストさせる必要がある。
if (a & b) { // 部分式aとbは値によらず常に評価される
}
if (0 <= i & i < length(array) & array(i) == 0) {
// iの範囲をチェックしてからarrayにアクセスするつもりのコード。実際には範囲外だろうがarrayにアクセスしてエラーを吐く
}
// 正しくはこう
if (0 <= i & i < length(array)) {
if (array(i) == 0) {
}
}
//またはこう
if (0 <= i) {
if (i < length(array)) {
if (array(i) == 0) {
}
}
}
この書き方はネストが際限なく深くなってしまう。また、コードリーディングのコストも高くつく。これを改善するため次のように書く方法がある。
if (0 <= i): if (i < length(array)): if (array(i) == 0) {
// Do something.
}
このイディオムをもう少し書きやすくするため次のようなマクロを考案した。
#define AND ):if(
これを使うと次のように書ける。
if (0 <= i AND i < length(array) AND array(i) == 0) {
// Do something.
}
ElonaのUIにはウィンドウの背景に羊皮紙が使われているが、これはいわゆる9-slice imageのような手法を用いていて描写回数が多い。これをウィンドウサイズごとにキャッシュしておけば速くならないか。
マップ、アイテム、キャラ、HUDのそれぞれ専用のテクスチャを用意し、それぞれが更新されたときだけ描写し直す。更新されたかどうか監視するコストの方が高くなる可能性はある。