Old methods die hard

 

A little over a year ago, I wrote a workaround for displaying multiple spaces in HTML TextField instances. The trick is to add   to each space. In non-Flash HTML, this would create two spaces for each space, but since the Flash TextField class is unfortunate in so many ways, it ignores the entity, but not completely. Instead of inserting a space,   creates an invisible divider, allowing multiple consecutive spaces in an HTML TextField instance.

In that same article, I shared my HTMLFormat.space(n) method that returns n spaces using a for loop. I’ve been using this method for the past few years, not thinking twice about it. Then, last week, I was showing it to a co-worker, saying it was faster than the String.replace() method. I have no idea why I said that, considering I never tested it—I just assumed. Of course, I was embarrassed when it turned out to be five times slower.

I headed to my Test app to find the best approach. Here’s the code I used:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package app.test {
	import flash.display.Sprite;
	import flash.utils.getTimer;
 
	import format.HTMLFormat;
 
	public class Test extends Sprite {
		public function Test () {
			__init ();
		}
 
		private function __init ():void {
			var $a:uint, $A:uint;
			var $before:int, $after:int;
			var $string:String;
 
			$A = 10000;
 
			$string = "Hey there  , what is    going on    ?";
 
			$before = getTimer ();
			for ($a = 0; $a < $A; $a++) {
			 	$string.replace (" ", "&nbsp; ");
			}
			$after = getTimer () - $before
			trace ("String.replace (\" \", \"&nbsp; \");", $after);
 
			$before = getTimer ();
			for ($a = 0; $a < $A; $a++) {
			 	$string.replace (/[ ]/ig, "&nbsp; ");
			}
			$after = getTimer () - $before
			trace ("String.replace (/[ ]/ig, \"&nbsp; \");", $after);
 
			$before = getTimer ();
			for ($a = 0; $a < $A; $a++) {
				"Hey there" + HTMLFormat.space (2) + ", what is" + HTMLFormat.space (4) + "going on" + HTMLFormat.space (4) + "?";
			}
			$after = getTimer () - $before
			trace ("HTMLFormat.space (n);", $after);
 
			$before = getTimer ();
			for ($a = 0; $a < $A; $a++) {
				String ("Hey there" + HTMLFormat.space (2) + ", what is" + HTMLFormat.space (4) + "going on" + HTMLFormat.space (4) + "?")
			}
			$after = getTimer () - $before
			trace ("String (HTMLFormat.space (n));", $after);
 
			$before = getTimer ();
			for ($a = 0; $a < $A; $a++) {
				("Hey there" + HTMLFormat.space (2) + ", what is" + HTMLFormat.space (4) + "going on" + HTMLFormat.space (4) + "?") as String
			}
			$after = getTimer () - $before
			trace ("HTMLFormat.space (n) as String;", $after);
 
			$before = getTimer ();
			for ($a = 0; $a < $A; $a++) {
				"Hey there" + "&nbsp; &nbsp; " + ", what is" + "&nbsp; &nbsp; &nbsp; &nbsp; " + "going on" + "&nbsp; &nbsp; &nbsp; &nbsp; " + "?";
			}
			$after = getTimer () - $before
			trace ("\"&nbsp; \"", $after);
 
			$before = getTimer ();
			for ($a = 0; $a < $A; $a++) {
				String ("Hey there" + "&nbsp; &nbsp; " + ", what is" + "&nbsp; &nbsp; &nbsp; &nbsp; " + "going on" + "&nbsp; &nbsp; &nbsp; &nbsp; " + "?");
			}
			$after = getTimer () - $before
			trace ("String (\"&nbsp; \");", $after);
 
			$before = getTimer ();
			for ($a = 0; $a < $A; $a++) {
				("Hey there" + "&nbsp; &nbsp; " + ", what is" + "&nbsp; &nbsp; &nbsp; &nbsp; " + "going on" + "&nbsp; &nbsp; &nbsp; &nbsp; " + "?") as String;
			}
			$after = getTimer () - $before
			trace ("(\"&nbsp; \") as String;", $after);
 
			$before = getTimer ();
			for ($a = 0; $a < $A; $a++) {
				$string.split (" ").join ("&nbsp; ");
			}
			$after = getTimer () - $before
			trace ("String.split (\" \").join (\"&nbsp; \");", $after);
		}
	}
}

And, the startling results:

