HSPで開発していると、「この処理、重いからDLL化したほうがいいのかな?」と悩むことがあります。 しかし、すべてをDLL化すればいいわけではありません。DLL化には開発コストがかかりますし、場合によってはかえって遅くなることもあります。

この記事では、DLL化すべき処理HSPのまま書くべき処理の判断基準を、具体例とともに解説します。

目次

  1. DLL化のメリット・デメリット
  2. DLL化すべき処理
  3. DLL化すべきでない処理
  4. 特殊なアーキテクチャパターン:HSPを「Drawer」として使う
  5. 判断基準のフローチャート
  6. 実装例
  7. まとめ

DLL化のメリット・デメリット

メリット

1. パフォーマンスの向上

C/C++でコンパイルされたネイティブコードは、インタプリタ言語であるHSPに比べて圧倒的に高速です。

  • ループ処理:10倍~100倍以上高速化することも
  • 浮動小数点演算:5倍~50倍程度高速化
  • メモリ操作:直接操作により大幅に高速化

2. 外部ライブラリの利用

HSPから直接使えない既存のライブラリを活用できます。

  • 画像処理ライブラリ(OpenCV、stb_image等)
  • 暗号化ライブラリ(OpenSSL等)
  • データベースライブラリ(SQLite等)
  • ネットワークライブラリ

3. コードの再利用性

一度DLLを作れば、複数のHSPプロジェクトで使い回せます。

4. 知的財産の保護

DLLはコンパイル済みのため、ロジックを隠蔽できます(完全ではありませんが)。

デメリット

1. 開発コストの増加

  • C/C++の知識が必要
  • デバッグが難しくなる
  • ビルド環境の構築が必要

2. 移植性の低下

  • プラットフォーム依存(Windows/Linux/Mac等)
  • アーキテクチャ依存(x86/x64/ARM等)

3. オーバーヘッド

HSPとDLL間の呼び出しにはコストがかかります。処理が軽すぎると、このオーバーヘッドで逆に遅くなることも。

4. 配布ファイルの増加

DLLファイルを別途配布する必要があり、管理が煩雑になります。


DLL化すべき処理

1. 大量のループ処理

例:配列の全要素に対する計算

; HSPでの実装(遅い)
dim data, 1000000
repeat 1000000
    data(cnt) = cnt * cnt + cnt * 2 + 1
loop

このような単純な計算でも、100万回のループになるとHSPでは数秒かかります。 DLL化すれば数十ミリ秒で完了します。

判断基準:

  • ループ回数が1万回以上
  • ループ内で複雑な計算を行う
  • 頻繁に実行される処理

2. 数値演算が中心の処理

例:

  • 物理演算(衝突判定、重力計算等)
  • 画像処理(フィルタ、色変換等)
  • 暗号化・ハッシュ計算
  • 数学的計算(行列演算、統計処理等)
; 物理演算の例(DLL化推奨)
; 多数のオブジェクトの衝突判定
repeat objnum
    repeat objnum
        ; 距離計算
        dx = obj_x(cnt) - obj_x(cnt2)
        dy = obj_y(cnt) - obj_y(cnt2)
        dist = sqrt(dx*dx + dy*dy)
        if dist < obj_r(cnt) + obj_r(cnt2) {
            ; 衝突処理
        }
    loop
loop

オブジェクト数が多いと処理が重くなるため、DLL化の効果が大きいです。

3. メモリ操作が多い処理

例:

  • 大量のデータのソート
  • バイナリデータの解析・変換
  • メモリバッファの操作
; バイナリデータの解析(DLL化推奨)
; ファイルから読み込んだバイナリデータを解析
sdim buf, filesize
noteload buf, filename
; 各バイトを処理...(HSPだと遅い)

4. 既存のC/C++ライブラリを使いたい場合

例:

  • 画像処理: OpenCV、stb_image
  • 音声処理: PortAudio、libsndfile
  • 圧縮: zlib、lz4
  • データベース: SQLite
  • JSON/XML解析: RapidJSON、pugixml
  • 正規表現: PCRE、oniguruma

これらのライブラリはC/C++で書かれているため、DLL経由で使うのが最適です。

5. システムAPIを直接叩く必要がある場合

例:

  • レジストリの高度な操作
  • ファイルシステムの詳細な制御
  • プロセス・スレッド管理
  • ネットワークの低レベル制御

HSPからも#uselibで一部可能ですが、複雑な構造体を扱う場合はDLLを挟んだ方が安全です。

6. リアルタイム性が求められる処理

例:

  • ゲームの当たり判定(60FPS維持)
  • 音声・動画のリアルタイム処理
  • センサーデータのリアルタイム解析

フレームレートを維持するために、1フレーム内で処理を完了させる必要がある場合、DLL化が有効です。


DLL化すべきでない処理

1. 単純な処理・軽い処理

例:

