Skip to content

Instantly share code, notes, and snippets.

@ki-foobar
Last active September 26, 2019 10:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ki-foobar/f583b39da952af6905caf86793b24d33 to your computer and use it in GitHub Desktop.
Save ki-foobar/f583b39da952af6905caf86793b24d33 to your computer and use it in GitHub Desktop.

もう使わないので置いておく

高速化のアイデアはいずれもどの程度速くなるのか調べていない。可読性とのトレードオフ

@tmp suffix

oomSESTのソースをデコンパイルするとある時期から@tmpという接尾辞がついた変数が使われるようになったことがわかる。これは次のルールで運用していた。

@tmpがついた変数が有効なうちはgosubgoto、ユーザー定義関数の呼び出しをおこなってはならない。逆に言えば、それらを呼んだ後は@tmp変数の値が変更されている可能性がある。

このルールを守ることで擬似的にローカル変数を使うことが可能になり、ネストした関数呼び出しの中で変数名がかぶってバグる可能性を減らせる。

swap@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の典型的な利用例でもある。当然この変数はこのマクロを通さずして参照してはならない。

Pre-parsed custom file

カスタムファイルの読み込みは高コストな処理である。起動時間を速めるため、一度パースしたあとのファイルをバイナリにデシリアライズしてファイルにダンプしておく。もしもカスタムファイル読み込み時にキャッシュが見つかればそちらを変わりに読む。うまくすれば10倍は速くなると踏んでいる。なお、大元のファイルが変更されたときはたとえキャッシュがあってもそれを使ってはならない。元のファイルの更新時間を取得してキャッシュファイルのそれと比較するか、元のファイルのハッシュ値をキャッシュファイルに埋め込んでおきハッシュ値が変わっていないことを確認するかすればよい。

Sorting algorithm

ほとんどの処理でバブルソートが使われているが、まともなソートに置き換える。バブルソートは安定なソートなので不安定なものにするとまずいかもしれない。

Faction

高速化のためfactionを文字列でなく数値で保持する。読み込みと保存の直後/直前に文字列に変換すればいい。

Binary search

アイテムやキャラクタの情報を参照する際現状はif文の羅列により処理しているがこれを二分探索するように変える。O(n)からO(log n)。これらを手で書くのは狂気の沙汰であるから、適当にスクリプトを書いて生成する。当然データを配列に格納してインデックスでアクセスすればO(1)になるが使える場所はやや限られる。

switch without fall-through

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の代わりにelifelsifを使いたいかもしれない。

Item/character generation

ランダムに生成されるアイテムやキャラで何を選ぶかはいくつかのパラメータと乱数で決まっているが、このパラメータのいくつかはほぼ特定のパターンしか使われない。更にいくつかのアイテムやキャラはランダム生成されることがない(ほとんどのユニークと固定AF)。flttypeごとに生成テーブルを分けるといった最適化が考えられる。

lang/en/jp

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"
}

Multi-line definition

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で利用しているマクロ用のプレフィクス。名前のコンフリクト防止)。

Nested if

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.
}

Sliced scroll cache

ElonaのUIにはウィンドウの背景に羊皮紙が使われているが、これはいわゆる9-slice imageのような手法を用いていて描写回数が多い。これをウィンドウサイズごとにキャッシュしておけば速くならないか。

Layered rendering

マップ、アイテム、キャラ、HUDのそれぞれ専用のテクスチャを用意し、それぞれが更新されたときだけ描写し直す。更新されたかどうか監視するコストの方が高くなる可能性はある。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment