monolithic kernel

HSP3 ショートプログラミングTips

HSPプログラムコンテストのHSPTV部門ではaxファイルのサイズに制限があるため、プログラムの容量を抑えるために特殊なテクニックが多用されます。今回はそうしたテクニックのうち、多くのプログラムで利用されそうなものを紹介したいと思います。といっても、多くはすでにいなえ氏による講座で紹介されているため、ここではいくつかのコード片を並べることにします。

#deffuncによる関数定義

改善前 (90bytes)

#module
#deffunc very_very_useful_function
  return
#global

very_very_useful_function

改善後 (66bytes)

if 0 {
#define global very_very_useful_function _
#deffunc very_very_useful_function
  return
}

very_very_useful_function

解説

##deffuncや#defcfuncで定義した関数名はなぜかaxファイルに保存されてしまうため、#defineで適当な短い名前に置き換えることで容量を削減できます。また、#moduleと#globalはif文で代用することができます。もちろん名前空間は分離されないので変数の扱いには注意してください。

非0チェックを含む複数条件のif (-8bytes)

改善前 (34bytes)

if x != 0 & y == z {
  // ...
}

改善後 (26bytes)

if x * (y == z) {
  // ...
}

解説

if文は最終的な値が0以外かどうかで分岐するため、&のかわりに*を利用しても複数の条件のandをとることができます。なお、== 0を判定するよりも!= 0を判定するほうが容量は少なくできるので、論理を反転させることも考えてみるといいかもしれません。

OBAQでのコリジョン (-12bytes)

改善前 (66bytes)

qcollision ship_id
repeat
  qgetcol id, x, y
  if id < 0 {
    break
  }
  // ...
loop

改善後 (54bytes)

qcollision ship_id
*@
  qgetcol id, x, y
  if id >= 0 {
    // ...
    goto *@b
  }

解説

repeat-loopをgoto文に置き換えると容量を削減できますが、このときローカルラベルを利用すると更に容量を削減することができます。ローカルラベルを入れ子にすることはできないほか、コードが読みにくくなるという問題もあるので、最初からローカルラベルを駆使しようとせず、普通に記述しておいてあとから最適化するほうが安全かもしれません。

カーソルキーでの移動 (-16bytes)

改善前 (132bytes)

#const SHIP_SPEED 8

stick in, 15
if in & 1 {
  x -= SHIP_SPEED
}
if in & 4 {
  x += SHIP_SPEED
}
if in & 2 {
  y -= SHIP_SPEED
}
if in & 8 {
  y += SHIP_SPEED
}

改善後 (116bytes)

#const SHIP_SPEED 8
#const SHIP_SPEED_DIV_2 (SHIP_SPEED / 2)

stick in, 15
x += SHIP_SPEED       * (((in & 4) >> 2) - (in & 1))
y += SHIP_SPEED_DIV_2 * (((in & 8) >> 2) - (in & 2))

解説

各方向についてif文で判定するのをやめ、X軸とY軸に分けてビット演算で処理するようにします。この時、単にビットが立っているかチェックして引き算するのではなく、もとの値を活かして演算が減るようにしています。