Junk Byte Code for Kicks

19Feb/129

Flash String weirdness

A long time ago...

I had this bug report in Console that FPS and memory graph is leaking memory.
Issue: http://code.google.com/p/flash-console/issues/detail?id=89
I was not able to find the exact cause of this other than the fact that if I don't update texts in graph, it doesn't show any memory problems.
I suspected back then that Strings were strange but was not able to pin point what's strange about it.

Fast forward to today

I recently learned that flash keeps 'master string' of strings when they get passed around.
See http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/sampler/package.html#getMasterString()
Quote:

For example, if you call String.substr(), the returned string will often actually be implemented as just a pointer into the original string, for the sake of efficiency. In normal usage, this is an implementation detail which is not visible to the user; however, it can be confusing when using a profiler to analyze your program's memory consumption, because the string may be shown as taking less memory than would be needed for the string's value. In addition, a string might be retained in memory solely because it is the master for other strings. getMasterString() allows profilers to show the user an accurate graph of string dependencies.

It sounds good, it is obviously saving memory by not copying the strings and reusing via referencing to original string where possible.
But...

  • What if you only need to keep a tiny part of the string (say downloaded from an external file), will it know to discard the big string?
  • What if you chain a lot of strings together, will it keep references to all its parts?
  • Could it cause memory leaks?

getMasterString() tests


public class StringMasterTest extends Sprite
	{
		private var partialString:String;
		private var twoJoinedString:String;
		private var threeJoinedString:String;
		private var loopedString:String

		public function StringMasterTest()
		{
			super();

			var longString:String = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
			partialString = longString.substring(5, 10);

			var joinPart1:String = "12345";
			var joinPart2:String = " + ";
			var joinPart3:String = "67890";

			twoJoinedString = joinPart1 + joinPart3;
			threeJoinedString = joinPart1 + joinPart2 + joinPart3;

			loopedString = "";
			for (var i:uint = 0; i < 10; i++)
			{
				loopedString += String(i);
			}

			System.gc(); // GC is only effective on next frame.

			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}

		private function onEnterFrame(event:Event):void
		{
			System.gc();

			trace("partialString master:", getMasterString(partialString));
			// 1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ

			trace("twoJoinedString master:", getMasterString(twoJoinedString));
			// null
			
			trace("threeJoinedString master:", getMasterString(threeJoinedString));
			// 12345 + 

			trace("loopedString master:", getMasterString(loopedString));
			// 01
		}
	}

NOTE: Requires flash SDK 4.5 or later for getMasterString().

Results

  • If you take a partial string from a long string, it will keep the long string.
  • If you join more than 2 strings, it will keep the second string as master string.
  • If you loop through and add strings together, it'll be the same as above, where it'll start keeping a master string from your third addition.
  • The looped string test seem to indicate that each string could have a chain of parents. This can not be conformed because we only have access to 'getMasterString()' which returns the root of the chain.

The test does not conclude how and when master strings are kept.

XML

I wasn't satisfied, so I tested with XMLs as well:


var xmlString:String = "<xml>";
xmlString += "<node value='node 0'>NODE 0</node>";
xmlString += "<node value='node 1'>NODE 1</node>";
xmlString += "</xml>";
var xml:XML = new XML(xmlString);

var nodes:XMLList = xml.node;
var node1Attribute:String = nodes[1].@value;
trace(node1Attribute , " => ", getMasterString(node1Attribute));
// node 1  =>  <xml><node value='node 0'>NODE 0</node>

This test sort of proves that strings that comes out of XML also reference to string that originally generated the XML object.
In the example, "node 1" string which came out of "nodes[1].@value" have a master string to the first part of xmlString.

So...
XML also keeps a reference to it's original string.

Does it mean, strings could leak memory?


textField = new TextField();
addChild(textField);
addEventListener(Event.ENTER_FRAME, onEnterFrame);

private function onEnterFrame(event:Event):void
{
	var string:String = String(System.totalMemory);
	textField.text = string;
}

No proof of leak here, the total memory number stays constant after several seconds.

Now another test:


public class StringTest extends Sprite
	{
		private var textField:TextField;

		private var frames:uint;

		public function StringTest()
		{
			super();

			textField = new TextField();
			textField.multiline = true;
			textField.defaultTextFormat = new TextFormat("Arial", 16);
			textField.width = 320;
			textField.height = 200;
			
			addChild(textField);

			addEventListener(Event.ENTER_FRAME, onEnterFrame);
		}

		private function onEnterFrame(event:Event):void
		{
			frames++;
			var string:String = "Memory:\t" + System.totalMemory;
			string += "\rFrames:\t" + frames;
			textField.text = string;
		}
	}

Now it shows that memory is increasing every frame.
Compiled swf:

The difference here is, I am now combining more than two strings at each tick and so it is making master strings of them.
There is some sort of persistence behavior associated with it.
This does not however mean that it is leaking memory.
Eventually the total memory should go back down when there is garbage collection on those unused strings.
System.gc() does not clear those unused Strings, it probably have a different timer.

Conclusion

  • Strings have 'parent strings'
  • XML keeps reference to it's parent string(s)
  • Strings can appear to leak memory
  • Console graphing appear to leak due to above reasons

Is it all bad?
I imagine not a big deal as long as they don't naturally cause leakages.
I generally prefer clarity over whats going on behind the scene and this is all a surprise to me.

Comments (9) Trackbacks (0)
  1. Hey, Lu-Aye! Glad you’re come to this, but memory is trashing by garbage only when FPS or Memory graphs are turned on. And this memory trashing is very unliked because it forces GC to clean trash and drop fps especially on mobile. Mr. doob’s Stats have no this issue – memory keeps at one level. Please, let me know if I can help with hunting it down!

    • Mr doob’s stats, I think that’s net.hires.debug.Stats?
      The graph there is not self scaling, its smartly fixed via ‘Math.sqrt(Math.sqrt(mem * 5000))’ so you can’t really see the graph that is slowly going up.
      If you look at the ‘MEM’ number, you will notice it goes up slowly too, just not as fast as Console. That is because console updates every frame, hence making more garbage, where hires updates every second.


Leave a comment

No trackbacks yet.