Get More out of Four Bytes than a Boolean
A Boolean
in AS3 takes up four bytes of memory to store a single bit of information. It takes up 32x more memory than it needs. We can make better use of this memory and today’s article explains how.
An int
, a uint
, and a Boolean
all take up 32 bits (4 bytes) of memory. A Boolean
allows two values: true
and false
. An int
allows 2^32 values: -2,147,483,648 to 2,147,483,647. A uint
allows 2^32 values: 0 to 4,294,967,295. We could just as easily store 0
for false
and anything else (e.g. 1
, 42
, -100
) as true in an int
or a uint
.
Some would argue that true
and false
are more readable than 0
and non-zero and that the implication of a Boolean
type is more specific (“put true
or false
here”) rather than the implications of an int
or uint
type (“put any integral value here”). Good variable, field, and parameter naming can help overcome some of these concerns, but AS3 is not as well equipped to really level the playing field. Readability will likely suffer from the switch from Boolean
to integers, but other areas will improve.
If you do decide to make the switch, you’ll gain a lot of flexibility from all those newly-accessible bits. One of the best things you can do is designate each bit as its own mini-Boolean
. For example, consider using a uint
as a “bit field” for a file:
Bit Index | Meaning of 1 |
---|---|
0 | Is a directory |
1 | Is a symbolic link (symlink) |
2 | Read only |
You could certainly add more and more bits to this. Now consider a function that gets file information:
static const MASK_IS_DIRECTORY:uint = 1; static const MASK_IS_SYMLINK:uint = 2; static const MASK_IS_READONLY:uint = 4; function getFileInfo(file): uint { var ret:uint; if (file is a directory) { ret |= MASK_IS_DIRECTORY; } if (file is a symlink) { ret |= MASK_IS_SYMLINK; } if (file is read only) { ret |= MASK_IS_READONLY; } return ret; } var fileInfo:uint = getFileInfo(file); if (fileInfo & MASK_IS_DIRECTORY) { trace(file + " is a directory"); } if (fileInfo & MASK_IS_SYMLINK) { trace(file + " is a symbolic link"); } if (fileInfo & MASK_IS_READONLY) { trace(file + " is read-only"); }
Here we’ve created three “mask” variables that are used to get and set each bit in the bit field. By using the bitwise “or” (|
) operator, we can set these bits: field |= MASK
. Here we’ve created three “mask” variables that are used to get and set each bit in the bit field. Unsetting a bit is similar: field &= ~MASK
. By using the bitwise “and” (&
) operator, we can get these bits: field &= MASK
. A simple uint
is all we need to store up to 32 boolean values. Here’s a cheat sheet:
// Set to true field |= MASK // Set to false (unset) field &= ~MASK // Get if (field & MASK)
Now consider the alternative for this function: a class with a bunch of Boolean
values in it.
class FileInfo { var isDirectory:Boolean; var isSymlink:Boolean; var isReadOnly:Boolean; } function getFileInfo(file): FileInfo { var ret:FileInfo = new FileInfo(); if (file is a directory) { ret.isDirectory = true; } if (file is a symlink) { ret.isSymlink = true; } if (file is read only) { ret.isReadOnly = true; } return ret; } var fileInfo:FileInfo = getFileInfo(file); if (fileInfo.isDirectory) { trace(file + " is a directory"); } if (fileInfo.isSymlink) { trace(file + " is a symbolic link"); } if (fileInfo.isReadOnly) { trace(file + " is read-only"); }
We know that a class with three Boolean
values takes up 48 bytes of memory. The uint
takes up only 4. That’s 12x less memory! There’s also the garbage collector to keep in mind. The getFileInfo
function now allocates a FileInfo
instance each time it’s called. This must be allocated by the VM and tracked by the GC. The instance is probably immediately thrown away, which triggers the GC to collect the memory. Call this function a lot and you could have a serious performance problem on your hands.
To get around this issue, you need to reuse FileInfo
instances by filling in the fields rather than creating a new one each time:
function getFileInfo(file, info:FileInfo): void { if (file is a directory) { info.isDirectory = true; } else { info.isDirectory = false; } if (file is a symlink) { info.isSymlink = true; } else { info.isSymlink = false; } if (file is read only) { info.isReadOnly = true; } else { info.isReadOnly = false; } } static const REUSED_FILE_INFO:FileInfo = new FileInfo(); getFileInfo(file, REUSED_FILE_INFO); if (REUSED_FILE_INFO.isDirectory) { trace(file + " is a directory"); } if (REUSED_FILE_INFO.isSymlink) { trace(file + " is a symbolic link"); } if (REUSED_FILE_INFO.isReadOnly) { trace(file + " is read-only"); }
The code is now arguably less readable than the version that simply used a uint
as a bit field.
Lastly, let’s make sure that using a uint
as a bit field doesn’t have any adverse performance effects compared to using a Boolean
. The following test program checks just that:
package { import flash.display.*; import flash.utils.*; import flash.text.*; public class DataDensity extends Sprite { private var logger:TextField = new TextField(); private function row(...cols): void { logger.appendText(cols.join(",") + "\n"); } private static const MASK_SOME_FLAG:uint = 0x4; public function DataDensity() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); init(); } private function init(): void { const REPS:uint = 100000000; var i:uint; var val:uint; var valTrue:uint = 0x4; var valFalse:uint = 0x0; var sum:int; var bool:Boolean; var boolTrue:Boolean = true; var boolFalse:Boolean = false; var beforeTime:int; var afterTime:int; row("Operation", "Time"); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { if (boolTrue) { sum++; } } afterTime = getTimer(); row("Boolean (true)", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { if (boolFalse) { sum++; } } afterTime = getTimer(); row("Boolean (false)", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { if (valTrue & MASK_SOME_FLAG) { sum++; } } afterTime = getTimer(); row("Flags (true)", (afterTime-beforeTime)); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { if (valFalse & MASK_SOME_FLAG) { sum++; } } afterTime = getTimer(); row("Flags (false)", (afterTime-beforeTime)); } } }
I tested this app using the following environment:
- Release version of Flash Player 13.0.0.214
- 2.3 Ghz Intel Core i7-3615QM
- Mac OS X 10.9.2
- Google Chrome 35.0.1916.114
- ASC 2.0.0 build 354130 (
-debug=false -verbose-stacktraces=false -inline -optimize=true
)
And got these results:
Operation | Time |
---|---|
Boolean (true) | 235 |
Boolean (false) | 189 |
Flags (true) | 219 |
Flags (false) | 187 |
If anything, the bit fields seem slightly faster than the Boolean
values. These results are over 100 million iterations though, so the advantage is very slight.
In conclusion, consider using bit fields over Boolean
values to save memory and GC time. Readability is always arguable, but the data density improvements are clear. You’ll store 32x more than with Boolean
Spot a bug? Have a question or suggestion? Post a comment!
#1 by makc on June 10th, 2014 ·
Okay, I am probably missing something, but does not storing the mask takes just as much bits and so we’re back where we started?
#2 by jackson on June 10th, 2014 ·
You don’t need to store the mask. The mask is just used to read and write the bits you need. That’s why it’s just a
static const uint
in the test program.