; これをDLL化する必要はない
a = 10
b = 20
c = a + b

理由:

  • DLL呼び出しのオーバーヘッドの方が大きい
  • HSPでも十分高速
  • 開発コストに見合わない

判断基準:

  • ループ回数が数百回以下
  • 処理時間が1ミリ秒未満
  • 頻繁に呼び出さない処理

2. HSPの標準命令で十分な処理

例:

; 画面描画(HSPの得意分野)
gsel 0
color 255, 0, 0
boxf 100, 100, 200, 200
pos 110, 120
mes "Hello, HSP!"

理由:

  • HSPの描画命令は内部的に最適化されている
  • DLL化してもほとんど速くならない
  • コードの可読性が下がる

3. UIの制御・ユーザー入力の処理

例:

; ボタン配置やイベント処理
button "OK", *on_click
stop
*on_click
    dialog "クリックされました"
    return

理由:

  • HSPのUI系命令は使いやすく、DLL化のメリットが少ない
  • イベント駆動の処理はHSPで書いた方がシンプル
  • デバッグがしやすい

4. 頻繁に仕様変更がある処理

例:

  • ゲームのバランス調整パラメータ
  • UIのレイアウト調整
  • テキストメッセージの変更

理由:

  • DLL化すると再コンパイル・再配布が必要
  • HSPなら即座に修正・テスト可能
  • 開発スピードが重要

5. プロトタイピング段階の処理

開発初期段階では、すべてHSPで書くべきです。

理由:

  • 仕様が固まっていない
  • 何度も書き直す可能性がある
  • まず動くものを作ることが優先

戦略:

  1. Phase 1: すべてHSPで実装
  2. Phase 2: ボトルネックを特定(gettime等で計測)
  3. Phase 3: 本当に遅い部分だけDLL化

6. デバッグが複雑な処理

例:

  • 状態管理が複雑な処理
  • 多数の条件分岐がある処理
  • エラー処理が重要な処理

理由:

  • C/C++のデバッグはHSPより難しい
  • クラッシュすると原因特定が困難
  • まずHSPで動作確認してからDLL化を検討

特殊なアーキテクチャパターン:HSPを「Drawer(描画エンジン)」として使う

概要

HSPでは構造体の扱いが難しく、複雑なゲームロジックを実装するのが大変です。 そこで、ゲームロジック全体をC++/DLLで実装し、HSPは描画とUI表示のみを担当するというアーキテクチャがあります。

このパターンが適している場合

複雑なゲームロジック

  • オブジェクト指向設計が必要
  • 多数のエンティティ管理(敵、弾、アイテム等)
  • 複雑な状態管理(ゲームステート、AI等)

構造体・クラスを多用したい

  • HSPでは構造体の扱いが制限的
  • C++ならクラス、継承、ポリモーフィズムが使える

大規模プロジェクト

  • コードの保守性・再利用性を重視
  • チーム開発で役割分担したい

アーキテクチャ例

┌─────────────────────────────────┐
│  HSP(描画レイヤー)             │
│  - screen, color, boxf, mes      │
│  - UI表示                        │
│  - 入力の受付と転送              │
└─────────────────────────────────┘
              ↕ DLL呼び出し
┌─────────────────────────────────┐
│  C++ DLL(ロジックレイヤー)     │
│  - ゲームループ                  │
│  - 当たり判定                    │
│  - AI・物理演算                  │
│  - エンティティ管理              │
└─────────────────────────────────┘

実装イメージ

C++側(DLL):

// ゲームロジック全体を管理
class GameEngine {
    std::vector<Enemy> enemies;
    std::vector<Bullet> bullets;
    Player player;
    
public:
    void Update(float deltaTime);
    RenderData GetRenderData();  // 描画情報を返す
};

// DLL公開関数
extern "C" __declspec(dllexport) 
void GameUpdate(float deltaTime, int* inputs) {
    // ロジック更新
    gameEngine.HandleInput(inputs);
    gameEngine.Update(deltaTime);
}

extern "C" __declspec(dllexport)
int GetObjectCount() { return gameEngine.GetObjectCount(); }

extern "C" __declspec(dllexport)
void GetObjectPosition(int index, int* x, int* y) {
    auto pos = gameEngine.GetObjectPosition(index);
    *x = pos.x;
    *y = pos.y;
}

HSP側(描画):

#uselib "gamelogic.dll"
#func game_update "GameUpdate" double, var
#func get_object_count "GetObjectCount"
#func get_object_position "GetObjectPosition" int, var, var

; メインループ
screen 0, 800, 600
dim inputs, 10  ; キー入力配列

repeat
    ; 入力収集
    stick inputs(0), 15
    
    ; ゲームロジック更新(DLL)
    game_update 16.6, inputs  ; 60FPS想定
    
    ; 描画(HSP)
    redraw 0
    color 0, 0, 0 : boxf
    
    get_object_count
    objCount = stat
    
    repeat objCount
        get_object_position cnt, x, y
        ; オブジェクトを描画
        pos x, y
        mes "●"
    loop
    
    redraw 1
    await 16