String.replace (" ", "&nbsp; "); // 15
String.replace (/[ ]/ig, "&nbsp; "); // 142
HTMLFormat.space (n); // 72
String (HTMLFormat.space (n)); // 73
HTMLFormat.space (n) as String; // 75
"&nbsp; " // 15
String ("&nbsp; "); // 15
("&nbsp; ") as String; // 19
String.split (" ").join ("&nbsp; "); // 178

It looks like your best bet is to use the String.replace () method with a String pattern. Though typing out “&nbsp; “ is just as fast, it’s an inconvenience for the developer to type out and dirties up the code. As you might have guessed, I will retire the HTMLFormat.space (n) method and spend a solid hour refactoring some code.

2 replies

  1. First of all, the sigils are freaking me out. :)

    Secondly, I ran your test and got:

    String.replace (" ", "&nbsp; "); // 4
    String.replace (/[ ]/ig, "&nbsp; "); // 85
    HTMLFormat.space (n); // 26
    String (HTMLFormat.space (n)); // 27
    HTMLFormat.space (n) as String; // 27
    "&nbsp; " // 5
    String ("&nbsp; "); // 7
    ("&nbsp; ") as String; // 7
    String.split (" ").join ("&nbsp; "); // 86

    With an HTMLFormat.space() of my own recreation:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    public static function space(n:int): String
    {
    	var str:String = "";
    	for (var i:int = 0; i < n; ++i)
    	{
    		str += "&nbsp;";
    	}
    	return str;
    }

    So I’m seeing similar results. But I have two issues:

    1) The HTMLFormat.space() tests are on building a new string every time but the String.replace() is working on the same string each iteration of the loop. Further, the String.replace() will do its job on the first iteration, but then the next 9999 iterations require no actual replaces.

    2) The String.replace() method is only for when you know the number of spaces you want at compile time, not at runtime as with the variable n. This is an extra feature of HTMLFormat.space() that makes the comparison less than equal.

    Lastly, I made a few tests of my own:

    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
    26
    27
    28
    29
    30
    31
    32
    33
    34
    
    // A non-static version
    public function space(n:int): String
    {
    	var str:String = "";
    	for (var i:int = 0; i < n; ++i)
    	{
    		str += "&nbsp; ";
    	}
    	return str;
    }
     
    // The worst of both worlds
    private function space2(n:int): String
    {
    	// Make a string with n spaces
    	var str:String = "";
    	for (var i:int = 0; i < n; ++i)
    	{
    		str += " ";
    	}
    	return str.replace(" ", "&nbsp; ");
    }
     
    // An attempt to put more work on the player
    private function space3(n:int): String
    {
    	return new Array(n+1).join("&nbsp; ");
    }
     
    // Vector version of space3
    private function space4(n:int): String
    {
    	return new Vector.(n+1, true).join("&nbsp; ");
    }

    But none of them are even as fast as my above implementation of HTMLFormat.space():

    Non-static space (n); // 27
    space2 (n); // 36
    space3 (n); // 66
    space4 (n); // 127

    Thanks for the article!

    PS: Do you allow code markup for syntax highlighting in your posts?

  2. @Jackson — Since I’m not setting $string to the replaced value, String.replace actually does occur for each iteration. After writing the post, I did realize how it’s more convenient to have an n number of spaces. In the end, I don’t see myself running something like this 10,000+ times. For kicks, I added one more player the game, knowing, however, that it would fail miserably: StringUtil.substitute (str, …rest);.

    The code:

    1
    2
    3
    4
    5
    6
    7
    8
    
    $string = "Hey there{2}, what is{4}going on{4}?";
     
    $before = getTimer ();
    for ($a = 0; $a < $A; $a++) {
    	StringUtil.substitute ($string, " &nbsp;", " &nbsp; &nbsp;", " &nbsp; &nbsp; &nbsp; &nbsp;", " &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;");
    }
    $after = getTimer () - $before
    trace ("StringUtil.substitute", $after);

    and the result:

    StringUtil.substitute (str, ...rest); // 325

    If you wrap your comment code with <pre lang="actionscript3"> and </pre>, it should format it correctly. At the moment, however, it will be bold because of a lingering CSS style. Since I’m 40,000 feet up, using in-flight wifi, FTP isn’t cooperating for me to fix it. Give me a few hours. :)

Reply