HSP

HSP3.5.1のオブジェクトファイルサイズを徹底的に削減する


はじめに

hsptvでは,HSPプログラミングコンテストを毎年開催しています.

その中には「hsptv部門」がありますが,この部門に投稿するには,「start.ax」を6000byte以下に収めなければなりません.

投稿した際に苦労したので,記事としてまとめたいと思います.

…という記事を書こうとおもったら先客がいらっしゃったのでリンクを貼ります.

HSPTV部門でのstart.ax削減術

https://qiita.com/hato/items/7b720e87a408a6ab147a

start.ax のダイエット

https://sites.google.com/site/hspsource/diet

HSP: オブジェクトファイルのサイズを小さく

http://freeh.minim.ne.jp/minimini/develop/hspcpt.html

実行ファイルのサイズを小さくする

http://lhsp.s206.xrea.com/hsp_tips5.html

なるべく内容が重ならないようにします…


注意事項

可読性が酷く落ちます


-


共通部分を纏める

共通している部分があったら,変数でまとめてみましょう.以下の例では4byte減ります.

    c = cnt*3

noteget rscore, c ;cnt*3
noteget ruser, c+1 ;cnt*3+1
noteget rcomm, c+2 ;cnt*3+2


配列をまとめてセットする

    dim v, 4

v.0 = 3
v.1 = 2
v.2 = 1
v.3 = 0

と書くと238byteですが,

    dim v, 4

v = 3,2,1,0

と書くと166byteになります.

この例のような,1次元配列の場合はdim命令も要らないので,154byteまで減らせます.


hsvcolorを思い出す

HSV形式で色を指定できるhsvcolorという命令があります.

HSV形式では,「色相H」「彩度S」「明度V」で色を表現します.

hsvcolorでパラメータを省略した場合はcolorと同様に0が指定されます.

HまたはSまたはV成分が0の時,オブジェクトファイルサイズを短縮出来ます.

例えばグレーRGB=(x,x,x)の場合,color x,x,xとなりますが,

HSV=(0,0,x)で表現すると hsvcolor ,,xと書くことが出来ます.


マクロの挙動を理解する

switchfor等はHSPインタプリタの命令では無くマクロによって記述された命令です.

for i, 1, 10, 1

mes "i="+i
next

例えば,上のコードは,以下のように展開されます.

#cmpopt ppout 1を加えるとファイルhsptmp.iが出力されるので,

このファイルの中身からどのように展開されたか確認することが出来ます.

 i= 1:*_for_0000:exgoto@hsp  i, 1, 10,*_break_0000

mes@hsp "i="+i
*_continue_0000: i+= 1:goto@hsp *_for_0000:*_break_0000

大量の命令が出てきました.

複雑なマクロを多用すると,ファイルサイズ短縮のチャンスを見逃してしまうかもしれません.

私は単純そうなマクロにも気をつけています.

例えば,dir_tvdirinfo(5)に展開されます.命令だけでなくパラメータも1つ隠れていますね.

mes dir_tv+"sozai5.jpg

mes dir_tv+"mini_kei.png"
mes dir_tv+"mini_tamane.png"
mes dir_tv+"tamadot.png"

「共通部分を纏める」と同様に,一時変数_dir_tvに放り込んでから使うようにすることで,

上の例では294byteから270byteに短縮します.

_dir_tv = dir_tv

mes _dir_tv+"sozai5.jpg
mes _dir_tv+"mini_kei.png"
mes _dir_tv+"mini_tamane.png"
mes _dir_tv+"tamadot.png"


浮動小数点数(double)を極力避ける

整数(int)は4byte,浮動小数点数(double)は8byteです.整数に書き換えると4byte減ります.

例えば,次の「たまねちゃんが横倒しの状態から始まり3/4回転するアニメーション」のコードを見てみます.

celputの引数の浮動小数点はいくつまで減らせるでしょうか?

M_PIが円周率(もちろん浮動小数点数)のマクロであることも忘れずに.

#const PATTERN_WID 1

celload dir_tv+"tamadot.png",SOZAI_WID
celdiv PATTERN_WID, 64, 64, 32, 32

repeat 100
redraw 0: boxf
pos 100,100

celput PATTERN_WID, 31, 2,2, M_PI*2.0/4.0 + (M_PI*2.0*(3.0/4.0)/100)*cnt

redraw: wait 1
loop

答えは0個.円周率どこいったのー!

    celput PATTERN_WID, 31, 2,2, 573204 + 659213*cnt

以下,求め方(PI*2/4 が何故573204になるのか)について書きます.

要は,x \ (PI*2)が目標値に近くなるような整数xを求めたいです.(\は剰余)

ラジアン以外の数学の知識は必要ありません.ちょっとだけコーディングするだけです.


  1. 2つの角度の差を求める関数diffangle(double ang1, double ang2)を作る.


  2. 0から999999の整数の中で,diffangle(x\(PI*2), 目標値)が最小となるような値を全探索する.

HSPで書いたコードは以下のようになります.

#module

