Skip to content

Commit

Permalink
Fix S.union performance (#75)
Browse files Browse the repository at this point in the history
* Reproduce the problem

* Improve parsing of unions with different types
  • Loading branch information
DZakh authored Jul 12, 2024
1 parent 6f80856 commit 6f4f4ca
Show file tree
Hide file tree
Showing 9 changed files with 782 additions and 134 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG_NEXT.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@
- Release 7.0.2 with type check fix for recursive schema
- Test GenType compatibility with d.ts
- Clean up error tags
- Set 8.0.x for rescript-schema in ppx deps
- Update github ci to run on "v*.*.\*-patch"
97 changes: 90 additions & 7 deletions packages/tests/src/benchmark/Benchmark.bs.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Generated by ReScript, PLEASE EDIT WITH CARE

import * as Benchmark from "benchmark";
import * as Core__Array from "@rescript/core/src/Core__Array.bs.mjs";
import * as S$RescriptSchema from "rescript-schema/src/S.bs.mjs";

function addWithPrepare(suite, name, fn) {
Expand Down Expand Up @@ -74,47 +75,129 @@ S$RescriptSchema.setGlobalConfig({
disableNanNumberCheck: true
});

var schema = S$RescriptSchema.recursive(function (schema) {
return S$RescriptSchema.union([
S$RescriptSchema.object(function (s) {
s.tag("type", "A");
return {
TAG: "A",
_0: s.f("nested", S$RescriptSchema.array(schema))
};
}),
S$RescriptSchema.literal("B"),
S$RescriptSchema.literal("C"),
S$RescriptSchema.literal("D"),
S$RescriptSchema.literal("E"),
S$RescriptSchema.literal("F"),
S$RescriptSchema.literal("G"),
S$RescriptSchema.literal("H"),
S$RescriptSchema.literal("I"),
S$RescriptSchema.literal("J"),
S$RescriptSchema.literal("K"),
S$RescriptSchema.literal("L"),
S$RescriptSchema.literal("M"),
S$RescriptSchema.literal("N"),
S$RescriptSchema.literal("O"),
S$RescriptSchema.literal("P"),
S$RescriptSchema.literal("Q"),
S$RescriptSchema.literal("R"),
S$RescriptSchema.literal("S"),
S$RescriptSchema.literal("T"),
S$RescriptSchema.literal("U"),
S$RescriptSchema.literal("V"),
S$RescriptSchema.literal("W"),
S$RescriptSchema.literal("X"),
S$RescriptSchema.literal("Y"),
S$RescriptSchema.object(function (s) {
s.tag("type", "Z");
return {
TAG: "Z",
_0: s.f("nested", S$RescriptSchema.array(schema))
};
})
]);
});

var testData1 = {
TAG: "Z",
_0: Core__Array.make(25, {
TAG: "Z",
_0: Core__Array.make(25, {
TAG: "Z",
_0: Core__Array.make(25, "Y")
})
})
};

var testData2 = {
TAG: "A",
_0: Core__Array.make(25, {
TAG: "A",
_0: Core__Array.make(25, {
TAG: "A",
_0: Core__Array.make(25, "B")
})
})
};

function test() {
console.time("testData1 serialize");
var json = S$RescriptSchema.serializeOrRaiseWith(testData1, schema);
console.timeEnd("testData1 serialize");
console.time("testData1 parse");
S$RescriptSchema.parseOrRaiseWith(json, schema);
console.timeEnd("testData1 parse");
console.time("testData2 serialize");
var json$1 = S$RescriptSchema.serializeOrRaiseWith(testData2, schema);
console.timeEnd("testData2 serialize");
console.time("testData2 parse");
S$RescriptSchema.parseOrRaiseWith(json$1, schema);
console.timeEnd("testData2 parse");
}

test();

var data = makeTestObject();

console.time("makeAdvancedObjectSchema");

var schema = makeAdvancedObjectSchema();
var schema$1 = makeAdvancedObjectSchema();

console.timeEnd("makeAdvancedObjectSchema");

console.time("parseAnyWith: 1");

S$RescriptSchema.parseAnyWith(data, schema);
S$RescriptSchema.parseAnyWith(data, schema$1);

console.timeEnd("parseAnyWith: 1");

console.time("parseAnyWith: 2");

S$RescriptSchema.parseAnyWith(data, schema);
S$RescriptSchema.parseAnyWith(data, schema$1);

console.timeEnd("parseAnyWith: 2");

console.time("parseAnyWith: 3");

S$RescriptSchema.parseAnyWith(data, schema);
S$RescriptSchema.parseAnyWith(data, schema$1);

console.timeEnd("parseAnyWith: 3");

console.time("serializeWith: 1");

S$RescriptSchema.serializeWith(data, schema);
S$RescriptSchema.serializeWith(data, schema$1);

console.timeEnd("serializeWith: 1");

console.time("serializeWith: 2");

S$RescriptSchema.serializeWith(data, schema);
S$RescriptSchema.serializeWith(data, schema$1);

console.timeEnd("serializeWith: 2");

console.time("serializeWith: 3");

S$RescriptSchema.serializeWith(data, schema);
S$RescriptSchema.serializeWith(data, schema$1);

console.timeEnd("serializeWith: 3");

Expand Down
110 changes: 110 additions & 0 deletions packages/tests/src/benchmark/Benchmark.res
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,116 @@ S.setGlobalConfig({
disableNanNumberCheck: true,
})

// Reported in https://gist.github.com/cknitt/4ac6813a3f3bc907187105e01a4324ca
module CrazyUnion = {
type rec test =
| A(array<test>)
| B
| C
| D
| E
| F
| G
| H
| I
| J
| K
| L
| M
| N
| O
| P
| Q
| R
| S
| T
| U
| V
| W
| X
| Y
| Z(array<test>)

let schema = S.recursive(schema =>
S.union([
S.object(s => {
s.tag("type", "A")
A(s.field("nested", S.array(schema)))
}),
S.literal(B),
S.literal(C),
S.literal(D),
S.literal(E),
S.literal(F),
S.literal(G),
S.literal(H),
S.literal(I),
S.literal(J),
S.literal(K),
S.literal(L),
S.literal(M),
S.literal(N),
S.literal(O),
S.literal(P),
S.literal(Q),
S.literal(R),
S.literal(S),
S.literal(T),
S.literal(U),
S.literal(V),
S.literal(W),
S.literal(X),
S.literal(Y),
S.object(s => {
s.tag("type", "Z")
Z(s.field("nested", S.array(schema)))
}),
])
)

let testData1 = Z(Array.make(~length=25, Z(Array.make(~length=25, Z(Array.make(~length=25, Y))))))

let testData2 = A(Array.make(~length=25, A(Array.make(~length=25, A(Array.make(~length=25, B))))))

let test = () => {
Console.time("testData1 serialize")
let json = S.serializeOrRaiseWith(testData1, schema)
Console.timeEnd("testData1 serialize")

Console.time("testData1 parse")
let _ = S.parseOrRaiseWith(json, schema)
Console.timeEnd("testData1 parse")

Console.time("testData2 serialize")
let json = S.serializeOrRaiseWith(testData2, schema)
Console.timeEnd("testData2 serialize")

Console.time("testData2 parse")
let _ = S.parseOrRaiseWith(json, schema)
Console.timeEnd("testData2 parse")

// Js.log((schema->Obj.magic)["parseOrThrow"]["toString"]())
}
}

// Full
// testData1 serialize: 5.414s
// testData1 parse: 5.519s
// testData2 serialize: 70.864ms
// testData2 parse: 70.967ms

// Wip
// testData1 serialize: 5.398s
// testData1 parse: 6.171ms
// testData2 serialize: 69.621ms
// testData2 parse: 0.878ms

// Partial
// testData1 serialize: 1.802ms
// testData1 parse: 1.411ms
// 734 Error.make
CrazyUnion.test()

let data = makeTestObject()
Console.time("makeAdvancedObjectSchema")
let schema = makeAdvancedObjectSchema()
Expand Down
2 changes: 1 addition & 1 deletion packages/tests/src/core/Example_test.res
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ test("Compiled parse code snapshot", t => {
t->U.assertCompiledCode(
~schema=filmSchema,
~op=#Parse,
`i=>{if(!i||i.constructor!==Object){e[11](i)}let v0=i["Id"],v1=i["Title"],v2=i["Tags"],v6=i["Rating"],v7,v8=i["Age"];if(typeof v0!=="number"||Number.isNaN(v0)){e[0](v0)}if(typeof v1!=="string"){e[1](v1)}if(v2!==void 0&&(!Array.isArray(v2))){e[2](v2)}if(v2!==void 0){for(let v3=0;v3<v2.length;++v3){let v5=v2[v3];try{if(typeof v5!=="string"){e[3](v5)}}catch(v4){if(v4&&v4.s===s){v4.path="[\\"Tags\\"]"+\'["\'+v3+\'"]\'+v4.path}throw v4}}}try{v6==="G"||e[5](v6);v7=v6}catch(e0){try{v6==="PG"||e[6](v6);v7=v6}catch(e1){try{v6==="PG13"||e[7](v6);v7=v6}catch(e2){try{v6==="R"||e[8](v6);v7=v6}catch(e3){e[9]([e0,e1,e2,e3,])}}}}if(v8!==void 0&&(typeof v8!=="number"||v8>2147483647||v8<-2147483648||v8%1!==0)){e[10](v8)}return {"id":v0,"title":v1,"tags":v2===void 0?e[4]:v2,"rating":v7,"deprecatedAgeRestriction":v8,}}`,
`i=>{if(!i||i.constructor!==Object){e[11](i)}let v0=i["Id"],v1=i["Title"],v2=i["Tags"],v6=i["Rating"],v7,v8=i["Age"];if(typeof v0!=="number"||Number.isNaN(v0)){e[0](v0)}if(typeof v1!=="string"){e[1](v1)}if(v2!==void 0&&(!Array.isArray(v2))){e[2](v2)}if(v2!==void 0){for(let v3=0;v3<v2.length;++v3){let v5=v2[v3];try{if(typeof v5!=="string"){e[3](v5)}}catch(v4){if(v4&&v4.s===s){v4.path="[\\"Tags\\"]"+\'["\'+v3+\'"]\'+v4.path}throw v4}}}if(!(v6==="G")){if(!(v6==="PG")){if(!(v6==="PG13")){if(!(v6==="R")){e[5](v6)}else{v7=v6}}else{v7=v6}}else{v7=v6}}else{v7=v6}if(v8!==void 0&&(typeof v8!=="number"||v8>2147483647||v8<-2147483648||v8%1!==0)){e[10](v8)}return {"id":v0,"title":v1,"tags":v2===void 0?e[4]:v2,"rating":v7,"deprecatedAgeRestriction":v8,}}`,
)
})

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Generated by ReScript, PLEASE EDIT WITH CARE
/* This output is empty. Its source's type definitions, externals and/or unused code got optimized away. */
Loading

2 comments on commit 6f4f4ca

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark

Benchmark suite Current: 6f4f4ca Previous: 06df40b Ratio
Parse string 818105965 ops/sec (±0.11%) 818372259 ops/sec (±0.13%) 1.00
Serialize string 817114429 ops/sec (±0.10%) 819110535 ops/sec (±0.05%) 1.00
Advanced object schema factory 467750 ops/sec (±0.20%) 449156 ops/sec (±0.56%) 0.96
Parse advanced object 56548716 ops/sec (±0.68%) 45100200 ops/sec (±0.16%) 0.80
Assert advanced object 173340104 ops/sec (±0.12%)
Create and parse advanced object 92120 ops/sec (±0.82%) 35041 ops/sec (±1.18%) 0.38
Parse advanced strict object 25331923 ops/sec (±0.27%) 22748126 ops/sec (±0.24%) 0.90
Assert advanced strict object 30493043 ops/sec (±1.27%)
Serialize advanced object 52269079 ops/sec (±1.31%) 806595235 ops/sec (±0.11%) 15.43

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.50.

Benchmark suite Current: 6f4f4ca Previous: 06df40b Ratio
Serialize advanced object 52269079 ops/sec (±1.31%) 806595235 ops/sec (±0.11%) 15.43

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.