8006408: Clean up and specialize NativeString
authorhannesw
Thu, 24 Jan 2013 14:55:57 +0100
changeset 16192 a2f89e8f0b87
parent 16191 7dd981da8e11
child 16193 34f41481a840
8006408: Clean up and specialize NativeString Reviewed-by: jlaskey, lagergren
nashorn/src/jdk/nashorn/internal/objects/NativeString.java
nashorn/test/examples/string-micro.js
--- a/nashorn/src/jdk/nashorn/internal/objects/NativeString.java	Thu Jan 24 17:49:03 2013 +0530
+++ b/nashorn/src/jdk/nashorn/internal/objects/NativeString.java	Thu Jan 24 14:55:57 2013 +0100
@@ -36,6 +36,7 @@
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.regex.Pattern;
 import jdk.nashorn.internal.objects.annotations.Attribute;
@@ -428,17 +429,30 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object charAt(final Object self, final Object pos) {
-        try {
-            return String.valueOf(((String)self).charAt(((Number)pos).intValue()));
-        } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
-            Global.checkObjectCoercible(self);
-            final String str = JSType.toString(self);
-            final int    at  = JSType.toInteger(pos);
-            if (at < 0 || at >= str.length()) {
-                return "";
-            }
-            return String.valueOf(str.charAt(at));
-        }
+        return charAt(self, JSType.toInteger(pos));
+    }
+
+    /**
+     * ECMA 15.5.4.4 String.prototype.charAt (pos) - specialized version for double position
+     * @param self self reference
+     * @param pos  position in string
+     * @return string representing the char at the given position
+     */
+    @SpecializedFunction
+    public static String charAt(final Object self, final double pos) {
+        return charAt(self, (int)pos);
+    }
+
+    /**
+     * ECMA 15.5.4.4 String.prototype.charAt (pos) - specialized version for int position
+     * @param self self reference
+     * @param pos  position in string
+     * @return string representing the char at the given position
+     */
+    @SpecializedFunction
+    public static String charAt(final Object self, final int pos) {
+        final String str = checkObjectToString(self);
+        return (pos < 0 || pos >= str.length()) ? "" : String.valueOf(str.charAt(pos));
     }
 
     /**
@@ -449,18 +463,30 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object charCodeAt(final Object self, final Object pos) {
-        try {
-            return (int)((String)self).charAt(((Number)pos).intValue());
-        } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
-            Global.checkObjectCoercible(self);
-            final String str = JSType.toString(self);
-            final int at     = JSType.toInteger(pos);
-            if (at < 0 || at >= str.length()) {
-                return Double.NaN;
-            }
+        return charCodeAt(self, JSType.toInteger(pos));
+    }
 
-            return JSType.toObject(str.charAt(at));
-        }
+    /**
+     * ECMA 15.5.4.5 String.prototype.charCodeAt (pos) - specialized version for double position
+     * @param self self reference
+     * @param pos  position in string
+     * @return number representing charcode at position
+     */
+    @SpecializedFunction
+    public static double charCodeAt(final Object self, final double pos) {
+        return charCodeAt(self, (int) pos);
+    }
+
+    /**
+     * ECMA 15.5.4.5 String.prototype.charCodeAt (pos) - specialized version for int position
+     * @param self self reference
+     * @param pos  position in string
+     * @return number representing charcode at position
+     */
+    @SpecializedFunction
+    public static double charCodeAt(final Object self, final int pos) {
+        final String str = checkObjectToString(self);
+        return (pos < 0 || pos >= str.length()) ? Double.NaN :  str.charAt(pos);
     }
 
     /**
@@ -471,14 +497,13 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
     public static Object concat(final Object self, final Object... args) {
-        Global.checkObjectCoercible(self);
-        final StringBuilder sb = new StringBuilder(JSType.toString(self));
+        CharSequence cs = checkObjectToString(self);
         if (args != null) {
             for (final Object obj : args) {
-                sb.append(JSType.toString(obj));
+                cs = new ConsString(cs, JSType.toCharSequence(obj));
             }
         }
-        return sb.toString();
+        return cs;
     }
 
     /**
@@ -490,12 +515,43 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
     public static Object indexOf(final Object self, final Object search, final Object pos) {
-        try {
-            return ((String)self).indexOf((String)search, ((Number)pos).intValue()); //assuming that the conversions really mean "toInteger" and not "toInt32" this is ok.
-        } catch (final ClassCastException | IndexOutOfBoundsException | NullPointerException e) {
-            Global.checkObjectCoercible(self);
-            return JSType.toString(self).indexOf(JSType.toString(search), JSType.toInteger(pos));
-        }
+        final String str = checkObjectToString(self);
+        return str.indexOf(JSType.toString(search), JSType.toInteger(pos));
+    }
+
+    /**
+     * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) specialized for no position parameter
+     * @param self   self reference
+     * @param search string to search for
+     * @return position of first match or -1
+     */
+    @SpecializedFunction
+    public static int indexOf(final Object self, final Object search) {
+        return indexOf(self, search, 0);
+    }
+
+    /**
+     * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) specialized for double position parameter
+     * @param self   self reference
+     * @param search string to search for
+     * @param pos    position to start search
+     * @return position of first match or -1
+     */
+    @SpecializedFunction
+    public static int indexOf(final Object self, final Object search, final double pos) {
+        return indexOf(self, search, (int) pos);
+    }
+
+    /**
+     * ECMA 15.5.4.7 String.prototype.indexOf (searchString, position) specialized for int position parameter
+     * @param self   self reference
+     * @param search string to search for
+     * @param pos    position to start search
+     * @return position of first match or -1
+     */
+    @SpecializedFunction
+    public static int indexOf(final Object self, final Object search, final int pos) {
+        return checkObjectToString(self).indexOf(JSType.toString(search), pos);
     }
 
     /**
@@ -507,9 +563,8 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE, arity = 1)
     public static Object lastIndexOf(final Object self, final Object search, final Object pos) {
-        Global.checkObjectCoercible(self);
 
-        final String str       = JSType.toString(self);
+        final String str       = checkObjectToString(self);
         final String searchStr = JSType.toString(search);
 
         int from;
@@ -532,9 +587,8 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object localeCompare(final Object self, final Object that) {
-        Global.checkObjectCoercible(self);
 
-        final String   str      = JSType.toString(self);
+        final String   str      = checkObjectToString(self);
         final Collator collator = Collator.getInstance(Global.getThisContext().getLocale());
 
         collator.setStrength(Collator.IDENTICAL);
@@ -551,9 +605,8 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object match(final Object self, final Object regexp) {
-        Global.checkObjectCoercible(self);
 
-        final String str = JSType.toString(self);
+        final String str = checkObjectToString(self);
 
         NativeRegExp nativeRegExp;
         if (regexp == UNDEFINED) {
@@ -599,9 +652,8 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object replace(final Object self, final Object string, final Object replacement) {
-        Global.checkObjectCoercible(self);
 
-        final String str = JSType.toString(self);
+        final String str = checkObjectToString(self);
 
         final NativeRegExp nativeRegExp;
         if (string instanceof NativeRegExp) {
@@ -626,9 +678,8 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object search(final Object self, final Object string) {
-        Global.checkObjectCoercible(self);
 
-        final String       str          = JSType.toString(self);
+        final String       str          = checkObjectToString(self);
         final NativeRegExp nativeRegExp = Global.toRegExp(string == UNDEFINED ? "" : string);
 
         return nativeRegExp.search(str);
@@ -644,30 +695,70 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object slice(final Object self, final Object start, final Object end) {
-        Global.checkObjectCoercible(self);
+
+        final String str      = checkObjectToString(self);
+        if (end == UNDEFINED) {
+            return slice(str, JSType.toInteger(start));
+        }
+        return slice(str, JSType.toInteger(start), JSType.toInteger(end));
+    }
 
-        final String str      = JSType.toString(self);
-        final int    len      = str.length();
-        final int    intStart = JSType.toInteger(start);
-        final int    intEnd   = (end == UNDEFINED) ? len : JSType.toInteger(end);
+    /**
+     * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for single int parameter
+     *
+     * @param self  self reference
+     * @param start start position for slice
+     * @return sliced out substring
+     */
+    @SpecializedFunction
+    public static Object slice(final Object self, final int start) {
+        final String str = checkObjectToString(self);
+        final int from = (start < 0) ? Math.max(str.length() + start, 0) : Math.min(start, str.length());
 
-        int from;
+        return str.substring(from);
+    }
 
