Skip to content

Commit 7244aff

Browse files
committed
[1.7>master] [MERGE #3751 @obastemur] perf: Improve JSON.stringify performance
Merge pull request #3751 from obastemur:fjs Kraken/json-stringify-* perf ~25% better. Acme Air - LTO gain ~1.5% PR Details: - Improve `replacer != function && !HasObjectArray` case. (most common use of JSON.stringify) - Add JSON.stringify test cases for ObjectArray, toJSON, and replacer function - IsNumericPropertyId: fast path for internal properties - use requestContext instead of instance->scriptContext - use wmemcpy instead of memcpy
2 parents 5c840dc + c6b3f20 commit 7244aff

File tree

12 files changed

+220
-84
lines changed

12 files changed

+220
-84
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ tags
9292
Makefile
9393
pal/src/config.h
9494
DbgController.js.h
95+
lib/wabt/built/config.h
9596

9697
# Generated by other tools
9798
*.lldb.cmd

lib/Common/Core/Api.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
44
//-------------------------------------------------------------------------------------------------------
55
#include "CommonCorePch.h"
6+
#ifdef _WIN32
7+
#include <wchar.h> // wmemcpy_s
8+
#endif
69

710
void __stdcall js_memcpy_s(__bcount(sizeInBytes) void *dst, size_t sizeInBytes, __in_bcount(count) const void *src, size_t count)
811
{
@@ -21,8 +24,7 @@ void __stdcall js_wmemcpy_s(__ecount(sizeInWords) char16 *dst, size_t sizeInWord
2124
{
2225
Js::Throw::FatalInternalError();
2326
}
24-
25-
memcpy(dst, src, count * sizeof(char16));
27+
wmemcpy_s(dst, count, src, count);
2628
}
2729

2830
#if defined(_M_IX86) || defined(_M_X64)

lib/Runtime/Base/ThreadContext.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,6 +1244,11 @@ void ThreadContext::AddBuiltInPropertyRecord(const Js::PropertyRecord *propertyR
12441244

12451245
BOOL ThreadContext::IsNumericPropertyId(Js::PropertyId propertyId, uint32* value)
12461246
{
1247+
if (Js::IsInternalPropertyId(propertyId))
1248+
{
1249+
return false;
1250+
}
1251+
12471252
Js::PropertyRecord const * propertyRecord = this->GetPropertyName(propertyId);
12481253
Assert(propertyRecord != nullptr);
12491254
if (propertyRecord == nullptr || !propertyRecord->IsNumeric())

lib/Runtime/Library/JSON.cpp

Lines changed: 117 additions & 52 deletions
Large diffs are not rendered by default.

lib/Runtime/Library/JSON.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ namespace JSON
6161
}
6262
void CompleteInit(Js::Var space, ArenaAllocator* alloc);
6363

64-
Js::Var Str(Js::JavascriptString* key, Js::PropertyId keyId, Js::Var holder);
64+
Js::Var Str(Js::JavascriptString* key, Js::PropertyId keyId, Js::Var holder, Js::Var value = nullptr);
6565
Js::Var Str(uint32 index, Js::Var holder);
6666

6767
private:
@@ -74,8 +74,10 @@ namespace JSON
7474
Js::JavascriptString* GetPropertySeparator();
7575
Js::JavascriptString* GetIndentString(uint count);
7676
Js::JavascriptString* GetMemberSeparator(Js::JavascriptString* indentString);
77-
void StringifyMemberObject( Js::JavascriptString* propertyName, Js::PropertyId id, Js::Var value, Js::ConcatStringBuilder* result,
78-
Js::JavascriptString* &indentString, Js::JavascriptString* &memberSeparator, bool &isFirstMember, bool &isEmpty );
77+
void StringifyMemberObject(Js::JavascriptString* propertyName, Js::PropertyId id,
78+
Js::Var value, Js::ConcatStringBuilder* result, Js::JavascriptString* &indentString,
79+
Js::JavascriptString* &memberSeparator, bool &isFirstMember, bool &isEmpty,
80+
Js::Var propertyValue = nullptr );
7981

8082
uint32 GetPropertyCount(Js::RecyclableObject* object, Js::JavascriptStaticEnumerator* enumerator);
8183
uint32 GetPropertyCount(Js::RecyclableObject* object, Js::JavascriptStaticEnumerator* enumerator, bool* isPrecise);

lib/Runtime/Types/DynamicObject.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ namespace Js
121121
// For boxing stack instance
122122
DynamicObject(DynamicObject * instance);
123123

124-
DynamicTypeHandler * GetTypeHandler() const;
125124
uint16 GetOffsetOfInlineSlots() const;
126125

127126
template <class T>
@@ -136,6 +135,8 @@ namespace Js
136135
void EnsureSlots(int oldCount, int newCount, ScriptContext * scriptContext, DynamicTypeHandler * newTypeHandler = nullptr);
137136
void EnsureSlots(int newCount, ScriptContext *scriptContext);
138137

138+
DynamicTypeHandler * GetTypeHandler() const;
139+
139140
Var GetSlot(int index);
140141
Var GetInlineSlot(int index);
141142
Var GetAuxSlot(int index);

lib/Runtime/Types/PathTypeHandler.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,7 @@ namespace Js
231231

232232
// Check numeric propertyId only if objectArray available
233233
uint32 indexVal;
234-
ScriptContext* scriptContext = instance->GetScriptContext();
235-
if (instance->HasObjectArray() && scriptContext->IsNumericPropertyId(propertyId, &indexVal))
234+
if (instance->HasObjectArray() && requestContext->IsNumericPropertyId(propertyId, &indexVal))
236235
{
237236
return PathTypeHandlerBase::GetItem(instance, originalInstance, indexVal, value, requestContext);
238237
}

test/JSON/replacerFunction.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
6+
7+
var TEST = function(a, b) {
8+
if (a != b) {
9+
throw new Error(a + " != " + b);
10+
}
11+
}
12+
13+
var obj = { str:6 };
14+
obj[0] = 'value0'
15+
obj[6] = 'value6';
16+
TEST(JSON.stringify(obj, function(k, v) {
17+
if (!k) return v;
18+
return v + 1
19+
}), '{"0":"value01","6":"value61","str":7}');
20+
21+
// test ObjectArray
22+
TEST(JSON.stringify({0:0, 1:1, "two":2}), '{"0":0,"1":1,"two":2}')
23+
24+
var a = new Object();
25+
26+
function replacer(k, v)
27+
{
28+
return v;
29+
}
30+
31+
var until = (!WScript.Platform || WScript.Platform.BUILD_TYPE == 'Debug') ? 12 : 1290;
32+
for (var i = 0; i < until; i++)
33+
{
34+
a[i + 10] = 0;
35+
}
36+
37+
TEST(JSON.stringify(a, replacer).substring(0,20), '{"10":0,"11":0,"12":');
38+
39+
console.log("PASS")

test/JSON/rlexe.xml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@
2424
</test>
2525
<test>
2626
<default>
27-
<files>stringify-replacerfunc.js</files>
28-
<baseline>stringify-replacerfunc.baseline</baseline>
29-
<compile-flags>-recyclerstress</compile-flags>
30-
<tags>exclude_fre,Slow</tags>
27+
<files>replacerFunction.js</files>
3128
<timeout>300</timeout>
3229
</default>
3330
</test>
@@ -95,4 +92,9 @@
9592
<baseline>syntaxError.baseline</baseline>
9693
</default>
9794
</test>
95+
<test>
96+
<default>
97+
<files>toJSON.js</files>
98+
</default>
99+
</test>
98100
</regress-exe>

test/JSON/stringify-replacerfunc.baseline

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)