vitest: Vitest runs tests 3x slower than Jest with threads: true (default) setting
Describe the bug
With the latest Vitest/Vite dependencies, Vitest is ~3 times slower than Jest in https://github.com/EvHaus/jest-vs-jasmine (more or less like a typical front-end repo) with the threads: true
(default) setting.
Benchmark 2: yarn workspace jest test
Time (mean ± σ): 34.704 s ± 2.548 s [User: 91.246 s, System: 16.712 s]
Range (min … max): 32.017 s … 41.447 s 10 runs
Benchmark 3: yarn workspace vitest test --threads=true
Time (mean ± σ): 99.077 s ± 2.015 s [User: 185.493 s, System: 40.303 s]
Range (min … max): 97.352 s … 103.406 s 10 runs
Related comment https://github.com/vitest-dev/vitest/issues/229#issuecomment-1003235680. That issue was closed but threads: true
is still 3-4 slower than Jest after the issue was closed and the repro dependencies updated).
The issue was raised in Discord chat as well.
With threads: false
Vitest is ~2 times faster, but the cost of it - there’s no real isolation. With threads: false
it’s unfortunately not even close what Jest is doing in terms or resetting state, so not even sure if makes sense to compare threads: false
with Jest (maybe threads: false should be compared with uvu
or other “run-once-and-forget” runners with no real isolation). In a variety of projects that we have worked on, even with pure stateless components, the proper isolation is a super important concern. Especially in watch mode where introducing some unwanted state/mocking or polluting global/process state is simply an expected thing to happen due to the nature of various incremental changes (that are not always good or final) that simply break not properly isolated watch mode.
Reproduction
git clone https://github.com/EvHaus/jest-vs-jasmine
yarn
(if required, install hyperfine
)
hyperfine --warmup 1 'yarn workspace jest test' 'yarn workspace vitest test --threads=true'
System Info
System:
OS: macOS 11.2.3
CPU: (16) x64 Intel(R) Xeon(R) W-2140B CPU @ 3.20GHz
Memory: 25.41 MB / 32.00 GB
Shell: 3.2.57 - /bin/bash
Binaries:
Node: 16.13.1 - ~/.nvm/versions/node/v16.13.1/bin/node
Yarn: 3.1.1 - ~/.yarn/bin/yarn
npm: 8.1.2 - ~/.nvm/versions/node/v16.13.1/bin/npm
Watchman: 4.9.0 - /usr/local/bin/watchman
Browsers:
Chrome: 97.0.4692.71
Firefox Developer Edition: 61.0
Safari: 14.0.3
Used Package Manager
npm
Validations
- Follow our Code of Conduct
- Read the Contributing Guidelines.
- Read the docs.
- Check that there isn’t already an issue that reports the same bug to avoid creating a duplicate.
- Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- The provided reproduction is a minimal reproducible example of the bug.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 69
- Comments: 83 (36 by maintainers)
Links to this issue
Commits related to this issue
- docs: fix (#579) A slash was missing in the close-tag. — committed to chaii3/vitest by thisprojectworks 3 years ago
@pmdarrow I’m investigating switching to
vm.runInNewContext
fromvm.runInThisContext
. This, hopefully, will allow us to drop costly isolation using Workers. We will essentially have the same isolation as Jest.But I did not have time to work on this recently (there is the war going on)
So is the summary that we’re stuck with the performance where it’s at right now, unless we want to sacrifice isolation? If so, I think this should be published in the docs somewhere. I know the docs say disabling isolation can improve performance, but it doesn’t communicate that comparable tools (i.e. Jest) are 3x faster even with isolation. It would help to know up front why vite is slower by default and what is blocking it from becoming faster (
vm
module bugs?)Just a heads up. The next Vitest version should enable profiling workers (thanks to @AriPerkkio in #2702), so it might be easier to find the problem. In the meantime you can enable a few environmental variables to debug performance:
VITE_NODE_DEBUG_RUNNER=true
will log modules that take a lot of time to loadDEBUG=vite-node:*
will show resolve and load timeVITE_NODE_DEBUG_DUMP=load
enables debug cache (which is PERSISTENT between runs, so don’t use it for your regular tests), so you can see what files were transformed and how they look likeMy team in the process of migrating a large test suite (6073 tests) from jest to vitest. With jest, the full suite runs in 3m47s. With the latest vitest (0.18.0) it runs in 10m7s. We really want to continue with vitest but this kind of a slow down is making it challenging. We don’t want to disable isolation, as it’s crucial for reliability in a test suite this large. Is there any plan to improve the performance vitest, or is jest doing something fundamentally different that vitest won’t be able to replicate? @antfu @sheremet-va @Demivan
I trimmed a bit locally. Flow + Less is a bit less common tbh, but yes with your tests I can confirm it surfaced some bottleneck of Vitest, where I am still investigating.
Has there been an attempt to improve Vitest’s performance? I also experienced slower test-performance with Vitest compared to Jest (about 1.5x slower) as documented here. It would be unfortunate to see this project fail after being heralded as superior to the existing options.
SUMMARY
I’ve written up a summary of all the advice given in this GitHub Issue as a time saver for those coming here in the future.
Improving Vitest Performance
@antfu is there anything that we could do to help move this forward?
I tested on my end with a different setup.
Many test files (5000) 1 test per test file
Repo: https://github.com/aurelienbottazini/test-js-test-runners
Jest finishes in 70 seconds on my computer Vitest is very very slow (i end up canceling the task, tired of waiting) and test are progressively running slower and slower.
toward the end i even see slowness reports/warnings for each spec:
Running with
--isolate=false
is also very very slow--isolate=false --single-thread=true
is also very very slow@AriPerkkio Really great to see the performance improvements from https://github.com/vitest-dev/vitest/pull/3006. Moving the 5000 test above from 5667.68s to 4.63s is huge! Thanks for the effort on that.
Please, to all the people who basically just add “+1” - stop. A lot of people are following this thread, and it doesn’t help anyone, including maintainers.
If you have real examples, please provide it. Otherwise it’s just an annoying notification.
@EvHaus @antfu that’s not an apples-to-apples comparison. Jest has fully isolated tests, so setting
isolate: false
is an unfair comparison. And it’s clear most devs want their tests isolated.FYI – I updated my benchmarks to get rid of Flow, LESS and switched to more modern ways of doing things: TypeScript, Testing-Library, etc… and that had a drastic impact on the results.
Vitest is now consistently the fastest, with Jasmine just behind and Jest still consistently 4x slower than both (details).
What this tells me is that there’s some other external factor at play here. Perhaps a third-party library, or an extra preprocessor, or something.
Also interestingly,
swc
had 0 effect on the performance ofjest
in my case.Ok, I can have a look at this. Can anyone set up a minimal repo (2~3 test files, or the less possible) of how the slowdown is being observed (as I personally do not face such a slowdown when doing unit testing or testing Vue)? Thanks
Is there anyway of debugging
vitest
performance? Maybe list what takes time on each run, or which dependencies are loaded? After switching a React Testing Library application (built with Vite) from Jest to Vitest, the test time CI (Azure DevOps) went from around 50s with code coverage using Jest, to 2 minutes without code coverage in Vitest. It could very well be related to dependencies we are using (like Material UI), but it’s really hard to debug.Vitest 0.34.0 is released with the new
experimentalVmThreads
flag which should make tests run as fast as they do in Jest. If you have any issues with the new pool, please open a separate issue instead of commenting here.After investigating it more and checking with @sheremet-va i don’t think it can be implemented with current state of
vm
module. Fixing all the issues will mean getting all Jest bugs too.Lets hope ShadowRealm implementation will be better then
vm
module.--no-threads
is outdated. If you want your tests to run in sequence without firing up new workers use--single-thread
--no-threads
now useschild_process
instead of workers, so your code might run even slower with it.@marpro200 @dmitriybo @sheremet-va lets continue this specific topic (“ViteNodeServer: ${id} not found. This is a bug, please report it.”) in #2940
@cbxp User time will be higher because Vitest is multi-threaded. User time is the time processor spent running a process. If you have multiple cores, user time will be higher for multi-threaded apps. Because it aggregates all the work that all cores did.
I also noticed that after moving from Jest to Vitest, my tests were taking 2-3x more time to run.
After doing some experiments lately, one thing that drastically reduced the time to run all specs was to disable experimental fetch (
NODE_OPTIONS='--no-experimental-fetch' yarn test
). We currently usewhatwg-fetch
.Before: 126.28s After disabling experimental fetch: 53.05s
Same thing for me.
I just finished migrating a project with 3,000+ tests (in 450+ test files).
With Jest, it takes 30 to 40 seconds. With Vitest, it takes 130 to 150 seconds.
Roughly 3 to 4 times slower, which is a bummer, because we’ll probably have to abort the migration at this point.
That is an incorrect statement. It really deps on what and how your tests are written. You can’t say a man walks faster than cars without the context (in a traffic jam or something it might be true). The reason why this is happening commonly due to you are using some giant dependencies, where in isolation, they will be executed multiple times for each tests. Vitest is on-demand like Vite, while Jest will bundle the code, so in some scenario it would be more efficient than Vitest.
While indeed there is some space for optimization like pre-bundle the deps, or waiting for
vm
to support ESM. But in general, it’s still trade-offs between speed and correctness. For performance and also for good practice, I would suggest to make the tests as “unit” as possible, or mocking dependencies whenever you can.@aurelienbottazini this reproduction case seems to hit performance issue related to test reporter. See https://github.com/vitest-dev/vitest/issues/2602.
If you use
--isolate false --reporter tap
in your reproduction case the tests will execute fast.Thanks for the great advice @sheremet-va 🙇. Aliasing element-plus to a
dist
build(for exampleelement-plus/dist/index.full.mjs
) instead ofelement-plus/lib/index.js
sped up things even further: ~7.30s (compared to the initial ~18.66s)On a bigger real world project (couple hundred test files with couple thousand tests… ~30% of those using element-plus)… I managed to shave off 25s (~112s initially, 87s now ). And on top of that, switching to Node 18 brought it down to 70s. And the upcoming “~400 ms for each file” improvement sound brilliant! 🥳
I was actually tried @AriPerkkio’s PR and did some profiling before I came here but couldn’t quite understand what’s going on 😃 https://github.com/vitest-dev/vitest/pull/2702 and https://github.com/vitest-dev/vitest/pull/2721 - great stuff guys 🙌 Can’t wait to get my hands on the next release.
Speaking from experience, enabling
isolate: false
in a large test suite will cause all sorts of unexpected behaviour and flaky tests. In theory, it’s possible, but if you add more than a couple developers on a project, it will get out of hand pretty quickly.Like @thebuilder and @pmdarrow we have also experienced a 3x slowdown of tests after migrating to vitest from jest, our large CI unit test suite now takes 15 mins instead of 5 when running on GHA.
After your comment, I realized there was a test with using
vi.useFakeTimers
, but it’s strange that I didn’t have any issues locally.I ended up being able to make it work without any changes to the tests, using the option
threads: false
thanks to the issue I mentioned earlier.If a test that takes 30 seconds locally takes over 30 minutes on your server, the first thing you need to check is that the test is written correctly. There seems to be something causing an infinite loop or timeout.
Why not import
jsdom
orhappy-dom
once and reuse for all test files in need of it?@adrian-gierakowski
Yep, it depends on the environment. You can check out my previous comment https://github.com/vitest-dev/vitest/issues/579#issuecomment-1406519409
I’ve noticed that one of the reasons why there is a difference between Jest and Vitest is the fact that Vitest imports
jsdom
for each test file which takes a lot of time (300ms on my M1 Mac to simply import jsdom). So one of my recommended solutions is to try to usehappy-dom
, which takes 3 times less to import.I would highly argue that this flag is a solution vitest is still simply slow. Our previous setup with jest was taking around 30-40s and here, we’re facing over 200s just to finish the same tests - and we don’t really have any huge dependencies besides vue, vuex and moveable. We have around ~1500 tests to be run in 360 files, where we use snapshots & expectations. We really were pretty excited about moving onto vite with vitest but this seems simply unrealistic.
What’s even more funny in this case -> with the flag enabled the tests are running even slower 😃 The results with this flag on/off presented below.
With
experimentalOptimizer: true
-> Duration 218.01s (transform 23.50s, setup 47.16s, collect 1850.54s, tests 48.56s) WithexperimentalOptimizer: false
-> Duration 210.45s (transform 21.67s, setup 44.51s, collect 1764.43s, tests 49.67s)0.29.0 is out. This release adds
deps.experimentalOptimizer
option. Please, try this flag if you have any performance issues. Don’t forget to enable threads, if you used--no-threads
before!Any feedback concerning this flag should be published via new issues, NOT inside this issue. Thank you.
Yes, when you import the component, it imports the whole library. Vitest also needs to do this 100 times for each test file. Excluding it from
inline
might help. Also, it might be a good idea to aliaselement-plus
to its CJS version, since this is what jest is importing. My tests show ~10 seconds improvements with this alias (inlining disabled):Basically, it means the time it took for Vitest to do this:
There is also a bottleneck for just importing vitest (~400 ms for each file), which should be fixed in #2721.
Do https://github.com/justjake/quickjs-emscripten will resolve this?
AFAIK, Jasmine doesn’t even have watch mode, so I’m guessing you are comparing/referring to full runs with isolation vs full runs w/o isolation.
In watch mode it’s easy to introduce some unwanted state/mocking or polluting global/process state to the nature of various incremental changes (that are not always good or final). Without proper isolation watch mode becomes unusable and unreliable, it’s not even a choice
between isolation or performance
it’s betweenworking as expected vs restarting watch mode every few minutes
.@EvHaus As you can see from the benchmark above, there was a performance problem when
isolate
wastrue
.Glad to hear that, thanks for the update @EvHaus!
I think there is indeed some more space for us to optimize further.
@dmLinker @pmdarrow We also want to use vitest as our modern test tool but for now we are using @swc/jest with good performance gain (3x)
Same experience here. Jest ran my test-suite in about 3-5 minutes, after migrating to vitest it now takes 10-17 minutes 😞
I’m sure it would be a lot faster if I disabled threads, but it’s not worth dealing with all the potential issues that comes with that.
I do have
.less
files in my project as well, but they are not called.module.less
so I’m not sure if that would affect anything?@phifa I don’t think that’s related to this discussion - that’s just vitest using as many cores as possible. If you don’t want this behaviour, you can turn it off: https://vitest.dev/guide/features.html#threads.
You can get speed boost by disabling isolation. Bear in mind tho, that it will make
globalThis
object sharable between tests.