-        if (intStart < 0) {
-            from = Math.max(len + intStart, 0);
-        } else {
-            from = Math.min(intStart, len);
-        }
-
-        int to;
+    /**
+     * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for single double parameter
+     *
+     * @param self  self reference
+     * @param start start position for slice
+     * @return sliced out substring
+     */
+    @SpecializedFunction
+    public static Object slice(final Object self, final double start) {
+        return slice(self, (int)start);
+    }
 
-        if (intEnd < 0) {
-            to = Math.max(len + intEnd,0);
-        } else {
-            to = Math.min(intEnd, len);
-        }
+    /**
+     * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for two int parameters
+     *
+     * @param self  self reference
+     * @param start start position for slice
+     * @return sliced out substring
+     */
+    @SpecializedFunction
+    public static Object slice(final Object self, final int start, final int end) {
+
+        final String str = checkObjectToString(self);
+        final int len    = str.length();
 
-        return str.substring(Math.min(from,  to), to);
+        final int from = (start < 0) ? Math.max(len + start, 0) : Math.min(start, len);
+        final int to   = (end < 0)   ? Math.max(len + end, 0)   : Math.min(end, len);
+
+        return str.substring(Math.min(from, to), to);
+    }
+
+    /**
+     * ECMA 15.5.4.13 String.prototype.slice (start, end) specialized for two double parameters
+     *
+     * @param self  self reference
+     * @param start start position for slice
+     * @return sliced out substring
+     */
+    @SpecializedFunction
+    public static Object slice(final Object self, final double start, final double end) {
+        return slice(self, (int)start, (int)end);
     }
 
     /**
@@ -680,9 +771,8 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object split(final Object self, final Object separator, final Object limit) {
-        Global.checkObjectCoercible(self);
 
-        final String str = JSType.toString(self);
+        final String str = checkObjectToString(self);
 
         if (separator == UNDEFINED) {
             return new NativeArray(new Object[]{str});
@@ -694,9 +784,40 @@
             return ((NativeRegExp) separator).split(str, lim);
         }
 
-        // when separator is a string, it has to be treated as a
-        // literal search string to be used for splitting.
-        return new NativeRegExp(Pattern.compile(JSType.toString(separator), Pattern.LITERAL)).split(str, lim);
+        // when separator is a string, it is treated as a literal search string to be used for splitting.
+        return splitString(str, JSType.toString(separator), lim);
+    }
+
+    private static Object splitString(String str, String separator, long limit) {
+
+        if (separator.equals("")) {
+            Object[] array = new Object[str.length()];
+            for (int i = 0; i < array.length; i++) {
+                array[i] = String.valueOf(str.charAt(i));
+            }
+            return new NativeArray(array);
+        }
+
+        final List<String> elements = new LinkedList<>();
+        final int strLength = str.length();
+        final int sepLength = separator.length();
+        int pos = 0;
+        int count = 0;
+
+        while (pos < strLength && count < limit) {
+            int found = str.indexOf(separator, pos);
+            if (found == -1) {
+                break;
+            }
+            elements.add(str.substring(pos, found));
+            count++;
+            pos = found + sepLength;
+        }
+        if (pos <= strLength && count < limit) {
+            elements.add(str.substring(pos));
+        }
+
+        return new NativeArray(elements.toArray());
     }
 
     /**
@@ -732,25 +853,78 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object substring(final Object self, final Object start, final Object end) {
-        Global.checkObjectCoercible(self);
+
+        final String str = checkObjectToString(self);
+        if (end == UNDEFINED) {
+            return substring(str, JSType.toInteger(start));
+        }
+        return substring(str, JSType.toInteger(start), JSType.toInteger(end));
+    }
 
-        final String str        = JSType.toString(self);
-        final int    len        = str.length();
-        final int    intStart   = JSType.toInteger(start);
-        final int    intEnd     = (end == UNDEFINED) ? len : JSType.toInteger(end);
-        final int    finalStart = Math.min((intStart < 0) ? 0 : intStart, len);
-        final int    finalEnd   = Math.min((intEnd < 0) ? 0 : intEnd, len);
+    /**
+     * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start parameter
+     *
+     * @param self  self reference
+     * @param start start position of substring
+     * @return substring given start and end indexes
+     */
+    @SpecializedFunction
+    public static String substring(final Object self, final int start) {
+        final String str = checkObjectToString(self);
+        if (start < 0) {
+            return str;
+        } else if (start >= str.length()) {
+            return "";
+        } else {
+            return str.substring(start);
+        }
+    }
 
