Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(experimental): use DrainOuterGeneric helper on codec type mappings #2322

Merged
merged 1 commit into from
Mar 20, 2024

Conversation

lorisleiva
Copy link
Collaborator

@lorisleiva lorisleiva commented Mar 15, 2024

This PR aims to solve #2295 by wrapping codec type mappings with the suggested DrainOuterGeneric.

The TypeScript diagnostics on a complex codec — using both data enums and structs — changes as follows. As you can see we get a significant reduction of instantiations.

Files:                         204 -> 205
Lines of Library:            38131 -> 38131
Lines of Definitions:        52284 -> 52300
Lines of TypeScript:            81 -> 81
Lines of JavaScript:             0 -> 0
Lines of JSON:                   0 -> 0
Lines of Other:                  0 -> 0
Identifiers:                 92492 -> 92518
Symbols:                    111465 -> 111877
Types:                       55305 -> 55390
Instantiations:             603991 -> 362113    // -40%
Memory used:               187111K -> 189767K
Assignability cache size:    21397 -> 21404
Identity cache size:           350 -> 350
Subtype cache size:            161 -> 161
Strict subtype cache size:     358 -> 354
I/O Read time:               0.01s -> 0.01s
Parse time:                  0.18s -> 0.18s
ResolveModule time:          0.02s -> 0.02s
ResolveTypeReference time:   0.00s -> 0.00s
ResolveLibrary time:         0.01s -> 0.01s
Program time:                0.23s -> 0.24s
Bind time:                   0.07s -> 0.07s
Check time:                  0.78s -> 0.72s
transformTime time:          0.00s -> 0.00s
commentTime time:            0.00s -> 0.00s
I/O Write time:              0.00s -> 0.00s
printTime time:              0.01s -> 0.01s
Emit time:                   0.01s -> 0.01s
Total time:                  1.09s -> 1.04s

Copy link

changeset-bot bot commented Mar 15, 2024

🦋 Changeset detected

Latest commit: d309da8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 35 packages
Name Type
@solana/codecs-data-structures Patch
@solana/codecs Patch
@solana/transactions Patch
@solana/web3.js-experimental Patch
@solana/sysvars Patch
@solana/compat Patch
@solana/web3.js-legacy-sham Patch
@solana/programs Patch
@solana/rpc-api Patch
@solana/rpc-graphql Patch
@solana/rpc-subscriptions-api Patch
@solana/signers Patch
@solana/transaction-confirmation Patch
@solana/rpc Patch
@solana/rpc-subscriptions Patch
@solana/accounts Patch
@solana/addresses Patch
@solana/assertions Patch
@solana/codecs-core Patch
@solana/codecs-numbers Patch
@solana/codecs-strings Patch
@solana/errors Patch
@solana/functional Patch
@solana/instructions Patch
@solana/keys Patch
@solana/options Patch
@solana/rpc-parsed-types Patch
@solana/rpc-spec-types Patch
@solana/rpc-spec Patch
@solana/rpc-subscriptions-spec Patch
@solana/rpc-subscriptions-transport-websocket Patch
@solana/rpc-transformers Patch
@solana/rpc-transport-http Patch
@solana/rpc-types Patch
@solana/webcrypto-ed25519-polyfill Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Collaborator Author

lorisleiva commented Mar 15, 2024

@mergify mergify bot added the community label Mar 15, 2024
@mergify mergify bot requested a review from a team March 15, 2024 12:35
@lorisleiva lorisleiva force-pushed the loris/codec-drain-outer-generic branch from a8fbba0 to 2206d24 Compare March 15, 2024 12:44
@lorisleiva
Copy link
Collaborator Author

@lithdew Would you have any recommendation on how I could reproduce your performance issues? I copy/pasted the code you shared in the original issue for the benchmark above but it didn't make any difference.

@lorisleiva lorisleiva self-assigned this Mar 15, 2024
@lithdew
Copy link

