{"id":576,"date":"2009-11-04T21:08:53","date_gmt":"2009-11-05T01:08:53","guid":{"rendered":"http:\/\/www.outofwhatbox.com\/blog\/?p=576"},"modified":"2009-11-07T10:49:09","modified_gmt":"2009-11-07T14:49:09","slug":"trimming-trim-via-razing-arrays-javascript","status":"publish","type":"post","link":"https:\/\/www.outofwhatbox.com\/blog\/2009\/11\/trimming-trim-via-razing-arrays-javascript\/","title":{"rendered":"Trimming trim via razing arrays (JavaScript)"},"content":{"rendered":"<p>Back in 2007, Steve Levithan <a href=\"http:\/\/blog.stevenlevithan.com\/archives\/faster-trim-javascript\">compared the speed<\/a> of different implementations for the missing JavaScript <code>String.trim()<\/code> function. Steve&#8217;s blog post has launched The Comment Thread That Will Not Die, as a number of folks have been tempted to try their hand at writing their own implementation.<\/p>\n<p>Count me in.<\/p>\n<p>It started when <a href=\"http:\/\/yesudeep.wordpress.com\/2009\/07\/31\/even-faster-string-prototype-trim-implementation-in-javascript\/\">Yesudeep Mangalapilly&#8217;s version<\/a> caught my attention. Yesudeep, working with an idea from <a href=\"http:\/\/blog.stevenlevithan.com\/archives\/faster-trim-javascript#comment-25052\">Michael Lee Finney<\/a>, had a fast implementation that didn&#8217;t use regular expressions. Instead of regexps, Yesudeep&#8217;s and Michael&#8217;s versions scan the string one character at a time, from the front and back ends, checking each character against a lookup table to determine if it&#8217;s whitespace.<\/p>\n<p>However: The largest Unicode code point that&#8217;s counted as whitespace is <a href=\"http:\/\/unicode.org\/charts\/PDF\/U3000.pdf\">U+3000 <em>(pdf)<\/em><\/a> (12288 in decimal), the <a href=\"http:\/\/en.wikipedia.org\/wiki\/Space_%28punctuation%29\">Ideographic Space<\/a> character. Hence, the lookup table array in Michael&#8217;s and Yasudeep&#8217;s implementations has a length of 12289, with most entries undefined. That&#8217;s a pretty large array, and a pretty sparse one.<\/p>\n<p>Even though these were already among the fastest of the <code>trim<\/code>s, I wondered if using a large array as a lookup table might carry any performance penalty. My concern stemmed from the fact that <a href=\"https:\/\/developer.mozilla.org\/en\/Core_JavaScript_1.5_Reference\/Objects\/Array#Increasing_the_array_length_indirectly\">JavaScript arrays grow dynamically<\/a>, adjusting in size to hold the highest index assigned into them. This poses a challenge to the interpreter: If it always places an array in a linear block of memory (as in C++), then accommodating array growth is likely to be a problem. So, to allow for reasonable performance at the array grows, the interpreter might not use a linear storage model for arrays. Non-linear models (trees or linked lists, for example) may make random access to the array slower, but would allow for reasonable performance when growing the array, while exhibiting reasonable memory consumption<sup><a name=\"trim-ref-mem\" href=\"#trim-note-mem\">1<\/a><\/sup>.<\/p>\n<p>Through testing in three popular browsers, I found reason to be concerned about large arrays. I profiled the <code>trim17<\/code> method (Yasudeep&#8217;s implementation), using input strings that contained only spaces and tabs for whitespace. After this profiling run, <a name=\"ReducedArraySize\">I trimmed <code>trim17<\/code>&#8216;s lookup table<\/a>\u00e2\u20ac\u201dhacked it, really\u00e2\u20ac\u201dby removing all entries above U+0020, and so limiting it to recognizing only ASCII whitespace chars. Then I profiled it again. <\/p>\n<p>The table below shows the milliseconds spent within the two versions of <code>trim17<\/code>; the difference between their runtimes is most likely due to the change in size of the lookup table array.<\/p>\n<table style=\"height: 77px;\" border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"517\">\n<tbody>\n<tr class=\"oowbfirstRow\">\n<th><\/th>\n<th style=\"text-align: right;\" ><strong>Original <code>trim17<\/code><\/strong><\/th>\n<th style=\"text-align: right;\" ><strong>Reduced <code>trim17<\/code><\/strong><\/th>\n<th style=\"text-align: right;\" ><strong>% Saved<\/strong><\/th>\n<\/tr>\n<tr>\n<td><strong>Internet Explorer 8<\/strong><\/td>\n<td>\n<p align=\"right\">27,328<\/p>\n<\/td>\n<td>\n<p align=\"right\">20,281<\/p>\n<\/td>\n<td>\n<p align=\"right\">26<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td><strong>Firefox 3.5<\/strong><\/td>\n<td>\n<p align=\"right\">3,689<\/p>\n<\/td>\n<td>\n<p align=\"right\">2,978<\/p>\n<\/td>\n<td>\n<p align=\"right\">20<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td><strong>Chrome 3.0<\/strong><\/td>\n<td>\n<p align=\"right\">610<\/p>\n<\/td>\n<td>\n<p align=\"right\">191<\/p>\n<\/td>\n<td>\n<p align=\"right\">69<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>These results confirm that, in JavaScript, accessing larger arrays can be slower than accessing smaller arrays.<\/p>\n<hr style=\"margin:1em 0\"\/>\n<p>But, perhaps applying these results to all arrays is an overgeneralization. Ideally, at least, it should be possible for an interpreter to recognize a &#8220;read-only&#8221; array, and use a more efficient layout for it. That is, if the interpreter can verify that an array isn&#8217;t modified after its initial construction, then perhaps it can safely flatten out the array into a linear block of memory.<\/p>\n<p>The array in <code>trim17<\/code> was constructed as a property of the <code>String<\/code> prototype. An array couldn&#8217;t be more modifiable than that, and so I wouldn&#8217;t expect it to be flattened by the interpreter. But suppose the array were accessible only from within a single function (a <a href=\"https:\/\/developer.mozilla.org\/en\/Core_JavaScript_1.5_Guide\/Working_with_Closures\">closure<\/a>). In that case, if that single function isn&#8217;t modifying the array, we know that nothing will. Depending on the JavaScript interpreter, that might allow for better performance.<\/p>\n<p>I changed the code accordingly, but the actual improvement in speed was&#8230; unremarkable. Nonexistent, even. It may have eked out around a 5% gain in some tests, but there&#8217;s enough noise in the measurements that it&#8217;s hard to be sure. Still, I dislike globals (and, especially, modifiable globals), so I decided to stick with the closure. (Besides, maybe someday, somewhere, an optimizing interpreter will know how to make use of it.)<\/p>\n<p>The next approach was to try using a smaller array. Or, rather, two smaller arrays: One to represent the whitespace characters at and below U+0020, and another to represent the whitespace characters between U+2000 and U+205f. To keep the second array small, its indices are offset by <code>\u00e2\u20ac\u201c0x2000<\/code>; this gives it a size of <code>0x0060<\/code> (96 decimal) entries. (There are three whitespace values that aren&#8217;t in either array: U+1680, U+180e, and U+3000. The new code checks for these explicitly.)<\/p>\n<p>Even with smaller arrays, I found that random access into them is still slower than making a few comparisons on scalar variables. Hence the code is written so that, for any character value, it consults no more than one of the two lookup tables, and then only if the character is in a reasonable range for that table.<\/p>\n<p>Here&#8217;s the new method:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\nvar trimOOWB = (function() {\r\n\r\n var whiteSpace = new Array(0x00a0 + 1);\r\n whiteSpace[0x0009] = true;    whiteSpace[0x000a] = true;\r\n whiteSpace[0x000b] = true;    whiteSpace[0x000c] = true;\r\n whiteSpace[0x000d] = true;    whiteSpace[0x0020] = true;\r\n whiteSpace[0x0085] = true;    whiteSpace[0x00a0] = true;\r\n\r\n var whiteSpace2 = new Array(0x005f + 1);\r\n var base = 0x2000;\r\n whiteSpace2[0x2000 - base] = true;  whiteSpace2[0x2001 - base] = true;\r\n whiteSpace2[0x2002 - base] = true;  whiteSpace2[0x2003 - base] = true;\r\n whiteSpace2[0x2004 - base] = true;  whiteSpace2[0x2005 - base] = true;\r\n whiteSpace2[0x2006 - base] = true;  whiteSpace2[0x2007 - base] = true;\r\n whiteSpace2[0x2008 - base] = true;  whiteSpace2[0x2009 - base] = true;\r\n whiteSpace2[0x200a - base] = true;  whiteSpace2[0x200b - base] = true;\r\n whiteSpace2[0x2028 - base] = true;  whiteSpace2[0x2029 - base] = true;\r\n whiteSpace2[0x202f - base] = true;  whiteSpace2[0x205f - base] = true;\r\n\r\n\r\n    function trimOOWB2(str) {\r\n        var ws = whiteSpace, ws2 = whiteSpace2;\r\n        var i=0, len=str.length, ch;\r\n        while ((ch = str.charCodeAt(--len)) &amp;amp;&amp;amp;\r\n               (ch &amp;lt;= 0x00A0 ? ws[ch] :\r\n                (ch &amp;gt;= 0x2000 ? (ch===0x3000 || ws2[ch - 0x2000])\r\n                 : (ch===0x1680 || ch===0x180e ))))\r\n            ;\r\n        \r\n        if (++len) {\r\n            while ((ch = str.charCodeAt(i)) &amp;amp;&amp;amp;\r\n                   (ch &amp;lt;= 0x00A0 ? ws[ch] :\r\n                    (ch &amp;gt;= 0x2000 ? (ch===0x3000 || ws2[ch - 0x2000])\r\n                     : (ch===0x1680 || ch===0x180e )))) {\r\n                ++i; \r\n            }\r\n        }\r\n        return str.substring(i, len);\r\n    }\r\n\r\n    return trimOOWB2;\r\n})();\r\n<\/pre>\n<p>I benchmarked the new function (<code>trimOOWB<\/code>) and Yesudeep&#8217;s <code>trim17<\/code> function, using two sets of input strings. In the first test, only legacy ASCII whitespace characters (e.g., spaces and tabs) were used, as in the test above. The second test data set used the full set of whitespace in the Unicode character set. The numbers shown are milliseconds spent within the trim functions; lower is better.<\/p>\n<table style=\"\" border=\"0\" cellspacing=\"0\" cellpadding=\"2\" >\n<tbody>\n<tr class=\"oowbfirstRow\">\n<th style=\"text-align: right;\"><\/th>\n<th style=\"text-align: right;\" ><strong><code>trim17 (ASCII)<\/code><\/strong><\/th>\n<th style=\"text-align: right;\" ><strong><code>trimOOWB (ASCII)<\/code><\/strong><\/th>\n<th style=\"text-align: right;\" ><strong><code>% saved<\/code><\/strong><\/th>\n<th style=\"text-align: right;\" ><strong><code>trim17 (Unicode)<\/code><\/strong><\/th>\n<th style=\"text-align: right;\" ><strong><code>trimOOWB (Unicode)<\/code><\/strong><\/th>\n<th style=\"text-align: right;\" ><strong><code>% saved<\/code><\/strong><\/th>\n<\/tr>\n<tr>\n<td><strong>Internet Explorer 8<\/strong><\/td>\n<td style=\"text-align: right;\">25,953<\/td>\n<td style=\"text-align: right;\">22,359<\/td>\n<td style=\"text-align: right;\">14<\/td>\n<td style=\"text-align: right;\" >27,469<\/td>\n<td style=\"text-align: right;\" >26,500<\/td>\n<td style=\"text-align: right;\" >4<\/td>\n<\/tr>\n<tr>\n<td><strong>Firefox 3.5<\/strong><\/td>\n<td style=\"text-align: right;\">3,706<\/td>\n<td style=\"text-align: right;\">3,089<\/td>\n<td style=\"text-align: right;\">17<\/td>\n<td style=\"text-align: right;\" >3,831<\/td>\n<td style=\"text-align: right;\" >3,439<\/td>\n<td style=\"text-align: right;\" >10<\/td>\n<\/tr>\n<tr>\n<td><strong>Google Chrome 3.0<\/strong><\/td>\n<td style=\"text-align: right;\">604<\/td>\n<td style=\"text-align: right;\">139<\/td>\n<td style=\"text-align: right;\">77<\/td>\n<td style=\"text-align: right;\" >664<\/td>\n<td style=\"text-align: right;\" >166<\/td>\n<td style=\"text-align: right;\" >75<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3>A final thought<\/h3>\n<p>It&#8217;s interesting that there&#8217;s a direct correlation between the base time for the browser to execute <code>trim17<\/code>, and the percentage of time saved by <code>trimOOWB<\/code>. In other words, the <em>percentage<\/em> of performance boost from using smaller arrays increases as the browser&#8217;s speed increases: IE showed the highest time and lowest gain, followed by Firefox on both counts, and finishing with Chrome, which had the lowest base time and the highest percentage gain.<\/p>\n<p>I&#8217;m guessing here, but I think the easiest way to explain this is that all three JavaScript interpreters are using roughly equivalent strategies for managing arrays. The percentages gained are different because the same <em>absolute<\/em> time savings in Chrome results in a higher <em>relative<\/em> performance boost when compared to IE or Firefox.<\/p>\n<p>That raises a question, though: Is it possible that the structure of <code>trimOOWB<\/code> gives it any performance advantage over <code>trim17<\/code> <em>aside from<\/em> the savings generated through reducing the array size? I&#8217;ve looked for such artifacts in the tests. In short, and skipping the details for now, I think it&#8217;s likely that such artifacts couldn&#8217;t account for more than one quarter of the overall speed boost; it&#8217;s probably much less than that. It&#8217;s at least as likely that the overhead added by <code>trimOOWB<\/code> is <em>obscuring<\/em> part of the overall performance boost.<\/p>\n<h3>Another final thought<\/h3>\n<p>The multiple comparisons made in <code>trimOOWB<\/code> might raise the question: Why not try using a <code>switch<\/code> statement instead of all those conditionals? Well, I <em>did<\/em> try this, with mixed results. On one hand, Firefox showed a significant speedup, around 25%. On the other hand, IE may have been a little slower, and Chrome was more than twice as slow. (Besides, it was a <code>switch<\/code> statement. We&#8217;re looking for speed, but a fellow&#8217;s got to have <em>some<\/em> standards.)<\/p>\n<h3>A final final thought<\/h3>\n<p>Using a closure offers another advantage: It&#8217;s simple to redirect calls to <code>trim<\/code> to the native String.trim() function, if the browser supports it. All that&#8217;s required is to change the <code>return<\/code> in the outer (anonymous) function from this:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    return trimOOWB2;\r\n<\/pre>\n<p>to:<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\r\n    return String.trim || trimOOWB2;\r\n<\/pre>\n<p><a href=\"http:\/\/ejohn.org\/blog\/ecmascript-5-strict-mode-json-and-more\/\">ECMAScript 5.0 is slated<\/a> to include <code>String.trim<\/code>, so it&#8217;s probably worth thinking ahead for this. In Firefox 3.5\u00e2\u20ac\u201dthe only current browser that I know of that supports <code>String.trim<\/code>\u00e2\u20ac\u201dcalls to the native <code>String.trim<\/code> run in a fraction of the time of any of the JavaScript implementations.<\/p>\n<div class=\"oowbbtw\"><strong>Note<\/strong>: Firefox&#8217;s native implementation of <code>String.trim<\/code> does not count U+1680 or U+180E as whitespace. It does treat U+3000 as whitespace.<\/div>\n<hr \/>\n<div><sup><a name=\"trim-note-mem\" href=\"#trim-ref-mem\">1<\/a><\/sup>Yes, I wrote &#8220;JavaScript&#8221; and &#8220;reasonable memory consumption&#8221; in the same article. Go ahead and snicker.<\/div>\n<p \/>\n","protected":false},"excerpt":{"rendered":"<p>It may well be that for better JavaScript performance, large arrays should be avoided where possible. That seems fairly clear in this case, where an already fast implementation of the missing String.trim() method was made even faster by using much smaller lookup table arrays.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[26,3,20],"tags":[23,29,28],"_links":{"self":[{"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/posts\/576"}],"collection":[{"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/comments?post=576"}],"version-history":[{"count":6,"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/posts\/576\/revisions"}],"predecessor-version":[{"id":582,"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/posts\/576\/revisions\/582"}],"wp:attachment":[{"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/media?parent=576"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/categories?post=576"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.outofwhatbox.com\/blog\/wp-json\/wp\/v2\/tags?post=576"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}