-        int from, to;
+    /**
+     * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start parameter
+     *
+     * @param self  self reference
+     * @param start start position of substring
+     * @return substring given start and end indexes
+     */
+    @SpecializedFunction
+    public static String substring(final Object self, final double start) {
+        return substring(self, (int)start);
+    }
 
-        if (finalStart < finalEnd) {
-            from = finalStart;
-            to = finalEnd;
+    /**
+     * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for int start and end parameters
+     *
+     * @param self  self reference
+     * @param start start position of substring
+     * @param end   end position of substring
+     * @return substring given start and end indexes
+     */
+    @SpecializedFunction
+    public static String substring(final Object self, final int start, final int end) {
+        final String str = checkObjectToString(self);
+        final int len = str.length();
+        final int validStart = start < 0 ? 0 : (start > len ? len : start);
+        final int validEnd   = end < 0 ? 0 : (end > len ? len : end);
+
+        if (validStart < validEnd) {
+            return str.substring(validStart, validEnd);
         } else {
-            from = finalEnd;
-            to = finalStart;
+            return str.substring(validEnd, validStart);
         }
-        return str.substring(from, to);
+    }
+
+    /**
+     * ECMA 15.5.4.15 String.prototype.substring (start, end) specialized for double start and end parameters
+     *
+     * @param self  self reference
+     * @param start start position of substring
+     * @param end   end position of substring
+     * @return substring given start and end indexes
+     */
+    @SpecializedFunction
+    public static String substring(final Object self, final double start, final double end) {
+        return substring(self, (int)start, (int)end);
     }
 
     /**
@@ -760,8 +934,7 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object toLowerCase(final Object self) {
-        Global.checkObjectCoercible(self);
-        return JSType.toString(self).toLowerCase();
+        return checkObjectToString(self).toLowerCase();
     }
 
     /**
@@ -771,8 +944,7 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object toLocaleLowerCase(final Object self) {
-        Global.checkObjectCoercible(self);
-        return JSType.toString(self).toLowerCase(Global.getThisContext().getLocale());
+        return checkObjectToString(self).toLowerCase(Global.getThisContext().getLocale());
     }
 
     /**
@@ -782,8 +954,7 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object toUpperCase(final Object self) {
-        Global.checkObjectCoercible(self);
-        return JSType.toString(self).toUpperCase();
+        return checkObjectToString(self).toUpperCase();
     }
 
     /**
@@ -793,8 +964,7 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object toLocaleUpperCase(final Object self) {
-        Global.checkObjectCoercible(self);
-        return JSType.toString(self).toUpperCase(Global.getThisContext().getLocale());
+        return checkObjectToString(self).toUpperCase(Global.getThisContext().getLocale());
     }
 
     /**
@@ -804,12 +974,11 @@
      */
     @Function(attributes = Attribute.NOT_ENUMERABLE)
     public static Object trim(final Object self) {
-        Global.checkObjectCoercible(self);
 
-        final String str = JSType.toString(self);
-
+        final String str = checkObjectToString(self);
+        final int len = str.length();
         int start = 0;
-        int end   = str.length() - 1;
+        int end   = len - 1;
 
         while (start <= end && Lexer.isJSWhitespace(str.charAt(start))) {
             start++;
@@ -818,7 +987,7 @@
             end--;
         }
 
-        return str.substring(start, end + 1);
+        return start == 0 && end + 1 == len ? str : str.substring(start, end + 1);
     }
 
     private static Object newObj(final Object self, final CharSequence str) {
@@ -860,7 +1029,22 @@
         return newObj ? newObj(self, "") : "";
     }
 
-    //TODO - why is there no String with one String arg constructor?
+    /**
+     * ECMA 15.5.2.1 new String ( [ value ] ) - special version with one arg
+     *
+     * Constructor
+     *
+     * @param newObj is this constructor invoked with the new operator
+     * @param self   self reference
+     * @param arg    argument
+     *
+     * @return new NativeString (arg)
+     */
+    @SpecializedConstructor
+    public static Object constructor(final boolean newObj, final Object self, final Object arg) {
+        final CharSequence cs = JSType.toCharSequence(arg);
+        return newObj ? newObj(self, cs) : cs;
+    }
 
     /**
      * ECMA 15.5.2.1 new String ( [ value ] ) - special version with exactly one {@code int} arg
@@ -924,6 +1108,23 @@
         }
     }
 
+    /**
+     * Combines ECMA 9.10 CheckObjectCoercible and ECMA 9.8 ToString with a shortcut for strings.
+     *
+     * @param self the object
+     * @return the object as string
+     */
+    private static String checkObjectToString(final Object self) {
+        if (self instanceof String) {
+            return (String)self;
+        } else if (self instanceof ConsString) {
+            return self.toString();
+        } else {
+            Global.checkObjectCoercible(self);
+            return JSType.toString(self);
+        }
+    }
+
     private boolean isValid(final int key) {
         return key >= 0 && key < value.length();
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/nashorn/test/examples/string-micro.js	Thu Jan 24 14:55:57 2013 +0100
@@ -0,0 +1,179 @@
+/*
+ * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ *   - Redistributions of source code must retain the above copyright
+ *     notice, this list of conditions and the following disclaimer.
+ * 
+ *   - Redistributions in binary form must reproduce the above copyright
+ *     notice, this list of conditions and the following disclaimer in the
+ *     documentation and/or other materials provided with the distribution.
+ * 
+ *   - Neither the name of Oracle nor the names of its
+ *     contributors may be used to endorse or promote products derived
+ *     from this software without specific prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+var str = "The quick gray nashorn jumps over the lazy zebra.";
+
+function bench(name, func) {
+    var start = Date.now();
+    for (var iter = 0; iter < 5000000; iter++) {
+        func();
+    }
+    print((Date.now() - start) + "\t" + name);
+}
+
+bench("[]", function() {
+    str[0];
+    str[1];
+    str[2];
+});
+
+bench("fromCharCode", function() {
+    String.fromCharCode(97);
+    String.fromCharCode(98);
+    String.fromCharCode(99);
+});
+
+bench("charAt 1", function() {
+    str.charAt(0);
+    str.charAt(1);
+    str.charAt(2);
+});
+
+bench("charAt 2", function() {
+    str.charAt(100);
+    str.charAt(-1);
+});
+
+bench("charCodeAt 1", function() {
+    str.charCodeAt(0);
+    str.charCodeAt(1);
+    str.charCodeAt(2);
+});
+
+bench("charCodeAt 2", function() {
+    str.charCodeAt(100);
+    str.charCodeAt(-1);
+});
+
+bench("indexOf 1", function() {
+    str.indexOf("T");
+    str.indexOf("h");
+    str.indexOf("e");
+});
+
+bench("indexOf 2", function() {
+    str.indexOf("T", 0);
+    str.indexOf("h", 1);
+    str.indexOf("e", 2);
+});
+
+bench("lastIndexOf", function() {
+    str.indexOf("a");
+    str.indexOf("r");
+    str.indexOf("b");
+});
+
+bench("slice", function() {
+    str.slice(5);
+    str.slice(5);
+    str.slice(5);
+});
+
+bench("split 1", function() {
+    str.split();
+});
+
+bench("split 2", function() {
+    str.split("foo");
+});
+
+bench("split 3", function() {
+    str.split(/foo/);
+});
+
+bench("substring 1", function() {
+    str.substring(0);
+    str.substring(0);
+    str.substring(0);
+});
+
+bench("substring 2", function() {
+    str.substring(0, 5);
+    str.substring(0, 5);
+    str.substring(0, 5);
+});
+
+bench("substr", function() {
+    str.substr(0);
+    str.substr(0);
+    str.substr(0);
+});
+
+bench("slice", function() {
+    str.slice(0);
+    str.slice(0);
+    str.slice(0);
+});
+
+bench("concat", function() {
+    str.concat(str);
+    str.concat(str);
+    str.concat(str);
+});
+
+bench("trim", function() {
+    str.trim();
+    str.trim();
+    str.trim();
+});
+
+bench("toUpperCase", function() {
+    str.toUpperCase();
+});
+
+bench("toLowerCase", function() {
+    str.toLowerCase();
+});
+
+bench("valueOf", function() {
+    str.valueOf();
+    str.valueOf();
+    str.valueOf();
+});
+
+bench("toString", function() {
+    str.toString();
+    str.toString();
+    str.toString();
+});
+
+bench("String", function() {
+    String(str);
+    String(str);
+    String(str);
+});
+
+bench("new String", function() {
+    new String(str);
+    new String(str);
+    new String(str);
+});