lithdew commented Mar 15, 2024

Would you be able to try a trace with this?

import {
  getDataEnumCodec,
  getStructCodec,
  getStringCodec,
  getU64Codec,
  getU16Codec,
  getNullableCodec,
  getArrayCodec,
  getUnitCodec,
  getBase58Codec,
  getBooleanCodec,
  getScalarEnumCodec,
  getU8Codec,
  type Codec,
} from "@solana/web3.js";

export enum Currency {
  sol = "sol",
  like = "like",
  usdc = "usdc",
}

export const CURRENCY_CODEC = getScalarEnumCodec(Currency);

export const PUBLIC_KEY = getStringCodec({
  size: 32,
  encoding: getBase58Codec(),
});

export const CREATOR = getStructCodec([
  ["address", PUBLIC_KEY],
  ["verified", getBooleanCodec()],
  ["share", getU8Codec()],
]);

export const SPLIT = getStructCodec([
  ["address", PUBLIC_KEY],
  ["share", getU8Codec()],
]);

export const PASS_INSTRUCTION_DATA = getDataEnumCodec([
  [
    "create_pass",
    getStructCodec([
      ["name", getStringCodec()],
      ["symbol", getStringCodec()],
      ["uri", getStringCodec()],
      ["price", getU64Codec()],
      ["token", CURRENCY_CODEC],
      ["supply", getU64Codec()],
      ["sellerFeeBasisPoints", getU16Codec()],
      ["creators", getNullableCodec(getArrayCodec(CREATOR))],
      ["firstSaleSplit", getArrayCodec(SPLIT)],
    ]),
  ],
  [
    "update_pass",
    getStructCodec([
      ["name", getNullableCodec(getStringCodec())],
      ["symbol", getNullableCodec(getStringCodec())],
      ["uri", getNullableCodec(getStringCodec())],
      ["price", getNullableCodec(getU64Codec())],
      ["token", getNullableCodec(CURRENCY_CODEC)],
      ["supply", getNullableCodec(getU64Codec())],
      ["sellerFeeBasisPoints", getNullableCodec(getU16Codec())],
      ["creators", getNullableCodec(getNullableCodec(getArrayCodec(CREATOR)))],
      ["firstSaleSplit", getNullableCodec(getArrayCodec(SPLIT))],
    ]),
  ],
  ["mint_pass", getUnitCodec()],
]);

export type CodecInput<T> = T extends Codec<infer TFrom, any> ? TFrom : never;

export type CodecOutput<T> = T extends Codec<any, infer TTo> ? TTo : never;

export type PassInstructionDataInput = CodecInput<typeof PASS_INSTRUCTION_DATA>;
export type PassInstructionDataOutput = CodecOutput<
  typeof PASS_INSTRUCTION_DATA
>;

@lorisleiva lorisleiva force-pushed the loris/codec-drain-outer-generic branch from 2206d24 to 08b075b Compare March 15, 2024 13:14
@lorisleiva
Copy link
Collaborator Author

@lithdew Almost identical traces on my side. I don't know if the results are biased by my machine's specs though. I also tried running this snippet on a code-sandbox and the TypeScript autocompletion was almost instant.

Are you able to compare the two branches on your side and tell me if you see a difference?

@lithdew
Copy link

lithdew commented Mar 15, 2024

Would you have the code sandbox available? I can try paste in parts of my project into it. Having a hard time linking to @solana/web3.js@master.

@lorisleiva
Copy link
Collaborator Author

Sure it's here but the codesandbox only checks the current version. You'd need to check the branch locally to test the changes of this PR.

@lithdew
Copy link

lithdew commented Mar 17, 2024

Before:

