unplugin-typia icon indicating copy to clipboard operation
unplugin-typia copied to clipboard

Transform Performance Slow Especially When we run it on dev mode..

Open timoxley opened this issue 1 year ago • 17 comments

I originally posted this on the typia repo, https://github.com/samchon/typia/issues/1303 but it's probably more appropriate here.

I have a small vite/vitest application with a handful of tests. The tests take a long time to cold-start and it appears they are getting slowed down by the typia transform: ~15 seconds for ~15 passes, with about 5 files? That seems awfully slow to me, like it must be doing something very inefficient to end up like that? 🤷 What is it doing with all those cpu cycles?

Is there something that can be tweaked to make it transform faster, e.g. more parallelism, reduce repeated work or something, or is this perhaps a limitation of typescript transform? Any suggestions or additional information I can provide?

~15 seconds for ~15 files

output of vite-plugin-inspect showing typia transform being slow         output of vite-plugin-inspect showing files using typia being the slow ones

AFAICT I'm on the latest version of relevant packages.

    "typescript": "^5.6.2",
    "typia": "^6.11.0",
    "vite": "^5.4.4",
    "vite-plugin-inspect": "^0.8.7",
    "vitest": "^2.1.1"

I had a quick poke around the codebase and I wonder if the problem might be related to creating a new ts.host/ts.program for each file ([id]), rather than a single program for all the files?

https://github.com/ryoppippi/unplugin-typia/blob/9877201c8e456f25c2656a9eebd00b53537cfda3/packages/unplugin-typia/src/core/typia.ts#L180

Not sure if that would make any difference, just guessing.

I also notice that the unplugin-typia cache appears very fragile, with any small change it seems to cause a cache miss on everything. I wonder why it's so sensitive? and whether it could use the built-in caches for rollup/vite/tsc etc?

Also, I tried disabling this plugin in vite and using https://github.com/herberttn/vite-plugin-typescript-transform, hoping that it would pick up on the plugins.transform in my tsconfig.json and do the transforming with the rest of the typescript transpiling, but this didn't seem to work and I'm not sure why.

timoxley avatar Sep 27 '24 18:09 timoxley

Reopened and updated the original issue on the typia repo, I'm now suspecting it might be the typia transform itself that's slow.

https://github.com/samchon/typia/issues/1303#issuecomment-2383941395

timoxley avatar Sep 30 '24 19:09 timoxley

