core icon indicating copy to clipboard operation
core copied to clipboard

perf: Use @@toStringTag and Map for better perf

Open ShuiRuTian opened this issue 1 year ago • 11 comments

There are 2 macro optimizations:

  1. Use instanceof rather than toString, this is about 100% faster, and makes things more accurate.
  2. Use Map rather than Object, this is about 30% faster.

However, I am not sure about isPromise. According to the name, it only want to judge whether it's a Promise or not. But current logic is to judge whether it's thennable. This might be a breaking change.

ShuiRuTian avatar Dec 01 '24 04:12 ShuiRuTian

instanceof cannot work across <iframe> boundaries. Using Object.prototype.toString is a common alternative to determine the type of an object. This covers more use cases.

Justineo avatar Dec 01 '24 05:12 Justineo

@Justineo Thanks. I hardly use iframe and did not think about them...

Use Symbol.toStringTag instead if possible, and it's even faster. However, I found the profiling time vary a lot on my local machine, is there some robot I could call out to profile?

ShuiRuTian avatar Dec 01 '24 10:12 ShuiRuTian

You could consider setting up some benchmarks on platforms like jsbench.me.

Justineo avatar Dec 01 '24 13:12 Justineo

Here is my benchmark example: https://jsbench.me/krm45pp7nd/1

And here is the result:

  • Current: 2266万 ops/s ± 3.28%
  • instanceof: 2.3亿 ops/s ± 1.22%
  • @@toStringTag: 2.2亿 ops/s ± 2.1%

Looks good.

And here is benchmark comparation result, I only picked the result that is changed larger than 5% and rme is less than 1%:

I do not know which part is important, but it looks good to me, especially there are 3 cases that improves 20%+

   · write ref, read computed (with effect)                         7,683,786.00  0.0001  0.3473  0.0001  0.0002  0.0002  0.0003  0.0004  ±0.24%   3841893  [0.93x] ⇓
     write ref, read computed (with effect)                         8,265,654.35  0.0001  0.7701  0.0001  0.0001  0.0002  0.0003  0.0004  ±0.80%   4132828  (baseline)
   · write ref, don't read 1000 computeds (without effect)         22,545,853.49  0.0000  0.4810  0.0000  0.0001  0.0001  0.0001  0.0003  ±0.30%  11272929  [1.06x] ⇑
     write ref, don't read 1000 computeds (without effect)         21,235,565.75  0.0000  0.8568  0.0000  0.0001  0.0001  0.0001  0.0003  ±0.61%  10617785  (baseline)
   · write ref, read 1000 computeds (with multiple effects)            13,229.05  0.0680  0.9690  0.0756  0.0739  0.1381  0.1837  0.3030  ±0.64%      6615  [0.91x] ⇓
     write ref, read 1000 computeds (with multiple effects)            14,535.18  0.0650  0.2618  0.0688  0.0684  0.0814  0.0944  0.1181  ±0.15%      7268  (baseline)
   · 1000 refs, read 1 computed (with effect)                          57,845.38  0.0153  0.3155  0.0173  0.0177  0.0258  0.0286  0.0484  ±0.25%     28923  [0.94x] ⇓
     1000 refs, read 1 computed (with effect)                          61,730.02  0.0148  0.1841  0.0162  0.0166  0.0229  0.0245  0.0271  ±0.11%     30866  (baseline)
   · write reactive map, don't read computed (invoked)              6,280,659.49  0.0001   0.0844  0.0002  0.0002  0.0003  0.0003  0.0005   ±0.10%  3140331  [1.36x] ⇑
     write reactive map, don't read computed (invoked)              4,616,937.08  0.0001   0.3780  0.0002  0.0002  0.0004  0.0005  0.0007   ±0.46%  2308469  (baseline)
   · write reactive map, read computed                              3,317,786.00  0.0002   0.1023  0.0003  0.0003  0.0005  0.0005  0.0008   ±0.08%  1658893  [1.21x] ⇑
     write reactive map, read computed                              2,751,338.35  0.0003   0.2377  0.0004  0.0004  0.0007  0.0008  0.0010   ±0.35%  1375670  (baseline)
   · write reactive map, don't read 1000 computeds (invoked)        5,989,028.00  0.0001   0.0963  0.0002  0.0002  0.0003  0.0004  0.0005   ±0.10%  2994514  [1.41x] ⇑
     write reactive map, don't read 1000 computeds (invoked)        4,245,770.30  0.0001   1.2880  0.0002  0.0003  0.0004  0.0005  0.0007   ±0.84%  2122886  (baseline)
   · 1 ref invoking 10 effects                         3,628,439.27   0.0002   0.3025   0.0003   0.0003   0.0005   0.0005   0.0007  ±0.38%  1814220  [1.10x] ⇑
     1 ref invoking 10 effects                         3,289,414.00   0.0002   0.1333   0.0003   0.0003   0.0004   0.0005   0.0007  ±0.14%  1644707  (baseline)
   · 1 ref invoking 100 effects                          410,433.67   0.0023   0.2580   0.0024   0.0024   0.0028   0.0036   0.0048  ±0.12%   205217  [1.10x] ⇑
     1 ref invoking 100 effects                          374,439.98   0.0025   1.0689   0.0027   0.0027   0.0035   0.0042   0.0051  ±0.43%   187221  (baseline)
   · update ref to trigger watchEffect (scheduled but not executed)  2,781,636.33  0.0002  0.1086  0.0004  0.0004  0.0007  0.0008  0.0010  ±0.10%  1390819  [1.06x] ⇑
     update ref to trigger watchEffect (scheduled but not executed)  2,628,404.42  0.0003  0.1979  0.0004  0.0004  0.0007  0.0008  0.0009  ±0.13%  1314203  (baseline)
   · read reactive obj property   14,950,800.00  0.0000  0.2837  0.0001  0.0001  0.0001  0.0002  0.0003   ±0.30%  7475400  [1.07x] ⇑
     read reactive obj property   13,940,586.00  0.0000  0.2386  0.0001  0.0001  0.0001  0.0001  0.0003   ±0.24%  6970293  (baseline)

