ActionScript Workers: Sharing ByteArrays
ActionScript workers allow you to take advantage of today’s multi-core processors by creating multiple threads of execution. These threads will invariably need to share some data between them. By default, all data passed between the workers/threads is copied, which can be really slow. The ByteArray
class can be shared without copying. Today’s article discusses this and talks about some quirks that come along with it.
Sharing a ByteArray
is really easy. The first step is to mark it as shareable
:
bytes.shareable = true;
Then you set it as a shared property on a worker:
worker.setSharedProperty("bytes", bytes);
The worker then retrieves the shared bytes:
var bytes:ByteArray = Worker.current.getSharedProperty("bytes");
All of this is done without any copying of the ByteArray
, which could be disastrously slow if the ByteArray
is quite long. So now that two workers can read and write the same bytes, can they collaborate with the length
and position
fields, too? That is, do all workers/threads with the ByteArray
see the same length
and position
fields?
The following simple test app answers the question in a mere 70 lines:
package { import flash.display.Sprite; import flash.events.Event; import flash.utils.ByteArray; import flash.text.TextField; import flash.text.TextFieldAutoSize; import flash.system.Worker; import flash.system.WorkerDomain; import flash.system.MessageChannel; public class ByteArrayTest extends Sprite { private var logger:TextField = new TextField(); private function row(...cols): void { logger.appendText(cols.join(",")+"\n"); } public var worker:Worker; public var workerToMain:MessageChannel; public function ByteArrayTest() { logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); if (Worker.current.isPrimordial) { startMainThread(); } else { startWorkerThread(); } } private function startMainThread(): void { var bytes:ByteArray = new ByteArray(); bytes.length = 20; bytes.position = 10; bytes.shareable = true; row("Reporter", "Length", "Position"); row("Main", bytes.length, bytes.position); worker = WorkerDomain.current.createWorker(this.loaderInfo.bytes); workerToMain = worker.createMessageChannel(Worker.current); workerToMain.addEventListener(Event.CHANNEL_MESSAGE, onMessage); worker.setSharedProperty("mc", workerToMain); worker.setSharedProperty("bytes", bytes); worker.start(); } private function startWorkerThread(): void { var mc:MessageChannel = Worker.current.getSharedProperty("mc"); var bytes:ByteArray = Worker.current.getSharedProperty("bytes"); mc.send(["Worker", bytes.length,bytes.position]); } private function onMessage(ev:Event): void { row.apply(null, (ev.target as MessageChannel).receive()); } } }
It prints this:
Reporter,Length,Position Main,20,10 Worker,20,0
So the two workers are sharing the length
field, which is very handy. However, they are unfortunately not sharing the position
field. This can lead to some very strange behavior if you don’t know this. For example, all of the readX
and writeX
functions of ByteArray
rely on the position
field. For example:
bytes.position = 70; bytes.readByte(); bytes.position == 71;
Since each worker has its own position
, two workers can’t collaborate directly using these functions. That is, if worker A calls readByte
it will not affect worker B’s position
and worker B will get the same byte when it calls readByte
. To work around this, you’ll need to pass the position
field back and forth with either setSharedProperty
and getSharedProperty
or with MessageChannel
.
Aside from this quirk of shared ByteArray
instances, it should serve as your best shared data structure because of its shareable
property. Other classes like Array
, Vector
, and Dictionary
are all subject to expensive copying operations, so it’s best to use them sparingly or not at all.
Spot a bug? Have a question or suggestion? Post a comment!
#1 by Deril on November 25th, 2013
Hi, I have one concept I am working on to overcome position problem.
Idea is – to create helper class, that would claim bytes from shared ByteArray.. (for various data types.)
Then have both workers use same helper class to read/write data from shared ByteArray.
this is work in progress concept : https://github.com/MindScriptAct/mvcEpress-extension-workers/wiki/BytesProxy-concepts-for-worker-extension
What do you think about it?
#2 by jackson on November 25th, 2013
This sounds like it could be a really easy-to-use way to overcome the issue for those who want to effectively share the
position
field. You might want to check out some of my recent articles about message passing between workers to get some ideas about the performance and convenience of a few different strategies that will allow you to effectively share theposition
field.#3 by henke37 on November 25th, 2013
I think it is more of a blessing that the position isn’t shared. It being shared would lead to hard to debug errors. Besides, you nearly never want it shared anyway, since the most complex operation is to write data in one thread and then read it from another. You would need to reset the position anyway in that scenario.
#4 by Benjamin Guihaire on November 25th, 2013
I totally agree on that,
Also, it would be interesting too see if the endian property is shared.
and look at the effect of the clear() function when using the byteArray on 2 threads (would it reset the position to 0 on both threads ?)
#5 by jackson on November 25th, 2013
Sounds like the makings of a followup article. :)
#6 by Glidias on February 13th, 2014
As with all “sharable” instances, only the underlying data is shared , not the instance itself. I guess if they did a strict equality with mutexInstance === mutexInstance as well, it’ll probably return false (not that it really matters, though..). I haven’t confirmed this yet except for sharable bytearrays. “Endianness” is how the data is read/intepreted. I doubt it’s shared (not too sure about this tho, i didn’t bother to check but just use a safe pessimist assumption). But the length is obviously syncronised, as confirmed by a developer. http://forums.adobe.com/message/5803611#5803611
#7 by Dawson Valdes on February 20th, 2014
The workers having a different position from main thread is not an issue for me. For my use the main thread writes to the array and the workers read from it. It would be a problem for me if they did share the same position. However, it would be nice if the workers all shared the same position and bytesAvailable property. This is only an issue when there are multiple read workers. If there is only one worker reading from the array then there is no problem at all.
#8 by Scott Kell on June 19th, 2014
Thanks for the useful article.
I’ve been playing with shared memory between a main and worker swf. I’m trying to understand under what conditions the shared ByteArray will be garbage collected. For a non-worker usage of ByteArray, I can see the data garbage collected if I clear it and null the reference. Like this:
// Create and ‘fill’ ByteArray
var memory:ByteArray = new ByteArray();
memory.length = 1000000; // 1 Mb
var lastMem:uint = System.totalMemory;
// clear and null so it can be GC-ed
memory.clear();
memory = null;
System.gc(); // mark
System.gc(); // sweep
trace(“Freed:”+(lastMem-System.totalMemory));
However, for a shared ByteArray between worker and thread, I cannot seem to find the magic set of circumstances under which it will be garbage collected. The code to do this is a bit long, so I will not include it.
I’ve tried clearing the data on both sides, changed shareable to false on both sides, ensured all references are null-ed, etc. I’m concerned that by the nature of sending the shareable data across from main to worker, there is some type of internal reference to it and it cannot be GC-ed. I can’t find any Adobe documentation on the subject of garbage collecting shared ByteArrays. Wonder if you have any experience with this?
#9 by jackson on June 20th, 2014
It sounds like you’re doing all the right things to get the GC to collect it. The only other step I can think of is to shut down the worker. You might also want to wait for a while to see if it gets collected eventually. Even with two
System.gc()
calls it may night collect immediately. If all else fails, submit a bug with Adobe.