flash.sampler っていうパッケージを最近知ったので使ってみたかっただけです。
メモリ管理のコツとかそういう tips 的なことは一切書いてないですごめんなさい。。
注) 32bit CPU での検証結果です
flash.sampler.getSize() で各クラスのサイズを調べてみました。
1 2 3 4 5 6 7 8 9 10 |
import flash.sampler.getSize; // トップレベルのコアクラス (Error のサブクラスなどは除く) var top_level:Array = [Array, Boolean, Date, Error, Function, int, Namespace, Number, Object, RegExp, String, uint, Vector.<int>, Vector.<String>, XML, XMLList]; for(var i:int=0; i<top_level.length; i++) { trace(top_level[i].toString() + ' : ' + getSize(new top_level[i]())); } |
・出力結果 (bytes)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[class Array] : 56 [class Boolean] : 4 [class Date] : 48 [class Error] : 56 [class Function] : 710 [class int] : 4 [class Namespace] : 20 [class Number] : 4 [class Object] : 40 [class RegExp] : 64 [class String] : 24 [class uint] : 4 [class Vector.<int>] : 56 [class Vector.<String>] : 60 [class XML] : 64 [class XMLList] : 48 |
きっと4バイト境界でメモリがアラインされてると思うのですが、
Function の値が不思議なのと、Number が4なのはどうして?
というかそもそも flash.sampler.getSize() で戻ってくる値は一体なんなのか。。
せっかくなので他のパッケージの主要(?なクラスも測ってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
[class Bitmap] : 232 [class ByteArray] : 92 [class Camera] : 44 [class ColorTransform] : 80 [class Dictionary] : 56 [class Loader] : 304 [class Matrix] : 64 [class Matrix3D] : 84 [class MovieClip] : 436 [class Point] : 32 [class Shape] : 232 [class SimpleButton] : 248 [class Socket] : 80 [class Sound] : 8384 [class Sprite] : 400 [class TextField] : 1288 [class TextFormat] : 104 [class Rectangle] : 48 [class RegExp] : 64 [class URLLoader] : 60 [class URLRequest] : 44 [class URLStream] : 72 [class URLVariables] : 40 [class Video] : 240 [class XMLSocket] : 104 |
Sound と TextField が一桁大きいです。
次に、ユーザー定義クラスについて。
以下のようなメンバを持たないクラスのサイズを調べます。
1 2 3 4 5 6 7 |
public class Struct { public function Struct() { } } // Struct クラスのサイズを調べる trace(getSize(new Struct()); // -> 16 |
メンバを持つ場合は?
1 2 3 4 5 6 7 8 9 |
// Sprite のメンバを持つクラス import flash.display.Sprite; public class Struct { private var a:Sprite; public function Struct() { } } trace(getSize(new Struct()); // -> 20 |
Sprite型のメンバを持っていても 416 とは返ってきません。
この場合、保持するデータ型自体のサイズではなく参照のサイズが加算されています。
なんとなくわかってきました。
次はコンテナに詰め込んで試してみます。
1 2 3 4 5 |
var struct_arr:Array = []; for(var i=0; i<10; i++) { struct_arr[i] = new Struct(); } trace("size: ", getSize(struct_arr) - getSize([])); // -> size: 48 |
あれ?40bytes じゃない?
trace をループの中に置いてみます。
1 2 3 4 5 |
var struct_arr:Array = []; for(var i=0; i<10; i++) { struct_arr[i] = new Struct(); trace("size: ", getSize(struct_arr) - getSize([])); } |
1 2 3 4 5 6 7 8 9 10 |
size: 16 size: 16 size: 16 size: 16 size: 24 size: 24 size: 32 size: 32 size: 48 size: 48 |
なるほど。一要素ずつちまちまとアロケートしないってことですね。
じゃあ Vector は?
1 2 3 4 5 6 |
var struct_vec:Vector.<Struct> = new Vector.<Struct>; var vec:Vector.<Struct> = new Vector.<Struct>; for(var i:int=0; i<10; i++) { struct_vec[i] = new Struct(); trace("size: ", getSize(struct_vec) - getSize(vec)); } |
1 2 3 4 5 6 7 8 9 10 |
size: 4 size: 8 size: 12 size: 20 size: 20 size: 28 size: 28 size: 40 size: 40 size: 40 |
ループの長さに比例した大きな単位でメモリを確保するようになっているようです。
(Array と Vector でルールは多少違うようですが)
ユーザ定義クラスではなく、int や Number でも同様でした。
気になる人は追試してみてください。
また、要素数100万のループでアロケートの回数を数えてみると、
Array:45回
Vector:57回
と、 Vector の方がアロケートの回数自体は多いという結果に。
Vector には fixed プロパティというものがあり、コンテナの長さを固定できるようになっています。
fixed を true にするとアロケートの回数は当然インスタンス生成時の1回限りです。
この fixed の有無でたいした差は出ないという内容のエントリーを国内外でたくさん見ましたが、
何十, 何百万要素のループくらいになると、メモリのアロケートがループ後半ではあまり起きないため、
たいしたパフォーマンスの差が出なかったということなんでしょう。
また、fixed を true にすると、コンテナのサイズは “参照のサイズ×要素数” ちょうどになります。
つまり要素数100万の場合は40,000,000bytesです。
fixed を false にすると、ループ中は上述のようにまとまった塊でメモリを確保していくので、
新たなメモリを確保した瞬間にループを抜けると、必要のない領域までガメてしまうことになります。
これは結構大切な事だと思うので覚えておくといいかもしれません。
せっかくなので ByteArray でも試してみます。
1 2 3 4 5 6 |
var struct_ba:ByteArray = new ByteArray(); var ba:ByteArray = new ByteArray(); for(var i:int=0; i<10; i++) { struct_ba[i] = new Struct(); trace("size: ", getSize(struct_ba) - getSize(ba)); } |
1 2 3 4 5 6 7 8 9 10 |
size: 4096 size: 4096 size: 4096 size: 4096 size: 4096 size: 4096 size: 4096 size: 4096 size: 4096 size: 4096 |
これ ByteArray はページ単位で管理してるってことですかね。
ページ境界をまたいだアクセスが頻発すると大変なことになりそうです。
インデックスの 1024 にアクセスしても返される値は変わりませんでしたが、
4096 にアクセスした瞬間にまたメモリをガメてきました。
ここでおもむろに、
getSize(struct_ba[1]) や getSize(struct_ba[1024])
とするといずれも 4 が返ってくることを確認。
なるほど、ByteArray と getSize() のことがわかってきました。
次に、最初に疑問を投げた Number型 が 4 を返すことについて。
F-size さんで以下のようなエントリーを見つけました。
F-site | [AS3] プリミティブ型データとメモリ
どうやら4bytesで抱えきれない値を代入すると8bytesにリアロケートするようです。
1 2 3 4 5 6 |
var n:Number = 1; trace('Number size: ',getSize(n)); n = Math.pow(2, 28); trace('Number size: ',getSize(n)); n = 1; trace('Number size: ',getSize(n)); |
1 2 3 |
Number size: 4 Number size: 8 Number size: 4 |
なんと、再度小さな値を入れると4bytesにリアロケートされます。
これけっこうなコストだと思うのですが、、
Flashで大きな数値を扱うことは少ないからでしょうか、ちょっとわかりません。
他にもいろいろ調べられそうですが、今回はこの辺で。
あとはFlash本業の方に任せようと思いますw
flash.sampler パッケージを使ってオレオレプロファイラ作るのも楽しいかもしれません。
2 Thoughts