Changeset 285955 in webkit


Ignore:
Timestamp:
Nov 17, 2021 2:51:04 PM (8 months ago)
Author:
ysuzuki@apple.com
Message:

[JSC] Revise JSON.parse atomize policy
https://bugs.webkit.org/show_bug.cgi?id=233231

Reviewed by Mark Lam.

This patch improves JSON.parse performance by the following two changes.

  1. Introduce JSONAtomStringCache. It is inspired from HTMLAtomStringCache. It offers cheap fixed-sized cache stored in VM. Since it is in VM, we do not need to clear it every time we call JSON.parse. We clear this cache when full GC happens. It contributes to flight-todomvc-json-parse by 5%.
  2. Do not atomize long string. Profiling of JSON.parse said that most of time is used for atomizing of Strings. There is a tradeoff that, atomizing strings can reduce duplicate string allocations, but it has a performance penalty. V8 limits atomizing for <= 10 length strings, and SpiderMonkey does not atomize strings. In this patch, we aligned our atomizing policy to V8, so we do not atomize strings if the length is longer than 10. It contributes to flight-todomvc-json-parse by 50%.

Many microbenchmarks show the improvement.

ToT Patched

json-parse-object-reviver-same-value 78.2683+-0.9598 77.7784+-0.9488
vanilla-es2015-babel-webpack-todomvc-json-parse

99.9129+-0.5508 85.8160+-0.8721 definitely 1.1643x faster

json-parse-array-reviver-same-value 63.5891+-0.8066 63.2895+-0.7336
flight-todomvc-json-parse 52.4230+-0.4474 34.1159+-0.2378 definitely 1.5366x faster
json-parse-object-reviver 80.8417+-0.5042 80.6393+-0.8087
json-parse-leaf-object 51.6836+-0.6754 46.8983+-0.1578 definitely 1.1020x faster
vanilla-es2015-todomvc-json-parse 100.5916+-0.9399 85.9522+-0.8470 definitely 1.1703x faster
vanilla-todomvc-json-parse 76.4518+-0.4341 64.2318+-0.7621 definitely 1.1902x faster
json-parse-array-reviver 76.1276+-0.8529 75.9747+-0.9002

And Speedometer2 shows 0.8% improvement.

----------------------------------------------------------------------------------------------------------------------------------
| subtest | ms | ms | b / a | pValue (significance using False Discovery Rate) |
----------------------------------------------------------------------------------------------------------------------------------
| Elm-TodoMVC |109.046667 |108.546667 |0.995415 | 0.197186 |
| VueJS-TodoMVC |21.813333 |21.566667 |0.988692 | 0.313141 |
| EmberJS-TodoMVC |117.796667 |118.086667 |1.002462 | 0.558244 |
| Flight-TodoMVC |64.273333 |62.260000 |0.968675 | 0.000000 (significant) |
| BackboneJS-TodoMVC |42.856667 |42.863333 |1.000156 | 0.975025 |
| Preact-TodoMVC |16.326667 |16.673333 |1.021233 | 0.298674 |
| AngularJS-TodoMVC |123.146667 |122.413333 |0.994045 | 0.160282 |
| Inferno-TodoMVC |57.510000 |57.533333 |1.000406 | 0.947767 |
| Vanilla-ES2015-TodoMVC |61.133333 |59.200000 |0.968375 | 0.000000 (significant) |
| Angular2-TypeScript-TodoMVC |38.863333 |38.963333 |1.002573 | 0.860359 |
| VanillaJS-TodoMVC |51.296667 |49.423333 |0.963480 | 0.000000 (significant) |
| jQuery-TodoMVC |210.933333 |210.596667 |0.998404 | 0.590132 |
| EmberJS-Debug-TodoMVC |326.093333 |324.890000 |0.996310 | 0.156955 |
| React-TodoMVC |81.113333 |81.360000 |1.003041 | 0.335615 |
| React-Redux-TodoMVC |132.560000 |132.256667 |0.997712 | 0.306072 |
| Vanilla-ES2015-Babel-Webpack-TodoMVC |60.073333 |59.026667 |0.982577 | 0.000883 (significant) |
----------------------------------------------------------------------------------------------------------------------------------
a mean = 280.29390
b mean = 282.51413
pValue = 0.0000083325
(Bigger means are better.)
1.008 times better
Results ARE significant

  • CMakeLists.txt:
  • JavaScriptCore.xcodeproj/project.pbxproj:
  • heap/Heap.cpp:

(JSC::Heap::finalize):

  • runtime/JSONAtomStringCache.h: Added.