ShuiRuTian avatar Dec 01 '24 15:12 ShuiRuTian

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 100 kB (+144 B) 38 kB (+27 B) 34.3 kB (+55 B)
vue.global.prod.js 158 kB (+142 B) 57.8 kB (+33 B) 51.5 kB (+54 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 47.2 kB (+76 B) 18.4 kB (+9 B) 16.8 kB (+17 B)
createApp 55.3 kB (+89 B) 21.4 kB (+14 B) 19.5 kB (+24 B)
createSSRApp 59.4 kB (+91 B) 23.1 kB (+17 B) 21 kB (+13 B)
defineCustomElement 60.2 kB (+91 B) 22.9 kB (+11 B) 20.9 kB (+31 B)
overall 69.2 kB (+87 B) 26.5 kB (+8 B) 24.1 kB (+25 B)

github-actions[bot] avatar Dec 02 '24 13:12 github-actions[bot]

Open in Stackblitz

@vue/compiler-core

npm i https://pkg.pr.new/@vue/compiler-core@12491
@vue/compiler-dom

npm i https://pkg.pr.new/@vue/compiler-dom@12491
@vue/compiler-sfc

npm i https://pkg.pr.new/@vue/compiler-sfc@12491
@vue/compiler-ssr

npm i https://pkg.pr.new/@vue/compiler-ssr@12491
@vue/runtime-core

npm i https://pkg.pr.new/@vue/runtime-core@12491
@vue/reactivity

npm i https://pkg.pr.new/@vue/reactivity@12491
@vue/runtime-dom

npm i https://pkg.pr.new/@vue/runtime-dom@12491
@vue/server-renderer

npm i https://pkg.pr.new/@vue/server-renderer@12491
@vue/shared

npm i https://pkg.pr.new/@vue/shared@12491
vue

npm i https://pkg.pr.new/vue@12491
@vue/compat

npm i https://pkg.pr.new/@vue/compat@12491

commit: c46cd24

pkg-pr-new[bot] avatar Dec 02 '24 13:12 pkg-pr-new[bot]

/ecosystem-ci run

edison1105 avatar Dec 05 '24 01:12 edison1105

📝 Ran ecosystem CI: Open

suite result latest scheduled
language-tools :x: failure :x: failure
nuxt :x: failure :x: failure
pinia :white_check_mark: success :white_check_mark: success
primevue :white_check_mark: success :white_check_mark: success
quasar :white_check_mark: success :white_check_mark: success
radix-vue :white_check_mark: success :white_check_mark: success
router :white_check_mark: success :white_check_mark: success
test-utils :white_check_mark: success :white_check_mark: success
vant :white_check_mark: success :white_check_mark: success
vite-plugin-vue :white_check_mark: success :white_check_mark: success
vitepress :white_check_mark: success :white_check_mark: success
vue-i18n :white_check_mark: success :white_check_mark: success
vue-macros :white_check_mark: success :white_check_mark: success
vuetify :white_check_mark: success :white_check_mark: success
vueuse :white_check_mark: success :white_check_mark: success
vue-simple-compiler :white_check_mark: success :white_check_mark: success

vue-bot avatar Dec 05 '24 01:12 vue-bot

Use Map rather than Object, this is about 30% faster.

I got different performance data, see https://measurethat.net/Benchmarks/Show/32894/1/test-map-vs-object

edison1105 avatar Dec 05 '24 01:12 edison1105

@edison1105

here is my 5 cents about benchmark: https://measurethat.net/Benchmarks/Show/32902/0/test-map-vs-object-with-string-which-use-string-as-key

For set-value, the perf is similar, for get-value, Map is 200% faster(wow...) as shown in this case. If for the cache in Vue, the key is more frequently to be different with the key last time visited, then this test case might reflect real world better.

ShuiRuTian avatar Dec 05 '24 09:12 ShuiRuTian

LGTM

edison1105 avatar Dec 06 '24 07:12 edison1105