LoginSignup
4
2

More than 3 years have passed since last update.

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

Last updated at Posted at 2018-12-08

はじめに

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

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

追記:2019年以降からhsptv部門が消えた。辛い。

注意事項

可読性が酷く落ちます

-

共通部分を纏める

共通している部分があったら,変数でまとめてみましょう.以下の例では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 が使われている場合はこの限りではありません. 

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2