(JSC::JSONAtomStringCache::makeIdentifier):
(JSC::JSONAtomStringCache::clear):
(JSC::JSONAtomStringCache::cacheSlot):
(JSC::JSONAtomStringCache::cache):

  • runtime/JSONAtomStringCacheInlines.h: Added.

(JSC::JSONAtomStringCache::make):
(JSC::JSONAtomStringCache::vm const):

  • runtime/LiteralParser.cpp:

(JSC::LiteralParser<CharType>::makeIdentifier):
(JSC::LiteralParser<CharType>::makeJSString):
(JSC::LiteralParser<CharType>::parsePrimitiveValue):
(JSC::LiteralParser<CharType>::parse):

  • runtime/LiteralParser.h:
  • runtime/VM.h:
Location:
trunk/Source/JavaScriptCore
Files:
2 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/CMakeLists.txt

    r285795 r285955  
    10861086    runtime/JSModuleRecord.h
    10871087    runtime/JSNativeStdFunction.h
     1088    runtime/JSONAtomStringCache.h
    10881089    runtime/JSONObject.h
    10891090    runtime/JSObject.h
  • trunk/Source/JavaScriptCore/ChangeLog

    r285850 r285955  
     12021-11-17  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        [JSC] Revise JSON.parse atomize policy
     4        https://bugs.webkit.org/show_bug.cgi?id=233231
     5
     6        Reviewed by Mark Lam.
     7
     8        This patch improves JSON.parse performance by the following two changes.
     9
     10        1. Introduce JSONAtomStringCache. It is inspired from HTMLAtomStringCache. It offers cheap
     11           fixed-sized cache stored in VM. Since it is in VM, we do not need to clear it every time
     12           we call JSON.parse. We clear this cache when full GC happens. It contributes to
     13           flight-todomvc-json-parse by 5%.
     14        2. Do not atomize long string. Profiling of JSON.parse said that most of time is used for
     15           atomizing of Strings. There is a tradeoff that, atomizing strings can reduce duplicate string
     16           allocations, but it has a performance penalty. V8 limits atomizing for <= 10 length strings,
     17           and SpiderMonkey does not atomize strings. In this patch, we aligned our atomizing policy to
     18           V8, so we do not atomize strings if the length is longer than 10. It contributes to
     19           flight-todomvc-json-parse by 50%.
     20
     21        Many microbenchmarks show the improvement.
     22                                                                 ToT                     Patched
     23
     24            json-parse-object-reviver-same-value           78.2683+-0.9598           77.7784+-0.9488
     25            vanilla-es2015-babel-webpack-todomvc-json-parse
     26                                                           99.9129+-0.5508     ^     85.8160+-0.8721        ^ definitely 1.1643x faster
     27            json-parse-array-reviver-same-value            63.5891+-0.8066           63.2895+-0.7336
     28            flight-todomvc-json-parse                      52.4230+-0.4474     ^     34.1159+-0.2378        ^ definitely 1.5366x faster
     29            json-parse-object-reviver                      80.8417+-0.5042           80.6393+-0.8087
     30            json-parse-leaf-object                         51.6836+-0.6754     ^     46.8983+-0.1578        ^ definitely 1.1020x faster
     31            vanilla-es2015-todomvc-json-parse             100.5916+-0.9399     ^     85.9522+-0.8470        ^ definitely 1.1703x faster
     32            vanilla-todomvc-json-parse                     76.4518+-0.4341     ^     64.2318+-0.7621        ^ definitely 1.1902x faster
     33            json-parse-array-reviver                       76.1276+-0.8529           75.9747+-0.9002
     34
     35        And Speedometer2 shows 0.8% improvement.
     36
     37            ----------------------------------------------------------------------------------------------------------------------------------
     38            |               subtest                |     ms      |     ms      |  b / a   | pValue (significance using False Discovery Rate) |
     39            ----------------------------------------------------------------------------------------------------------------------------------
     40            | Elm-TodoMVC                          |109.046667   |108.546667   |0.995415  | 0.197186                                         |
     41            | VueJS-TodoMVC                        |21.813333    |21.566667    |0.988692  | 0.313141                                         |
     42            | EmberJS-TodoMVC                      |117.796667   |118.086667   |1.002462  | 0.558244                                         |
     43            | Flight-TodoMVC                       |64.273333    |62.260000    |0.968675  | 0.000000 (significant)                           |
     44            | BackboneJS-TodoMVC                   |42.856667    |42.863333    |1.000156  | 0.975025                                         |
     45            | Preact-TodoMVC                       |16.326667    |16.673333    |1.021233  | 0.298674                                         |
     46            | AngularJS-TodoMVC                    |123.146667   |122.413333   |0.994045  | 0.160282                                         |
     47            | Inferno-TodoMVC                      |57.510000    |57.533333    |1.000406  | 0.947767                                         |
     48            | Vanilla-ES2015-TodoMVC               |61.133333    |59.200000    |0.968375  | 0.000000 (significant)                           |
     49            | Angular2-TypeScript-TodoMVC          |38.863333    |38.963333    |1.002573  | 0.860359                                         |
     50            | VanillaJS-TodoMVC                    |51.296667    |49.423333    |0.963480  | 0.000000 (significant)                           |
     51            | jQuery-TodoMVC                       |210.933333   |210.596667   |0.998404  | 0.590132                                         |
     52            | EmberJS-Debug-TodoMVC                |326.093333   |324.890000   |0.996310  | 0.156955                                         |
     53            | React-TodoMVC                        |81.113333    |81.360000    |1.003041  | 0.335615                                         |
     54            | React-Redux-TodoMVC                  |132.560000   |132.256667   |0.997712  | 0.306072                                         |
     55            | Vanilla-ES2015-Babel-Webpack-TodoMVC |60.073333    |59.026667    |0.982577  | 0.000883 (significant)                           |
     56            ----------------------------------------------------------------------------------------------------------------------------------
     57            a mean = 280.29390
     58            b mean = 282.51413
     59            pValue = 0.0000083325
     60            (Bigger means are better.)
     61            1.008 times better
     62            Results ARE significant
     63
     64        * CMakeLists.txt:
     65        * JavaScriptCore.xcodeproj/project.pbxproj:
     66        * heap/Heap.cpp:
     67        (JSC::Heap::finalize):
     68        * runtime/JSONAtomStringCache.h: Added.
     69        (JSC::JSONAtomStringCache::makeIdentifier):
     70        (JSC::JSONAtomStringCache::clear):
     71        (JSC::JSONAtomStringCache::cacheSlot):
     72        (JSC::JSONAtomStringCache::cache):
     73        * runtime/JSONAtomStringCacheInlines.h: Added.
     74        (JSC::JSONAtomStringCache::make):
     75        (JSC::JSONAtomStringCache::vm const):
     76        * runtime/LiteralParser.cpp:
     77        (JSC::LiteralParser<CharType>::makeIdentifier):
     78        (JSC::LiteralParser<CharType>::makeJSString):
     79        (JSC::LiteralParser<CharType>::parsePrimitiveValue):
     80        (JSC::LiteralParser<CharType>::parse):
     81        * runtime/LiteralParser.h:
     82        * runtime/VM.h:
     83
    1842021-11-15  Yusuke Suzuki  <ysuzuki@apple.com>
    285
  • trunk/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj

    r285795 r285955  
    18761876                E32D4DE926DAFD4300D4533A /* TemporalCalendar.h in Headers */ = {isa = PBXBuildFile; fileRef = E32D4DE326DAFD4300D4533A /* TemporalCalendar.h */; };
    18771877                E32D4DEA26DAFD4300D4533A /* TemporalCalendarConstructor.h in Headers */ = {isa = PBXBuildFile; fileRef = E32D4DE426DAFD4300D4533A /* TemporalCalendarConstructor.h */; };
     1878                E32FEA2C27448F3700FF41C1 /* JSONAtomStringCacheInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = E32FEA2A27448F3600FF41C1 /* JSONAtomStringCacheInlines.h */; };
     1879                E32FEA2D27448F3700FF41C1 /* JSONAtomStringCache.h in Headers */ = {isa = PBXBuildFile; fileRef = E32FEA2B27448F3600FF41C1 /* JSONAtomStringCache.h */; settings = {ATTRIBUTES = (Private, ); }; };
    18781880                E33095DD23210A1B00EB7856 /* JSInternalFieldObjectImpl.h in Headers */ = {isa = PBXBuildFile; fileRef = E33095DC23210A1400EB7856 /* JSInternalFieldObjectImpl.h */; settings = {ATTRIBUTES = (Private, ); }; };
    18791881                E334CBB521FD96A9000EB178 /* RegExpGlobalData.h in Headers */ = {isa = PBXBuildFile; fileRef = E334CBB321FD96A9000EB178 /* RegExpGlobalData.h */; settings = {ATTRIBUTES = (Private, ); }; };
     
    51775179                E32D4DE426DAFD4300D4533A /* TemporalCalendarConstructor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TemporalCalendarConstructor.h; sourceTree = "<group>"; };
    51785180                E32D4DE526DAFD4300D4533A /* TemporalCalendarConstructor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TemporalCalendarConstructor.cpp; sourceTree = "<group>"; };
     5181                E32FEA2A27448F3600FF41C1 /* JSONAtomStringCacheInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONAtomStringCacheInlines.h; sourceTree = "<group>"; };
     5182                E32FEA2B27448F3600FF41C1 /* JSONAtomStringCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSONAtomStringCache.h; sourceTree = "<group>"; };
    51795183                E3305FB020B0F78700CEB82B /* InByVariant.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InByVariant.cpp; sourceTree = "<group>"; };
    51805184                E3305FB120B0F78800CEB82B /* InByVariant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InByVariant.h; sourceTree = "<group>"; };
     
    78107814                                BC22A3990E16E14800AF21C8 /* JSObject.h */,
    78117815                                0F93275E1C21EF7F00CF6564 /* JSObjectInlines.h */,
     7816                                E32FEA2B27448F3600FF41C1 /* JSONAtomStringCache.h */,
     7817                                E32FEA2A27448F3600FF41C1 /* JSONAtomStringCacheInlines.h */,
    78127818                                A7F9935E0FD7325100A0B2D0 /* JSONObject.cpp */,
    78137819                                A7F9935D0FD7325100A0B2D0 /* JSONObject.h */,
     
    1043110437                                BC18C4250E16F5CD00B34460 /* JSObjectRef.h in Headers */,
    1043210438                                A7280A2811557E3000D56957 /* JSObjectRefPrivate.h in Headers */,
     10439                                E32FEA2D27448F3700FF41C1 /* JSONAtomStringCache.h in Headers */,
     10440                                E32FEA2C27448F3700FF41C1 /* JSONAtomStringCacheInlines.h in Headers */,
    1043310441                                A7F9935F0FD7325100A0B2D0 /* JSONObject.h in Headers */,
    1043410442                                BC87CDB910712AD4000614CF /* JSONObject.lut.h in Headers */,
  • trunk/Source/JavaScriptCore/heap/Heap.cpp

    r285653 r285955  
    20492049        cache->clear();
    20502050
     2051    if (m_lastCollectionScope && m_lastCollectionScope.value() == CollectionScope::Full)
     2052        vm().jsonAtomStringCache.clear();
     2053
    20512054    immutableButterflyToStringCache.clear();
    20522055   
  • trunk/Source/JavaScriptCore/runtime/LiteralParser.cpp

    r282468 r285955  
    3131#include "JSArray.h"
    3232#include "JSCInlines.h"
     33#include "JSONAtomStringCacheInlines.h"
    3334#include "Lexer.h"
    3435#include "ObjectConstructor.h"
     
    144145
    145146template <typename CharType>
    146 ALWAYS_INLINE Identifier LiteralParser<CharType>::makeIdentifier(typename Lexer::LiteralParserTokenPtr token)
     147ALWAYS_INLINE Identifier LiteralParser<CharType>::makeIdentifier(VM& vm, typename Lexer::LiteralParserTokenPtr token)
    147148{
    148149    if (token->stringIs8Bit)
    149         return makeIdentifier(token->stringToken8, token->stringLength);
    150     return makeIdentifier(token->stringToken16, token->stringLength);
    151 }
    152 
    153    
     150        return Identifier::fromString(vm, vm.jsonAtomStringCache.makeIdentifier(token->stringToken8, token->stringLength));
     151    return Identifier::fromString(vm, vm.jsonAtomStringCache.makeIdentifier(token->stringToken16, token->stringLength));
     152}
     153
    154154template <typename CharType>
    155 template <typename LiteralCharType>
    156 ALWAYS_INLINE Identifier LiteralParser<CharType>::makeIdentifier(const LiteralCharType* characters, size_t length)
    157 {
    158     VM& vm = m_globalObject->vm();
    159     if (!length)
    160         return vm.propertyNames->emptyIdentifier;
    161 
    162     auto firstCharacter = characters[0];
    163     if (length == 1) {
    164         if constexpr (sizeof(LiteralCharType) == 1)
    165             return Identifier::fromString(vm, vm.smallStrings.singleCharacterStringRep(firstCharacter));
    166         if (firstCharacter <= maxSingleCharacterString)
    167             return Identifier::fromString(vm, vm.smallStrings.singleCharacterStringRep(firstCharacter));
    168         return Identifier::fromString(vm, characters, length);
    169     }
    170 
    171     if (firstCharacter >= maximumCachableCharacter)
    172         return Identifier::fromString(vm, characters, length);
    173 
    174     // 0 means no entry since m_recentIdentifiersIndex is zero-filled initially.
    175     uint8_t indexPlusOne = m_recentIdentifiersIndex[firstCharacter];
    176     if (indexPlusOne) {
    177         uint8_t index = indexPlusOne - 1;
    178         auto& ident = m_recentIdentifiers[index];
    179         if (Identifier::equal(ident.impl(), characters, length))
    180             return ident;
    181         auto result = Identifier::fromString(vm, characters, length);
    182         m_recentIdentifiers[index] = result;
    183         return result;
    184     }
    185 
    186     auto result = Identifier::fromString(vm, characters, length);
    187     m_recentIdentifiers.uncheckedAppend(result);
    188     indexPlusOne = m_recentIdentifiers.size();
    189     m_recentIdentifiersIndex[firstCharacter] = indexPlusOne;
    190     return result;
     155ALWAYS_INLINE JSString* LiteralParser<CharType>::makeJSString(VM& vm, typename Lexer::LiteralParserTokenPtr token)
     156{
     157    constexpr unsigned maxAtomizeStringLength = 10;
     158    if (token->stringIs8Bit) {
     159        if (token->stringLength > maxAtomizeStringLength)
     160            return jsString(vm, String(token->stringToken8, token->stringLength));
     161        return jsString(vm, Identifier::fromString(vm, token->stringToken8, token->stringLength).string());
     162    }
     163    if (token->stringLength > maxAtomizeStringLength)
     164        return jsString(vm, String(token->stringToken16, token->stringLength));
     165    return jsString(vm, Identifier::fromString(vm, token->stringToken16, token->stringLength).string());
    191166}
    192167
     
    11451120    switch (m_lexer.currentToken()->type) {
    11461121    case TokString: {
    1147         JSValue result = jsString(vm, makeIdentifier(m_lexer.currentToken()).string());
     1122        JSString* result = makeJSString(vm, m_lexer.currentToken());
    11481123        m_lexer.next();
    11491124        return result;
     
    12851260            if (type == TokString || (m_mode != StrictJSON && type == TokIdentifier)) {
    12861261                while (true) {
    1287                     Identifier ident = makeIdentifier(m_lexer.currentToken());
     1262                    Identifier ident = makeIdentifier(vm, m_lexer.currentToken());
    12881263
    12891264                    if (UNLIKELY(m_lexer.next() != TokColon)) {
     
    13591334                return { };
    13601335            }
    1361             identifierStack.append(makeIdentifier(m_lexer.currentToken()));
     1336            identifierStack.append(makeIdentifier(vm, m_lexer.currentToken()));
    13621337
    13631338            // Check for colon
  • trunk/Source/JavaScriptCore/runtime/LiteralParser.h

    r282468 r285955  
    198198    JSValue parsePrimitiveValue(VM&);
    199199
    200     ALWAYS_INLINE Identifier makeIdentifier(typename Lexer::LiteralParserTokenPtr);
    201     template<typename LiteralCharType>
    202     ALWAYS_INLINE Identifier makeIdentifier(const LiteralCharType* characters, size_t length);
     200    ALWAYS_INLINE Identifier makeIdentifier(VM&, typename Lexer::LiteralParserTokenPtr);
     201    ALWAYS_INLINE JSString* makeJSString(VM&, typename Lexer::LiteralParserTokenPtr);
    203202
    204203    void setErrorMessageForToken(TokenType);
     
    209208    ParserMode m_mode;
    210209    String m_parseErrorMessage;
    211     static constexpr unsigned maximumCachableCharacter = 128;
    212     std::array<uint8_t, maximumCachableCharacter> m_recentIdentifiersIndex { };
    213     Vector<Identifier, maximumCachableCharacter> m_recentIdentifiers;
    214210};
    215211
  • trunk/Source/JavaScriptCore/runtime/VM.h

    r285795 r285955  
    4848#include "JSDateMath.h"
    4949#include "JSLock.h"
     50#include "JSONAtomStringCache.h"
    5051#include "MacroAssemblerCodeRef.h"
    5152#include "Microtask.h"
     
    806807    WeakGCMap<StringImpl*, JSString, PtrHash<StringImpl*>> stringCache;
    807808    Strong<JSString> lastCachedString;
     809    JSONAtomStringCache jsonAtomStringCache;
    808810
    809811    AtomStringTable* atomStringTable() const { return m_atomStringTable; }
Note: See TracChangeset for help on using the changeset viewer.