loop

メリット

ロジックとビューの分離 - 役割が明確で保守しやすい
C++の強力な機能を活用 - クラス、STL、デザインパターン等
描画はHSPで手軽に - HSPの得意分野を活かせる
段階的移行が可能 - 最初はHSP、徐々にロジックをDLL化

デメリット

初期開発コストが高い - 最初からDLLインターフェース設計が必要
デバッグが複雑 - HSPとC++を行き来する必要
オーバーヘッド - 毎フレーム大量のデータをやり取りすると遅くなる可能性

判断ポイント

この「HSP as Drawer」パターンを選ぶべきかは、以下で判断しましょう。

採用を検討すべき:

  • ゲームロジックが複雑(100行以上のステート管理等)
  • オブジェクト数が多い(50個以上のエンティティ)
  • 既にC++の経験がある
  • 長期的な保守・拡張を見越している

通常のHSPで十分:

  • シンプルなゲーム(ミニゲーム、パズル等)
  • プロトタイピング段階
  • 短期間で完成させたい
  • HSP初心者

判断基準のフローチャート

以下のフローチャートで判断してみてください。

START
  ↓
処理は重い(体感で遅い)?
  ↓ YES               ↓ NO
計測してみた?      → HSPのまま
  ↓ YES
本当にボトルネック?
  ↓ YES               ↓ NO
ループ処理?         → HSPのまま
  ↓ YES               ↓ NO
ループ回数1万回以上?  数値演算が中心?
  ↓ YES               ↓ YES
DLL化検討             外部ライブラリ使う?
                      ↓ YES
                     DLL化必須
                      ↓ NO
                     HSPで最適化を試す

重要:測定せずに最適化しない!

憶測でDLL化するのではなく、必ずgettimeなどで処理時間を計測しましょう。

; 処理時間計測の例
t1 = gettime(7) * 1000 + gettime(6)  ; ミリ秒取得
; ここに計測したい処理
t2 = gettime(7) * 1000 + gettime(6)
mes "処理時間: " + (t2 - t1) + " ms"

実装例

例1:配列のソート(DLL化推奨)

HSP版(遅い):

; バブルソート(10万件で数十秒)
dim arr, 100000
; ... データ初期化 ...
repeat length(arr) - 1
    repeat length(arr) - cnt - 1
        if arr(cnt2) > arr(cnt2 + 1) {
            tmp = arr(cnt2)
            arr(cnt2) = arr(cnt2 + 1)
            arr(cnt2 + 1) = tmp
        }
    loop
loop

DLL版(高速):

; C++のstd::sortを使ったDLL
#uselib "mysort.dll"
#func quick_sort "QuickSort" int, int

dim arr, 100000
; ... データ初期化 ...
quick_sort varptr(arr), length(arr)  ; 数ミリ秒で完了

例2:文字列処理(ケースバイケース)

単純な文字列連結 → HSPのまま

; これはHSPで十分
sdim result, 10000
result = "Hello, " + name + "!"

正規表現マッチング → DLL化推奨

; 複雑なパターンマッチングはDLLで
#uselib "myregex.dll"
#func regex_match "RegexMatch" str, str, var

sdim text
text = "メールアドレス: test@example.com"
regex_match text, "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}", result

例3:画像処理(DLL化推奨)

; ぼかしフィルタなどの画像処理はDLL化で大幅高速化
#uselib "imagefilter.dll"
#func blur_filter "BlurFilter" int, int, int, double

; HSPで画像読み込み
buffer 1
picload "input.jpg"

; DLLでフィルタ処理(高速)
blur_filter 1, ginfo_winx, ginfo_winy, 5.0  ; 半径5のガウシアンブラー

まとめ

DLL化すべき処理の特徴

大量のループ処理(1万回以上)
数値演算が中心(物理演算、画像処理等)
既存のC/C++ライブラリを使いたい
リアルタイム性が必要(60FPS維持等)
メモリ操作が多い(ソート、バイナリ解析等)

HSPのまま書くべき処理の特徴

軽い処理(数百回以下のループ、単純計算)
HSPの標準命令で十分(描画、UI制御等)
頻繁に変更する処理(パラメータ調整等)
プロトタイピング段階
デバッグが複雑な処理

開発フロー

  1. まずすべてHSPで実装する
  2. 計測してボトルネックを特定する(憶測で判断しない)
  3. 本当に遅い部分だけDLL化する
  4. 効果を測定して、改善されたか確認する

過度な最適化は悪です。必要な部分だけ、適切にDLL化しましょう。


参考リンク

HSPとDLLを適切に使い分けて、高速で保守性の高いアプリケーションを作りましょう!