Files:                         387
Lines of Library:            40626
Lines of Definitions:        92803
Lines of TypeScript:            81
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                153002
Symbols:                    163279
Types:                       82738
Instantiations:             642518
Memory used:               288025K
Assignability cache size:    42094
Identity cache size:           382
Subtype cache size:            161
Strict subtype cache size:     358
I/O Read time:               0.02s
Parse time:                  0.32s
ResolveModule time:          0.04s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.01s
Program time:                0.45s
Bind time:                   0.20s
Check time:                  1.88s
transformTime time:          0.01s
commentTime time:            0.00s
I/O Write time:              0.00s
printTime time:              0.01s
Emit time:                   0.01s
Total time:                  2.54s

After:

Files:                         388
Lines of Library:            40626
Lines of Definitions:        92819
Lines of TypeScript:            81
Lines of JavaScript:             0
Lines of JSON:                   0
Lines of Other:                  0
Identifiers:                153028
Symbols:                    163768
Types:                       82823
Instantiations:             400640
Memory used:               286162K
Assignability cache size:    42101
Identity cache size:           382
Subtype cache size:            161
Strict subtype cache size:     354
I/O Read time:               0.02s
Parse time:                  0.32s
ResolveModule time:          0.05s
ResolveTypeReference time:   0.01s
ResolveLibrary time:         0.01s
Program time:                0.44s
Bind time:                   0.18s
Check time:                  1.44s
transformTime time:          0.00s
commentTime time:            0.00s
I/O Write time:              0.00s
printTime time:              0.01s
Emit time:                   0.01s
Total time:                  2.08s

There is a noticeable improvement of 642518 -> 400640 instantiations processed. Wonder if there are other places this trick could be used too.

@lorisleiva
Copy link
Collaborator Author

Oh nice thanks! Would you mind sharing your process so I can check on my side and learn from you?

@lithdew
Copy link

lithdew commented Mar 18, 2024

Oh nice thanks! Would you mind sharing your process so I can check on my side and learn from you?

Sure - I cloned the @solana/web3.js repository, pnpm i && pnpm run compile, and created a packages/test folder with the following files:

src/index.ts:

import {
    type Codec,
    getArrayCodec,
    getBase58Codec,
    getBooleanCodec,
    getDataEnumCodec,
    getNullableCodec,
    getScalarEnumCodec,
    getStringCodec,
    getStructCodec,
    getU8Codec,
    getU16Codec,
    getU64Codec,
    getUnitCodec,
} from '@solana/codecs';

export enum Currency {
    sol = 'sol',
    like = 'like',
    usdc = 'usdc',
}

export const CURRENCY_CODEC = getScalarEnumCodec(Currency);

export const PUBLIC_KEY = getStringCodec({
    encoding: getBase58Codec(),
    size: 32,
});

export const CREATOR = getStructCodec([
    ['address', PUBLIC_KEY],
    ['verified', getBooleanCodec()],
    ['share', getU8Codec()],
]);

export const SPLIT = getStructCodec([
    ['address', PUBLIC_KEY],
    ['share', getU8Codec()],
]);

export const PASS_INSTRUCTION_DATA = getDataEnumCodec([
    [
        'create_pass',
        getStructCodec([
            ['name', getStringCodec()],
            ['symbol', getStringCodec()],
            ['uri', getStringCodec()],
            ['price', getU64Codec()],
            ['token', CURRENCY_CODEC],
            ['supply', getU64Codec()],
            ['sellerFeeBasisPoints', getU16Codec()],
            ['creators', getNullableCodec(getArrayCodec(CREATOR))],
            ['firstSaleSplit', getArrayCodec(SPLIT)],
        ]),
    ],
    [
        'update_pass',
        getStructCodec([
            ['name', getNullableCodec(getStringCodec())],
            ['symbol', getNullableCodec(getStringCodec())],
            ['uri', getNullableCodec(getStringCodec())],
            ['price', getNullableCodec(getU64Codec())],
            ['token', getNullableCodec(CURRENCY_CODEC)],
            ['supply', getNullableCodec(getU64Codec())],
            ['sellerFeeBasisPoints', getNullableCodec(getU16Codec())],
            ['creators', getNullableCodec(getNullableCodec(getArrayCodec(CREATOR)))],
            ['firstSaleSplit', getNullableCodec(getArrayCodec(SPLIT))],
        ]),
    ],
    ['mint_pass', getUnitCodec()],
]);

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type CodecInput<T> = T extends Codec<infer TFrom, any> ? TFrom : never;

// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type CodecOutput<T> = T extends Codec<any, infer TTo> ? TTo : never;

export type PassInstructionDataInput = CodecInput<typeof PASS_INSTRUCTION_DATA>;
export type PassInstructionDataOutput = CodecOutput<typeof PASS_INSTRUCTION_DATA>;

package.json:

{
  "name": "test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@solana/codecs": "workspace:2.0.0-preview.1"
  }
}

tsconfig.json

{
  "$schema": "https://json.schemastore.org/tsconfig",
  "display": "@solana/codecs",
  "extends": "@solana/tsconfig/base.json",
  "include": [
      "src"
  ]
}

I then ran pnpm tsc src/index.ts --extendedDiagnostics.

I switched branches to this PR, deleted all node_modules, and then repeated the same commands.

@lorisleiva
Copy link
Collaborator Author

Thank you for the detailed guide! I was able to reproduce the changes. Whilst on my machine I barely see a performance improvement measured in seconds, I can now see that the number of instantiations is almost reduced in half which is pretty amazing.

Files:                         204 -> 205
Lines of Library:            38131 -> 38131
Lines of Definitions:        52284 -> 52300
Lines of TypeScript:            81 -> 81
Lines of JavaScript:             0 -> 0
Lines of JSON:                   0 -> 0
Lines of Other:                  0 -> 0
Identifiers:                 92492 -> 92518
Symbols:                    111465 -> 111877
Types:                       55305 -> 55390
Instantiations:             603991 -> 362113    // -40%
Memory used:               187111K -> 189767K
Assignability cache size:    21397 -> 21404
Identity cache size:           350 -> 350
Subtype cache size:            161 -> 161
Strict subtype cache size:     358 -> 354
I/O Read time:               0.01s -> 0.01s
Parse time:                  0.18s -> 0.18s
ResolveModule time:          0.02s -> 0.02s
ResolveTypeReference time:   0.00s -> 0.00s
ResolveLibrary time:         0.01s -> 0.01s
Program time:                0.23s -> 0.24s
Bind time:                   0.07s -> 0.07s
Check time:                  0.78s -> 0.72s
transformTime time:          0.00s -> 0.00s
commentTime time:            0.00s -> 0.00s
I/O Write time:              0.00s -> 0.00s
printTime time:              0.01s -> 0.01s
Emit time:                   0.01s -> 0.01s
Total time:                  1.09s -> 1.04s

Wonder if there are other places this trick could be used too.

Yes I'm pretty confident it can. In fact, once I refactor the getTupleCodec slightly, it'll be able to benefit from the same treatment.

Copy link
Collaborator

@steveluscher steveluscher left a comment

Choose a reason for hiding this comment

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

Can you add a bunch of comments to DrainOuterGeneric?

@lorisleiva lorisleiva force-pushed the loris/codec-drain-outer-generic branch from e6d76e0 to d309da8 Compare March 20, 2024 10:25
Copy link
Collaborator Author

lorisleiva commented Mar 20, 2024

Merge activity

@lorisleiva lorisleiva merged commit 6dcf548 into master Mar 20, 2024
8 checks passed
@lorisleiva lorisleiva deleted the loris/codec-drain-outer-generic branch March 20, 2024 10:30
Copy link
Contributor

🎉 This PR is included in version 1.91.2 🎉

The release is available on:

Your semantic-release bot 📦🚀

Copy link
Contributor

Because there has been no activity on this PR for 14 days since it was merged, it has been automatically locked. Please open a new issue if it requires a follow up.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 10, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Improve performance of codec input/output types by draining outer generics.
3 participants