sinclairzx81 / typebox

Json Schema Type Builder with Static Type Resolution for TypeScript
Other
4.95k stars 155 forks source link

typeboxCompiler error ENUM #788

Closed mathcovax closed 7 months ago

mathcovax commented 7 months ago

Hi, I am the owner of zod-accelerator. I am flattered that you fork my project to better configure my tests with your library. By testing your configuration typebox performance increases well! However, for the verification of objects and paintings, it seemed very suspicious.

Capture d’écran 2024-03-12 à 20 01 45

The library seems to have the same performance as checking the simple string type. By testing the results of what your function returns, it turns out that the function returns "false" all the time. While performing other tests, I noticed that this problem was caused by the check of "ENUM".

sinclairzx81 commented 7 months ago

@mathcovax Hi! :)

Yeah I thought zod-accelerator looked awesome! (it's a great idea, and I think it has a LOT of potential). Was actually seeing if I could help optimize the Object assertion logic specifically (as I thought I could get this running a bit faster), unfortunately I haven't been able to find a good strategy to provide you a PR under the current compiler infrastructure, but will provide a quick bit of info below. But first enums ...

TypeBox Enums

So, TypeBox takes the value of the enum variant (which is a number) by default.

enum typeboxGender {
  boy,
  girl
}
console.log(typeboxGender.boy)  // 0
console.log(typeboxGender.girl) // 1

You can fix the test by updating the enum to the following.

enum typeboxGender {
    boy = 'boy', // <--- add string values here.
    girl = 'girl'
}

console.log(typeboxGender.boy)  // 'boy'
console.log(typeboxGender.girl) // 'girl'

I pushed a quick update https://github.com/sinclairzx81/duplojs-zod-accelerator/commit/0005126a8cf2a74bf64a484be1d0d3c2d68533d6 if you want to replicate yourside. This should resolve the issue.

Performance Optimizations

So was looking to see if I could help out optimizing zod-accelerator a bit further. These were the results I was getting after updating to the TypeCompiler.

Result test Object :
┌─────────┬─────────────────────┬──────────────┬────────────────────┬──────────┬─────────┐
│ (index) │      Task Name      │   ops/sec    │ Average Time (ns)  │  Margin  │ Samples │
├─────────┼─────────────────────┼──────────────┼────────────────────┼──────────┼─────────┤
│    0    │        'zod'        │  '531,437'   │ 1881.6893054170996 │ '±1.66%' │  53144  │
│    1    │        'joi'        │  '181,192'   │ 5518.979117044024  │ '±1.84%' │  18120  │
│    2    │ '@sinclair/typebox' │ '14,948,446' │ 66.89658229228486  │ '±0.77%' │ 1494846 │
│    3    │       'myzod'       │ '1,229,305'  │ 813.4677104448951  │ '±1.27%' │ 122931  │
│    4    │  'zodAccelerator'   │ '7,666,299'  │ 130.4410265347366  │ '±2.02%' │ 766630  │ <-- could go faster
└─────────┴─────────────────────┴──────────────┴────────────────────┴──────────┴─────────┘

Was looking at this code specifically. Note that the delete $output["${key}"]; will cause V8 hidden class optimizations to break down. By commenting out the delete I get the following results.

Result test Object :
┌─────────┬─────────────────────┬──────────────┬───────────────────┬──────────┬─────────┐
│ (index) │      Task Name      │   ops/sec    │ Average Time (ns) │  Margin  │ Samples │
├─────────┼─────────────────────┼──────────────┼───────────────────┼──────────┼─────────┤
│    0    │        'zod'        │  '536,841'   │ 1862.74578043499  │ '±1.16%' │  53685  │
│    1    │        'joi'        │  '171,201'   │ 5841.055050623709 │ '±2.26%' │  17121  │
│    2    │ '@sinclair/typebox' │ '15,048,605' │ 66.45134021480938 │ '±0.82%' │ 1504862 │
│    3    │       'myzod'       │ '1,183,123'  │ 845.2199762513359 │ '±1.41%' │ 118313  │
│    4    │  'zodAccelerator'   │ '13,828,761' │ 72.31305435280898 │ '±1.57%' │ 1382877 │ <-- 2x increase!
└─────────┴─────────────────────┴──────────────┴───────────────────┴──────────┴─────────┘

In terms of trying to optimize, I was seeing if I could find a way to avoid writing to the $output[key] for cases where it was eventually deleting (meaning you could avoid calling delete entirely), but couldn't find a way with the current setup. Since zod-accelerator is writing from $input[key] to $output[key], if you could avoid writing to the $output for properties that were eventually deleted, you should be able to get the same value result + the 2x increase.

Again, amazing work on Zod-Accelerator! All the best! S

sinclairzx81 commented 7 months ago

@mathcovax Heya, might close off this issue as the update to the enum should resolve things.

Btw, If you would like me to submit a PR with the current updates, let me know. I was intending to spend a little bit more time with zod-accelerator trying to work in the optimizations mentioned above, but not sure when I'd next be able to find time to sit down with the project. If you can find a way to work these optimizations in though, it would be super worth it....getting a ultra fast zod check+parse compiler with these performance characteristics is a massive win...and something that has proven to be quite elusive in other validation libraries.

Again, great work! :) I look forward to seeing where you can take the project in future! All the best S