#defcfunc diffangle double ang1, double ang2
mod = M_PI*2
dim a,3
a.0 = absf((ang1 - ang2 - mod)\mod)
a.1 = absf((ang1 - ang2 )\mod)
a.2 = absf((ang1 - ang2 + mod)\mod)
sortval a
return a.0
#global

target = M_PI/2

bestx = 0
bestdiff = 999

repeat 1000000
x = cnt
d = diffangle(target, x)
if (d < bestdiff){
bestdiff = d
bestx = x
}
loop

mes "answer: "+bestx
mes "diff: "+strf("%e",bestdiff)

先程の573204が求まります.


モジュール展開

ランキング機能を用いる際,hsptv.asをインクルードすると思います.

#ifndef __hsptv__

#define __hsptv__
#runtime "hsptv"
#regcmd 18
#cmd hsptv_send $00

#module hsptv
#define global HSPTV_RANK_MAX 30

#deffunc hsptv_up int _p1, str _p2, int _p3

buf=""
hsptv_send buf,_p1,_p2,_p3
return

#deffunc hsptv_getrank var _p1, var _p2, var _p3, int _p4

notesel buf
i=_p4*3
noteget _p2, i
_p1=0+_p2
noteget _p2, i+1
noteget _p3, i+2
noteunsel
return

#global
#endif

本当に必要なのは,次の3行だけです.

それ以外の部分は何のためにあるのかというと,hsptvの機能を分かりやすく安全に使うためのwrapperです.

#runtime "hsptv"

#regcmd 18
#cmd hsptv_send $00

hsptv_up 命令,hsptv_getrank 命令が5回10回現れないならば,#include "hsptv.as"せずに,直接埋め込めこむことで#deffunc分のオブジェクトファイルサイズを削減出来ます.

次の sample/hsptv/hsptv_test.hsp を例にあげてみます.start.axのサイズは944byteでした.


hsptv_test.hsp

#include "hsptv.as"

score=1000
sdim comm,64
mes "HSPTVデータの更新登録テスト"
hsptv_up -1,"" ; 最初に情報を更新しておく
gosub *update ; ランキング情報の表示

pos 500,32:objsize 120,24
mes "スコア"
input score
mes "コメント"
input comm
button "更新",*send
button "終了",*ok
dialog dirinfo(0)
stop

*send
hsptv_up score, comm
gosub *update
stop
*ok
end

*update
color 255,255,255
boxf 0,32,500,480 ; 背景をクリア
color 0,0,0
pos 0,32
repeat 10 ; 上位10位のみ表示
hsptv_getrank rscore,ruser,rcomm,cnt ; 情報を取得する
rank=cnt+1
mes "#"+rank+":"+rscore+"("+ruser+") "
mes " コメント:"+rcomm
loop
return


#include "hsptv.as" の代わりに先程の3行を埋め込みます.

hsptv_send にはバッファが必要なので,notesel buf で宣言します.1

hsptv_uphsptv_sendに,hsptv_getranknotegetに書き換えます.

結果,start.axのサイズは228byte減少し,716byteになりました.

#runtime "hsptv"        ; hsptv.as

#regcmd 18 ;
#cmd hsptv_send $00 ;

notesel buf ; hsptv_send用バッファ

score=1000
sdim comm,64
mes "HSPTVデータの更新登録テスト"
hsptv_send buf,-1,"" ; 情報更新のhsptv_upをhsptv_sendに書き換え
gosub *update

pos 500,32:objsize 120,24
mes "スコア"
input score
mes "コメント"
input comm
button "更新",*send
button "終了",*ok
stop

*send
hsptv_send buf,score,comm ; 情報更新のhsptv_upをhsptv_sendに書き換え
gosub *update
stop
*ok
end

*update
color 255,255,255
boxf 0,32,500,480
color 0,0,0
pos 0,32
repeat 10
noteget rscore, cnt*3 ; hsptv_getrankをnotegetに書き換え
noteget ruser, cnt*3+1 ;
noteget rcomm, cnt*3+2 ;
rank=cnt+1
mes "#"+rank+":"+rscore+"("+ruser+") "
mes " コメント:"+rcomm
loop
return

おまけ.もう少し頑張ってみました.

サブルーチンの移動やstrfの導入等の改善を行って614byteです.

スパゲティ感.

#runtime "hsptv"        ; hsptv.as

#regcmd 18 ;
#cmd hsptv_send $00 ;

notesel buf

score=1000
comm=""
mes "HSPTVデータの更新登録テスト"
hsptv_send buf,-1

pos 500,32:objsize 120,24
mes "スコア"
input score
mes "コメント"
input comm
button "更新",*send
button "終了",*ok

*update
hsvcolor ,,255
boxf
color
pos 0,32
repeat 10
c3 = cnt*3
noteget rscore, c3
noteget ruser, c3+1
noteget rcomm, c3+2
mes strf("#%d:%s(%s)",cnt+1,rscore, ruser)
mes " コメント:"+rcomm
loop
stop
*send
hsptv_send buf,score,comm
goto *update
*ok
end





  1. hsptv_test.hspではnotesel は使われていないので,buf の為の1度だけで済みます.別の場所でnotesel が使われている場合はこの限りではありません.