Bug 1275282 - Don't allocate duplicate empty or small strings. r=luke
authorJan de Mooij <jdemooij@mozilla.com>
Thu, 26 May 2016 10:21:55 +0200
changeset 338150 1f5902db90a7f9be77ee5938861b2067874fcdc7
parent 338149 df9fd4cff088217776253331d10e31de653cdbb4
child 338151 354813df8073e8afa0bbff61eb71606dc96290d7
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1275282
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1275282 - Don't allocate duplicate empty or small strings. r=luke
js/src/jsapi.cpp
js/src/vm/String.cpp
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -5050,27 +5050,25 @@ JS::AutoSetAsyncStackForNewCalls::~AutoS
 }
 
 /************************************************************************/
 JS_PUBLIC_API(JSString*)
 JS_NewStringCopyN(JSContext* cx, const char* s, size_t n)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    if (!n)
-        return cx->names().empty;
     return NewStringCopyN<CanGC>(cx, s, n);
 }
 
 JS_PUBLIC_API(JSString*)
 JS_NewStringCopyZ(JSContext* cx, const char* s)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
-    if (!s || !*s)
+    if (!s)
         return cx->runtime()->emptyString;
     return NewStringCopyZ<CanGC>(cx, s);
 }
 
 JS_PUBLIC_API(bool)
 JS_StringHasBeenPinned(JSContext* cx, JSString* str)
 {
     AssertHeapIsIdle(cx);
--- a/js/src/vm/String.cpp
+++ b/js/src/vm/String.cpp
@@ -779,27 +779,29 @@ bool
 StaticStrings::init(JSContext* cx)
 {
     AutoLockForExclusiveAccess lock(cx);
     AutoCompartment ac(cx, cx->runtime()->atomsCompartment(lock));
 
     static_assert(UNIT_STATIC_LIMIT - 1 <= JSString::MAX_LATIN1_CHAR,
                   "Unit strings must fit in Latin1Char.");
 
+    using Latin1Range = mozilla::Range<const Latin1Char>;
+
     for (uint32_t i = 0; i < UNIT_STATIC_LIMIT; i++) {
         Latin1Char buffer[] = { Latin1Char(i), '\0' };
-        JSFlatString* s = NewStringCopyN<NoGC>(cx, buffer, 1);
+        JSFlatString* s = NewInlineString<NoGC>(cx, Latin1Range(buffer, 1));
         if (!s)
             return false;
         unitStaticTable[i] = s->morphAtomizedStringIntoPermanentAtom();
     }
 
     for (uint32_t i = 0; i < NUM_SMALL_CHARS * NUM_SMALL_CHARS; i++) {
         Latin1Char buffer[] = { FROM_SMALL_CHAR(i >> 6), FROM_SMALL_CHAR(i & 0x3F), '\0' };
-        JSFlatString* s = NewStringCopyN<NoGC>(cx, buffer, 2);
+        JSFlatString* s = NewInlineString<NoGC>(cx, Latin1Range(buffer, 2));
         if (!s)
             return false;
         length2StaticTable[i] = s->morphAtomizedStringIntoPermanentAtom();
     }
 
     for (uint32_t i = 0; i < INT_STATIC_LIMIT; i++) {
         if (i < 10) {
             intStaticTable[i] = unitStaticTable[i + '0'];
@@ -807,17 +809,17 @@ StaticStrings::init(JSContext* cx)
             size_t index = ((size_t)TO_SMALL_CHAR((i / 10) + '0') << 6) +
                 TO_SMALL_CHAR((i % 10) + '0');
             intStaticTable[i] = length2StaticTable[index];
         } else {
             Latin1Char buffer[] = { Latin1Char('0' + (i / 100)),
                                     Latin1Char('0' + ((i / 10) % 10)),
                                     Latin1Char('0' + (i % 10)),
                                     '\0' };
-            JSFlatString* s = NewStringCopyN<NoGC>(cx, buffer, 3);
+            JSFlatString* s = NewInlineString<NoGC>(cx, Latin1Range(buffer, 3));
             if (!s)
                 return false;
             intStaticTable[i] = s->morphAtomizedStringIntoPermanentAtom();
         }
     }
 
     return true;
 }
@@ -1085,20 +1087,41 @@ NewInlineStringDeflated(ExclusiveContext
     for (size_t i = 0; i < len; i++) {
         MOZ_ASSERT(chars[i] <= JSString::MAX_LATIN1_CHAR);
         storage[i] = Latin1Char(chars[i]);
     }
     storage[len] = '\0';
     return str;
 }
 
+template <typename CharT>
+static MOZ_ALWAYS_INLINE JSFlatString*
+TryEmptyOrStaticString(ExclusiveContext* cx, const CharT* chars, size_t n)
+{
+    // Measurements on popular websites indicate empty strings are pretty common
+    // and most strings with length 1 or 2 are in the StaticStrings table. For
+    // length 3 strings that's only about 1%, so we check n <= 2.
+    if (n <= 2) {
+        if (n == 0)
+            return cx->emptyString();
+
+        if (JSFlatString* str = cx->staticStrings().lookup(chars, n))
+            return str;
+    }
+
+    return nullptr;
+}
+
 template <AllowGC allowGC>
 static JSFlatString*
 NewStringDeflated(ExclusiveContext* cx, const char16_t* s, size_t n)
 {
+    if (JSFlatString* str = TryEmptyOrStaticString(cx, s, n))
+        return str;
+
     if (JSInlineString::lengthFits<Latin1Char>(n))
         return NewInlineStringDeflated<allowGC>(cx, mozilla::Range<const char16_t>(s, n));
 
     ScopedJSFreePtr<Latin1Char> news(cx->pod_malloc<Latin1Char>(n + 1));
     if (!news)
         return nullptr;
 
     for (size_t i = 0; i < n; i++) {
@@ -1121,24 +1144,21 @@ NewStringDeflated(ExclusiveContext* cx, 
 {
     MOZ_CRASH("Shouldn't be called for Latin1 chars");
 }
 
 template <AllowGC allowGC, typename CharT>
 JSFlatString*
 js::NewStringDontDeflate(ExclusiveContext* cx, CharT* chars, size_t length)
 {
-    if (length == 1) {
-        char16_t c = chars[0];
-        if (StaticStrings::hasUnit(c)) {
-            // Free |chars| because we're taking possession of it, but it's no
-            // longer needed because we use the static string instead.
-            js_free(chars);
-            return cx->staticStrings().getUnit(c);
-        }
+    if (JSFlatString* str = TryEmptyOrStaticString(cx, chars, length)) {
+        // Free |chars| because we're taking possession of it, but it's no
+        // longer needed because we use the static string instead.
+        js_free(chars);
+        return str;
     }
 
     if (JSInlineString::lengthFits<CharT>(length)) {
         JSInlineString* str =
             NewInlineString<allowGC>(cx, mozilla::Range<const CharT>(chars, length));
         if (!str)
             return nullptr;
 
@@ -1161,24 +1181,16 @@ js::NewStringDontDeflate<CanGC>(Exclusiv
 template JSFlatString*
 js::NewStringDontDeflate<NoGC>(ExclusiveContext* cx, Latin1Char* chars, size_t length);
 
 template <AllowGC allowGC, typename CharT>
 JSFlatString*
 js::NewString(ExclusiveContext* cx, CharT* chars, size_t length)
 {
     if (IsSame<CharT, char16_t>::value && CanStoreCharsAsLatin1(chars, length)) {
-        if (length == 1) {
-            char16_t c = chars[0];
-            if (StaticStrings::hasUnit(c)) {
-                js_free(chars);
-                return cx->staticStrings().getUnit(c);
-            }
-        }
-
         JSFlatString* s = NewStringDeflated<allowGC>(cx, chars, length);
         if (!s)
             return nullptr;
 
         // Free |chars| because we're taking possession of it but not using it.
         js_free(chars);
         return s;
     }
@@ -1199,16 +1211,19 @@ template JSFlatString*
 js::NewString<NoGC>(ExclusiveContext* cx, Latin1Char* chars, size_t length);
 
 namespace js {
 
 template <AllowGC allowGC, typename CharT>
 JSFlatString*
 NewStringCopyNDontDeflate(ExclusiveContext* cx, const CharT* s, size_t n)
 {
+    if (JSFlatString* str = TryEmptyOrStaticString(cx, s, n))
+        return str;
+
     if (JSInlineString::lengthFits<CharT>(n))
         return NewInlineString<allowGC>(cx, mozilla::Range<const CharT>(s, n));
 
     ScopedJSFreePtr<CharT> news(cx->pod_malloc<CharT>(n + 1));
     if (!news) {
         if (!allowGC)
             cx->recoverFromOutOfMemory();
         return nullptr;