@timoxley OH I missed this issue! I think the transformation is slow because this transformation depends on tsc. I implemented some extra feature for speed in this plugin like file skipping, caching, and so on. But, the tsc itself is slow so I cannot deal with it. So, all we can do is improve typia itself! (Now that oxc can handle dts, I'm thinking it will be faster if rusty-typia can do it, but when will it come out? Or should I just make it ?

ryoppippi avatar Sep 30 '24 19:09 ryoppippi

I'll reopen this issue again

I also notice that the unplugin-typia cache appears very fragile, with any small change it seems to cause a cache miss on everything. I wonder why it's so sensitive? and whether it could use the built-in caches for rollup/vite/tsc etc?

I'm curios! If we can use it, this is really great! In dev mode, if you don't change file, unplugin-typia doesn't process it again, so caching in vite works fine.

I'm surprised that the unplugin-typia makes your project's build slow so much. Hmm, I just want to help you, but idk how can I do this..

ryoppippi avatar Sep 30 '24 19:09 ryoppippi

caching is fragile.

I agree. That is why I disabled the feature by default.. https://github.com/ryoppippi/unplugin-typia/issues/224

ryoppippi avatar Sep 30 '24 19:09 ryoppippi

Another bad thing is that vite transformation is not parallel, it is sequential.

ryoppippi avatar Sep 30 '24 19:09 ryoppippi

I had a quick poke around the codebase and I wonder if the problem might be related to creating a new ts.host/ts.program for each file ([id]), rather than a single program for all the files?

Yeah maybe we can fix it. When I first implement this plugin, creating ts.program with multiple id didn't work, so I implemented it like this. Can we improve it?

ryoppippi avatar Sep 30 '24 19:09 ryoppippi

Oh wait, I found that unplugin-typia gets slower in my project! Let me think... SCR-20240930-sfzv

ryoppippi avatar Sep 30 '24 20:09 ryoppippi

hm I tried a few combinations of up/downgrading both the plugin (to 1.0.0) and typia (random versions between 7.x -> 4.0.1) but didn't spot a clear point where the performance degraded significantly. I wonder if it's tsc or some other dependency

timoxley avatar Sep 30 '24 23:09 timoxley

(Now that oxc can handle dts, I'm thinking it will be faster if rusty-typia can do it, but when will it come out? Or should I just make it ?

maybe you should!

timoxley avatar Sep 30 '24 23:09 timoxley

Hmmm, I'll keep investigating it! Thank you for your reporting and sponsoring me!!

ryoppippi avatar Oct 01 '24 10:10 ryoppippi

edit: disregard this! I can get exactly what I want with `Pick`, and this is irrelevant to the plugin

Perhaps this is more a typia discussion than your plugin, but I had another thought 🙀

IIUC there's two main use-cases for these type checking functions:

  1. validating untyped data into typed data, and
  2. differentiating/deriving the type from already valid data.

The former requires strict, deep checks and is what typia does by default, but it could be rather "wasteful" when you're checking data that's already been (directly or indirectly) validated.

e.g. I'd like to be able to replace this kind of pattern with a typia assert:

const players: Record<PlayerId, Player> = getPlayers()
const player = player[playerId]
if (!player) throw new Error(`Player not found: ${playerId}`)

e.g.

const players: Record<PlayerId, Player> = getPlayers() 
const player = assert<Player>(player[playerId]) // throw if player isn't found

but I don't really need assert to validate all the nested fields on the Player type, just throw if it doesn't look roughly like a Player object i.e. it's a non-null object and the object has `player.type === 'Player', or some other equivalent type discriminator

i.e. before typia, my isPlayer looked something like this:

export const isPlayer = (value: unknown): value is Player => {
	return typeof value === 'object' && value !== null && (value as any).type === 'PLAYER'
}

and I kinda wish typia had an equivalent for this, and maybe even a little more strict e.g. checks existence of, and rough types for all top-level fields, but doesn't bother recursing into objects.


🤔 hm.

I guess I could get that right now just using Pick:

const isPlayer = createIs<Pick<Player, 'type'>>

and even with rough types:

type PrimitiveKeys<T> = {
	[K in keyof T]: T[K] extends object ? never : K
}[keyof T]

const isPlayer = createIs<Pick<Player, PrimitiveKeys<Player>>>

Anyway, the original thought was: if typia didn't always need to recurse into objects, it could have less transformation & validation to do and thus be faster, at both build and run time.

Not sure if this thought is helpful or not since I don't think that's even the bottleneck here, but it was just something I was pondering when I saw how a simple inline is<DeeplyNestedType>(...) call could expand into pages of checks and regexes for a deeply nested objects, checking things that have already been checked elsewhere.

timoxley avatar Oct 01 '24 16:10 timoxley

I think typia generates only what you defined in your type definition if you use assert function, not equal. Does this what you mean?

ryoppippi avatar Oct 01 '24 18:10 ryoppippi

If tsc is slow, it could be improved by using WatchCompilerHost.

like this: https://github.com/miyaji255/hono-typia-openapi/blob/main/src/plugin/index.ts

miyaji255 avatar Oct 30 '24 02:10 miyaji255

@miyaji255 looks interesting Do you want to contribute it?

ryoppippi avatar Nov 13 '24 05:11 ryoppippi

I think we could also save some type by only running the transform if typia is actually imported. Right now it looks like any import from the typia package (like the tags or types) will trigger a transform.

bradleat avatar Feb 06 '25 20:02 bradleat

try using cache: true and importing typia to your frontend app entrypoint to make vite (esbuild) optimizes it. only using cache: true causes vite new dependencies optimized for typia/lib/internal packages.

import { vitePlugin as remix } from '@remix-run/dev'
import UnpluginTypia from '@ryoppippi/unplugin-typia/vite'
import { flatRoutes } from 'remix-flat-routes'
import { defineConfig } from 'vite'
import OptimizeExclude from 'vite-plugin-optimize-exclude'
import tsconfigPaths from 'vite-tsconfig-paths'

export default defineConfig({
  optimizeDeps: {
    entries: ['./app/**/*.{js,ts,tsx}'],
  },
  plugins: [
    OptimizeExclude(),
    remix({}),
    tsconfigPaths(),
    UnpluginTypia({
      cache: true,
    }),
  ],
})

and put import 'typia' in your root.tsx (or any entrypoint) file

zzzz465 avatar Feb 12 '25 07:02 zzzz465

yeah actually caching is working. This is because of tsc's performance actually. And i don't know how typia goes after typescript-go come out.

Due to the nature of typia, it cannot help but depend on the speed of the tsc. If speed is needed, a new one has to be made.

ryoppippi avatar Mar 29 '25 16:03 ryoppippi