(この記事は 自身のブログ の記事 Cocos2d-x のカスタムアロケータを試してみた からの転載になります。)
メモリアロケーションとは
(間違っているところもあるかもしれないので、間違いがあればご指摘いただければ幸いです)
プログラムを動かす際には、プログラムやデータの領域をメモリ上に確保することが必要です。
その際、
- スタック領域にメモリ領域を確保(自動メモリアロケーション)
- ヒープ領域にメモリ領域を確保(動的メモリアロケーション)
の大きな二つがあります。
簡単に言うと、一つ目はnew
を使わずに変数領域を確保する方法、二つ目はnew
を使って変数領域を確保する方法です。
スタック上に確保すると、変数の生存期間がそのスタックだけに限られることもあるため、スタックを出ても領域を確保しておくためには、ヒープ領域に確保することが必要です。(つまりnew
を使って確保する必要がある)
動的メモリアロケーションを使用する場合、確保するメモリ領域が連続して空いている必要があり、その空いている領域を探す必要があります。また、異なるサイズの領域の確保/解放を繰り返すと、メモリのフラグメンテーションが発生し、メモリ自体は空いているものの、連続していないため確保できない、といったことが発生することがあります。
そういったこともあり、コンシューマ向けのゲーム開発では、メモリプールなどを使用したメモリアロケータを自前で作成することが一般的なようです。
また、new
関数をオーバーロードし、メモリ動作を自前のものにするなどもするようです。(こちらはデバッグのため?)
参考
Cocos2d-x でのメモリアロケータ
Cocos2d-x でもデフォルトでは、デフォルトのnew
関数やdelete
関数がメモリ管理に使用されていますが、パフォーマンス向上のためのメモリ管理についての議論がフォーラムでなされ、v3.4からカスタムアロケータの機能が追加されています。
v3.4のリリースノートにカスタムアロケータの使い方について書かれているのですが、わかりづらい部分もあったので、順を追って説明します。
使い方
この説明では、cocos2d-x v3.6を使って説明を行います。
カスタムアロケータのための設定として、ccConfig.h
ファイルにdefine
が追加されています。
デフォルトの設定では
#ifndef CC_ENABLE_ALLOCATOR
# define CC_ENABLE_ALLOCATOR 0
#endif
#ifndef CC_ENABLE_ALLOCATOR_DIAGNOSTICS
# define CC_ENABLE_ALLOCATOR_DIAGNOSTICS CC_ENABLE_ALLOCATOR
#endif
#ifndef CC_ENABLE_ALLOCATOR_GLOBAL_NEW_DELETE
# define CC_ENABLE_ALLOCATOR_GLOBAL_NEW_DELETE 0
# endif//CC_ENABLE_ALLOCATOR_GLOBAL_NEW_DELETE
#ifndef CC_ALLOCATOR_GLOBAL
# define CC_ALLOCATOR_GLOBAL cocos2d::allocator::AllocatorStrategyDefault
#endif
#ifndef CC_ALLOCATOR_GLOBAL_NEW_DELETE
# define CC_ALLOCATOR_GLOBAL_NEW_DELETE cocos2d::allocator::AllocatorStrategyGlobalSmallBlock
#endif
となっているかと思います。
追加されたdefine
-
CC_ENABLE_ALLOCATOR
- これを
1
に設定することで、カスタムアロケータに関する設定がビルドに加わります。
- これを
-
CC_ENABLE_ALLOCATOR_DIAGNOSTICS
- デフォルトでは、
CC_ENABLE_ALLOCATOR
と同じ値に設定されるようになっているため、CC_ENABLE_ALLOCATOR
を1
に設定すると、こちらも設定されます。これを設定することで、Console
からメモリ状態などを確認することができるようになります。
- デフォルトでは、
-
CC_ENABLE_ALLOCATOR_GLOBAL_NEW_DELETE
- これを
1
に設定すると、new
やdelelte
がCC_ALLOCATOR_GLOBAL_NEW_DELETE
で設定したもので置き換えられます。
- これを
-
CC_ALLOCATOR_GLOBAL
- グローバルアロケータの設定。デフォルトでは
cocos2d::allocator::AllocatorStrategyDefault
が使用される設定となっています。
- グローバルアロケータの設定。デフォルトでは
- CC_ALLOCATOR_GLOBAL_NEW_DELETE
-
new
やdelete
関数を置き換える際の置き換え方の設定。デフォルトではcocos2d::allocator::AllocatorStrategyGlobalSmallBlock
が使用されるようになっています。
Allocator
-
Default Allocator
-
malloc
とfree
のラッパー。現状メモリのトラッキングなどはできないが今後追加される可能性もある
-
-
General Allocator
- 固定長サイズのアロケータを複数用意し、一番適したサイズのアロケータを使ってメモリ領域を確保する。
cocos2d::allocator::AllocatorStrategyGlobalSmallBlock
というクラスがこれのことで、4
,8
,16
,32
,64
,128
,256
,512
,1024
,2048
,4096
,8192
バイトの固定長サイズのアロケータが準備され、これ以上のサイズのものはデフォルトのアロケータが使用されるようになっている。
- 固定長サイズのアロケータを複数用意し、一番適したサイズのアロケータを使ってメモリ領域を確保する。
-
Fixed Block Allocator
- 固定長サイズのアロケータ。同じサイズのブロックを複数用意し、そのブロックを確保/開放するので、メモリ領域の検索などが必要ないため動作が速い。
-
Pool Allocator
- 特定の型のためのアロケータ。
とりあえず試してみる
簡単に動作を試すため、
# define CC_ENABLE_ALLOCATOR 1
# define CC_ENABLE_ALLOCATOR_GLOBAL_NEW_DELETE 1
# define CC_ALLOCATOR_GLOBAL_NEW_DELETE cocos2d::allocator::AllocatorStrategyGlobalSmallBlock
に設定します。このように設定することで、カスタムアロケータ機能が追加され、new
関数を使った場合も、cocos2d::allocator::AllocatorStrategyGlobalSmallBlock
が使用されるようになります。
Console機能を追加するため、AppDelegate.cpp
のapplicationDidFinishLaunching
関数の中で、
auto director = Director::getInstance();
auto console = director->getConsole();
console->listenOnTCP(1234);
のように設定も行います。このように設定することで、localhost
の1234
番ポートを開いた状態でアプリが起動します。
今回のテストでは、cocos new
コマンドでデフォルトのテンプレートから作られるサンプルアプリケーションを使用します。そうしてiOS版のビルドを行った上で起動し、
$ telnet localhost 1234
とターミナルから実行すると、
$ telnet localhost 1234
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
>
このような画面が表示されると思います。そこで、
> allocator
と実行すると、今回の場合では、
GlobalSmallBlock::8192 initial:100 count:1 highest:3
GlobalSmallBlock::4096 initial:100 count:8 highest:8
GlobalSmallBlock::2048 initial:100 count:13 highest:14
GlobalSmallBlock::1024 initial:100 count:13 highest:13
GlobalSmallBlock::512 initial:100 count:49 highest:51
GlobalSmallBlock::256 initial:100 count:89 highest:97
GlobalSmallBlock::128 initial:100 count:275 highest:279
GlobalSmallBlock::64 initial:100 count:369 highest:373
GlobalSmallBlock::32 initial:100 count:356 highest:367
GlobalSmallBlock::16 initial:100 count:77 highest:82
GlobalSmallBlock::8 initial:100 count:89 highest:90
GlobalSmallBlock::4 initial:100 count:0 highest:0
GlobalSmallBlock::4 allocated:0
GlobalSmallBlock::8 allocated:712
GlobalSmallBlock::16 allocated:1232
GlobalSmallBlock::32 allocated:11392
GlobalSmallBlock::64 allocated:23552
GlobalSmallBlock::128 allocated:35200
GlobalSmallBlock::256 allocated:22784
GlobalSmallBlock::512 allocated:25600
GlobalSmallBlock::1024 allocated:14336
GlobalSmallBlock::2048 allocated:26624
GlobalSmallBlock::4096 allocated:32768
GlobalSmallBlock::8192 allocated:8192
Total:202,392
のような結果となりました。GlobalSmallBlock::xx
の部分がブロックサイズ、initial
が最初に確保したブロックの数、count
が現在の確保ブロック数、highest
が最大のブロック数のようです。
プールアロケータの使用
リリースノートに、プールアロケータの導入方法も書かれていましたが、その方法ではうまく行かなかったので、自分がやった方法を補足しておきます。今回はサンプルアプリケーションを使用しているので、HelloWorldScene.cpp
とHelloWorldScene.h
を以下のように変更しています。
HelloWorldScene.h
の変更点としては、
- アロケータ関連のヘッダの追加
- プールアロケータのstatic変数の確保
-
CC_USE_ALLOCATOR_POOL
マクロを使って、確保したstatic変数を使用してメモリ確保を行うように -
create
関数を用意-
CREATE_FUNC
マクロではnew(nothrow)
を使うようになっており、エラーが出ていたので、マクロを使わず自分で定義しています。
-
HelloWorldScene.cpp
の変更点としては、
- static変数の定義
を加えています。
このように変更を行うことで、先ほどと同じようにConsoleからメモリアロケーションの情報を見てみると、
HelloWorldPool initial:100 count:1 highest:1
GlobalSmallBlock::8192 initial:100 count:13 highest:19
GlobalSmallBlock::4096 initial:100 count:36 highest:54
GlobalSmallBlock::2048 initial:100 count:50 highest:232
GlobalSmallBlock::1024 initial:100 count:214 highest:284
GlobalSmallBlock::512 initial:100 count:59 highest:822
GlobalSmallBlock::256 initial:100 count:907 highest:11583
GlobalSmallBlock::128 initial:100 count:479 highest:41864
GlobalSmallBlock::64 initial:100 count:3661 highest:3867
GlobalSmallBlock::32 initial:100 count:269 highest:697
GlobalSmallBlock::16 initial:100 count:214 highest:300
GlobalSmallBlock::8 initial:100 count:40 highest:528
GlobalSmallBlock::4 initial:100 count:0 highest:0
GlobalSmallBlock::4 allocated:0
GlobalSmallBlock::8 allocated:320
GlobalSmallBlock::16 allocated:3424
GlobalSmallBlock::32 allocated:8608
GlobalSmallBlock::64 allocated:234240
GlobalSmallBlock::128 allocated:61312
GlobalSmallBlock::256 allocated:232192
GlobalSmallBlock::512 allocated:30208
GlobalSmallBlock::1024 allocated:220160
GlobalSmallBlock::2048 allocated:102400
GlobalSmallBlock::4096 allocated:147456
GlobalSmallBlock::8192 allocated:106496
Total:1146816
のようになり、HelloWorldPool
が追加されているのがわかります。
まとめ
Cocos2d-xのカスタムアロケータについて紹介し、Console機能からアロケーション情報について調べる方法について紹介しました。
追加や削除が頻繁に行われるクラスに対してはメモリプールを導入することで動作も早くなり、フラグメンテーションも起きづらくなることが期待されます。
また、メモリプールもデフォルトでは100ブロック確保するようになっていますが、Consoleからhighestの値を見て、適切な値を設定することにより、無駄なメモリ領域の確保をなくしたり、逆に確保するブロックが足りなくなるといったこともなくなるかとおもわれます。
ゲーム全体にすぐ導入はできないかもしれませんが、パフォーマンス・チューニングのために使ってみるのも良いかもしれません。