mathcovax commented 7 months ago

salut @sinclairzx81 , thank you for this lesson on ENUM !

Regarding optimization I am currently working on it. If you want to make a PR, I will be delighted with the review!

I admit that I am reaching my limit of understanding v8:/. The results are very random, I sometimes have the impression that it is the test library that has a problem x).

I found a solution that avoids writing in the case where the result is `undefined', but it is still a problem...

zod test schema:

const zodSchema = zod.object({
    firstname: zod.string().optional(), // slow
    lastname: zod.string(),
    age: zod.number(),
    email: zod.string(),
    gender: zod.enum(["boy", "girl"]),
    connected: zod.boolean(),
    createdAt: zod.date(),
    addresse: zod.object({
        postCode: zod.string(),
        city: zod.string(),
        number: zod.number()
    }),
});

generate code:

let duploj$177945150217291_input = duploj$177945149999291_input["firstname"];
if(duploj$177945150217291_input !== undefined){
    let duploj$177945150228291_input = duploj$177945150217291_input;

    if((typeof duploj$177945150228291_input !== "string")){
        new ZodAcceleratorError(`.firstname`, "");
    }

    duploj$177945150217291_input = duploj$177945150228291_input;
}
// = duploj$177945150217291_input;

if(duploj$177945150217291_input["firstname"] !== undefined){
    duploj$177945149999291_output["firstname"] = duploj$177945150217291_input["firstname"];
}

Result :

┌─────────┬─────────────────────┬──────────────┬───────────────────┬──────────┬─────────┐
│ (index) │ Task Name           │ ops/sec      │ Average Time (ns) │ Margin   │ Samples │
├─────────┼─────────────────────┼──────────────┼───────────────────┼──────────┼─────────┤
│ 0       │ '@sinclair/typebox' │ '31 945 548' │ 31.30326593116227 │ '±0.60%' │ 3194556 │
│ 1       │ 'zodAccelerator'    │ '17 231 656' │ 58.03272520893082 │ '±1.67%' │ 1723167 │
└─────────┴─────────────────────┴──────────────┴───────────────────┴──────────┴─────────┘

If I add other optional fields, the result does not change (which means that it is not the processing that is the problem, but just the written presence of this piece of code).

And if I ever withdraw from the championship, here are the results:

┌─────────┬─────────────────────┬──────────────┬────────────────────┬──────────┬─────────┐
│ (index) │ Task Name           │ ops/sec      │ Average Time (ns)  │ Margin   │ Samples │
├─────────┼─────────────────────┼──────────────┼────────────────────┼──────────┼─────────┤
│ 0       │ '@sinclair/typebox' │ '31 542 033' │ 31.703726835662202 │ '±1.65%' │ 3154204 │
│ 1       │ 'zodAccelerator'    │ '32 049 102' │ 31.202121993410532 │ '±0.76%' │ 3204911 │
└─────────┴─────────────────────┴──────────────┴────────────────────┴──────────┴─────────┘

There, I admit that I am a little lost ...

Thank you in advance for your help!