ManuelDeLeon / viewmodel

MVVM for Meteor
https://viewmodel.org
MIT License
205 stars 23 forks source link

ViewModel decreases Blaze performance x3 ~ x10 #290

Open arggh opened 7 years ago

arggh commented 7 years ago

@mitar built an UI benchmarking tool for Meteor. It included Blaze, vanilla JS, Blaze Components, Vue and React.

Blaze Components turned out to be really slow compared to plain Blaze.

I then created a fork of the tool with ViewModel included and found out ViewModel is even slower, in some tests a lot slower.

All frameworks compared:

Full results ``` [HMR] Vue 2.4.2 modules.js?hash=0b4346a…:8438 Download the Vue Devtools extension for a better development experience: https://github.com/vuejs/vue-devtools modules.js?hash=0b4346a…:8448 You are running Vue in development mode. Make sure to turn on production mode when deploying for production. See more tips at https://vuejs.org/guide/deployment.html dev-client.js:88 [HMR] Dev client URL 10.0.1.2:3003 dev-client.js:95 [HMR] Dev client connected favicon.ico:1 GET http://localhost:3000/favicon.ico 404 (Not Found) main.js:265 Benchmark started using 2 loops. Mon Jul 24 2017 13:48:28 GMT+0300 (EEST) main.js:243 blaze: null -> other: 3 ms main.js:243 blaze: other -> table1: 2537 ms main.js:243 blaze: table1 -> other: 1147 ms main.js:243 blaze: other -> table1: 2344 ms main.js:243 blaze: table1 -> other: 647 ms main.js:243 blaze: other -> recursive: 8882 ms main.js:243 blaze: recursive -> other: 4730 ms main.js:243 blaze: other -> recursive: 13502 ms main.js:243 blaze: recursive -> other: 3373 ms main.js:243 blaze: other -> table1: 5856 ms main.js:243 blaze: table1 -> table3: 9078 ms main.js:243 blaze: table3 -> table1: 10642 ms main.js:243 blaze: table1 -> table3: 4411 ms main.js:243 blaze: table3 -> table1: 6167 ms main.js:243 blaze: table1 -> table2: 8260 ms main.js:243 blaze: table2 -> table1: 4394 ms main.js:243 blaze: table1 -> table2: 6395 ms main.js:243 blaze: table2 -> table1: 8327 ms main.js:243 viewmodel: null -> other: 144 ms main.js:243 viewmodel: other -> table1: 15761 ms main.js:243 viewmodel: table1 -> other: 2607 ms main.js:243 viewmodel: other -> table1: 16816 ms main.js:243 viewmodel: table1 -> other: 2516 ms main.js:243 viewmodel: other -> recursive: 104410 ms main.js:243 viewmodel: recursive -> other: 16367 ms main.js:243 viewmodel: other -> recursive: 105932 ms main.js:243 viewmodel: recursive -> other: 14835 ms main.js:243 viewmodel: other -> table1: 14592 ms main.js:243 viewmodel: table1 -> table3: 16792 ms main.js:243 viewmodel: table3 -> table1: 19754 ms main.js:243 viewmodel: table1 -> table3: 17362 ms main.js:243 viewmodel: table3 -> table1: 17412 ms main.js:243 viewmodel: table1 -> table2: 19769 ms main.js:243 viewmodel: table2 -> table1: 23314 ms main.js:243 viewmodel: table1 -> table2: 19045 ms main.js:243 viewmodel: table2 -> table1: 25242 ms main.js:243 blaze-components: null -> other: 824 ms main.js:243 blaze-components: other -> table1: 11025 ms main.js:243 blaze-components: table1 -> other: 2054 ms main.js:243 blaze-components: other -> table1: 9106 ms main.js:243 blaze-components: table1 -> other: 2048 ms main.js:243 blaze-components: other -> recursive: 32702 ms main.js:243 blaze-components: recursive -> other: 15840 ms main.js:243 blaze-components: other -> recursive: 29574 ms main.js:243 blaze-components: recursive -> other: 13835 ms main.js:243 blaze-components: other -> table1: 15389 ms main.js:243 blaze-components: table1 -> table3: 14838 ms main.js:243 blaze-components: table3 -> table1: 13041 ms main.js:243 blaze-components: table1 -> table3: 15187 ms main.js:243 blaze-components: table3 -> table1: 13378 ms main.js:243 blaze-components: table1 -> table2: 16261 ms main.js:243 blaze-components: table2 -> table1: 13427 ms main.js:243 blaze-components: table1 -> table2: 17379 ms main.js:243 blaze-components: table2 -> table1: 13973 ms main.js:243 manual: null -> other: 1 ms main.js:243 manual: other -> table1: 1252 ms main.js:243 manual: table1 -> other: 74 ms main.js:243 manual: other -> table1: 639 ms main.js:243 manual: table1 -> other: 72 ms main.js:243 manual: other -> recursive: 338 ms main.js:243 manual: recursive -> other: 278 ms main.js:243 manual: other -> recursive: 144 ms main.js:243 manual: recursive -> other: 277 ms main.js:243 manual: other -> table1: 1094 ms main.js:243 manual: table1 -> table3: 912 ms main.js:243 manual: table3 -> table1: 2317 ms main.js:243 manual: table1 -> table3: 249 ms main.js:243 manual: table3 -> table1: 228 ms main.js:243 manual: table1 -> table2: 214 ms main.js:243 manual: table2 -> table1: 210 ms main.js:243 manual: table1 -> table2: 206 ms main.js:243 manual: table2 -> table1: 213 ms main.js:243 stateful-react: null -> other: 7 ms main.js:243 stateful-react: other -> table1: 1920 ms main.js:243 stateful-react: table1 -> other: 244 ms main.js:243 stateful-react: other -> table1: 985 ms main.js:243 stateful-react: table1 -> other: 229 ms main.js:243 stateful-react: other -> recursive: 3778 ms main.js:243 stateful-react: recursive -> other: 829 ms main.js:243 stateful-react: other -> recursive: 2760 ms main.js:243 stateful-react: recursive -> other: 852 ms main.js:243 stateful-react: other -> table1: 865 ms main.js:243 stateful-react: table1 -> table3: 1102 ms main.js:243 stateful-react: table3 -> table1: 1092 ms main.js:243 stateful-react: table1 -> table3: 1163 ms main.js:243 stateful-react: table3 -> table1: 1086 ms main.js:243 stateful-react: table1 -> table2: 1096 ms main.js:243 stateful-react: table2 -> table1: 1088 ms main.js:243 stateful-react: table1 -> table2: 1127 ms main.js:243 stateful-react: table2 -> table1: 1152 ms main.js:243 stateful-vue: null -> other: 5 ms main.js:243 stateful-vue: other -> table1: 1077 ms main.js:243 stateful-vue: table1 -> other: 1870 ms main.js:243 stateful-vue: other -> table1: 936 ms main.js:243 stateful-vue: table1 -> other: 1991 ms main.js:243 stateful-vue: other -> recursive: 3111 ms main.js:243 stateful-vue: recursive -> other: 10577 ms main.js:243 stateful-vue: other -> recursive: 2875 ms main.js:243 stateful-vue: recursive -> other: 12496 ms main.js:243 stateful-vue: other -> table1: 1246 ms main.js:243 stateful-vue: table1 -> table3: 6065 ms main.js:243 stateful-vue: table3 -> table1: 7207 ms main.js:243 stateful-vue: table1 -> table3: 6312 ms main.js:243 stateful-vue: table3 -> table1: 7163 ms main.js:243 stateful-vue: table1 -> table2: 6667 ms main.js:243 stateful-vue: table2 -> table1: 7304 ms main.js:243 stateful-vue: table1 -> table2: 6757 ms main.js:243 stateful-vue: table2 -> table1: 6760 ms main.js:243 stateless-vue: null -> other: 12 ms main.js:243 stateless-vue: other -> table1: 400 ms main.js:243 stateless-vue: table1 -> other: 66 ms main.js:243 stateless-vue: other -> table1: 357 ms main.js:243 stateless-vue: table1 -> other: 61 ms main.js:243 stateless-vue: other -> recursive: 1228 ms main.js:243 stateless-vue: recursive -> other: 231 ms main.js:243 stateless-vue: other -> recursive: 591 ms main.js:243 stateless-vue: recursive -> other: 229 ms main.js:243 stateless-vue: other -> table1: 1188 ms main.js:243 stateless-vue: table1 -> table3: 377 ms main.js:243 stateless-vue: table3 -> table1: 722 ms main.js:243 stateless-vue: table1 -> table3: 346 ms main.js:243 stateless-vue: table3 -> table1: 363 ms main.js:243 stateless-vue: table1 -> table2: 367 ms main.js:243 stateless-vue: table2 -> table1: 356 ms main.js:243 stateless-vue: table1 -> table2: 352 ms main.js:243 stateless-vue: table2 -> table1: 463 ms main.js:286 Benchmark ended. Mon Jul 24 2017 14:11:06 GMT+0300 (EEST) main.js:292 Computing minimongo baseline. Mon Jul 24 2017 14:11:06 GMT+0300 (EEST) main.js:301 Done. Mon Jul 24 2017 14:11:06 GMT+0300 (EEST) main.js:305 Result minimongo 106 main.js:321 Result blaze other -> table1 2440.5 2.581173982020095 main.js:321 Result blaze table1 -> other 897 12.287671232876713 main.js:321 Result blaze other -> recursive 11192 46.439834024896264 main.js:321 Result blaze recursive -> other 4051.5 14.6 main.js:321 Result blaze table1 -> table3 6744.5 11.618432385874247 main.js:321 Result blaze table3 -> table1 8404.5 6.604715127701375 main.js:321 Result blaze table1 -> table2 7327.5 34.892857142857146 main.js:321 Result blaze table2 -> table1 6360.5 30.073286052009458 main.js:321 Result viewmodel other -> table1 16288.5 17.22739291380222 main.js:321 Result viewmodel table1 -> other 2561.5 35.08904109589041 main.js:321 Result viewmodel other -> recursive 105171 436.3941908713693 main.js:321 Result viewmodel recursive -> other 15601 56.21981981981982 main.js:321 Result viewmodel table1 -> table3 17077 29.417743324720067 main.js:321 Result viewmodel table3 -> table1 18583 14.603536345776032 main.js:321 Result viewmodel table1 -> table2 19407 92.41428571428571 main.js:321 Result viewmodel table2 -> table1 24278 114.78959810874704 main.js:321 Result blaze-components other -> table1 10065.5 10.645690111052353 main.js:321 Result blaze-components table1 -> other 2051 28.095890410958905 main.js:321 Result blaze-components other -> recursive 31138 129.2033195020747 main.js:321 Result blaze-components recursive -> other 14837.5 53.468468468468465 main.js:321 Result blaze-components table1 -> table3 15012.5 25.86132644272179 main.js:321 Result blaze-components table3 -> table1 13209.5 10.38074656188605 main.js:321 Result blaze-components table1 -> table2 16820 80.0952380952381 main.js:321 Result blaze-components table2 -> table1 13700 64.77541371158392 main.js:321 Result manual other -> table1 945.5 1 main.js:321 Result manual table1 -> other 73 1 main.js:321 Result manual other -> recursive 241 1 main.js:321 Result manual recursive -> other 277.5 1 main.js:321 Result manual table1 -> table3 580.5 1 main.js:321 Result manual table3 -> table1 1272.5 1 main.js:321 Result manual table1 -> table2 210 1 main.js:321 Result manual table2 -> table1 211.5 1 main.js:321 Result stateful-react other -> table1 1452.5 1.5362242199894236 main.js:321 Result stateful-react table1 -> other 236.5 3.23972602739726 main.js:321 Result stateful-react other -> recursive 3269 13.564315352697095 main.js:321 Result stateful-react recursive -> other 840.5 3.028828828828829 main.js:321 Result stateful-react table1 -> table3 1132.5 1.950904392764858 main.js:321 Result stateful-react table3 -> table1 1089 0.8557956777996071 main.js:321 Result stateful-react table1 -> table2 1111.5 5.292857142857143 main.js:321 Result stateful-react table2 -> table1 1120 5.295508274231678 main.js:321 Result stateful-vue other -> table1 1006.5 1.064516129032258 main.js:321 Result stateful-vue table1 -> other 1930.5 26.445205479452056 main.js:321 Result stateful-vue other -> recursive 2993 12.41908713692946 main.js:321 Result stateful-vue recursive -> other 11536.5 41.57297297297297 main.js:321 Result stateful-vue table1 -> table3 6188.5 10.660637381567614 main.js:321 Result stateful-vue table3 -> table1 7185 5.6463654223968565 main.js:321 Result stateful-vue table1 -> table2 6712 31.961904761904762 main.js:321 Result stateful-vue table2 -> table1 7032 33.248226950354606 main.js:321 Result stateless-vue other -> table1 378.5 0.40031729243786357 main.js:321 Result stateless-vue table1 -> other 63.5 0.8698630136986302 main.js:321 Result stateless-vue other -> recursive 909.5 3.7738589211618256 main.js:321 Result stateless-vue recursive -> other 230 0.8288288288288288 main.js:321 Result stateless-vue table1 -> table3 361.5 0.6227390180878553 main.js:321 Result stateless-vue table3 -> table1 542.5 0.4263261296660118 main.js:321 Result stateless-vue table1 -> table2 359.5 1.7119047619047618 main.js:321 Result stateless-vue table2 -> table1 409.5 1.9361702127659575 ```
meteor_benchmark_results

Detailed comparison of just Blaze and ViewModel:

Full results ``` [HMR] Vue 2.4.1 akryum_vue-component-dev-client.js:174 [HMR] Dev client URL 10.0.1.3:4003 modules.js:8417 Download the Vue Devtools extension for a better development experience: https://github.com/vuejs/vue-devtools modules.js:8427 You are running Vue in development mode. Make sure to turn on production mode when deploying for production. See more tips at https://vuejs.org/guide/deployment.html akryum_vue-component-dev-client.js:181 [HMR] Dev client connected main.js:257 Benchmark started. Mon Jul 24 2017 14:38:22 GMT+0300 (EEST) main.js:238 blaze: null -> other: 2 ms main.js:238 blaze: other -> table1: 2633 ms main.js:238 blaze: table1 -> other: 1066 ms main.js:238 blaze: other -> table1: 3838 ms main.js:238 blaze: table1 -> other: 702 ms main.js:238 blaze: other -> table1: 4021 ms main.js:238 blaze: table1 -> other: 1153 ms main.js:238 blaze: other -> recursive: 13006 ms main.js:238 blaze: recursive -> other: 5486 ms main.js:238 blaze: other -> recursive: 16816 ms main.js:238 blaze: recursive -> other: 3731 ms main.js:238 blaze: other -> recursive: 16692 ms main.js:238 blaze: recursive -> other: 6881 ms main.js:238 blaze: other -> table1: 11134 ms main.js:238 blaze: table1 -> table3: 5288 ms main.js:238 blaze: table3 -> table1: 7305 ms main.js:238 blaze: table1 -> table3: 9565 ms main.js:238 blaze: table3 -> table1: 5145 ms main.js:238 blaze: table1 -> table3: 7301 ms main.js:238 blaze: table3 -> table1: 9281 ms main.js:238 blaze: table1 -> table2: 5199 ms main.js:238 blaze: table2 -> table1: 7197 ms main.js:238 blaze: table1 -> table2: 9419 ms main.js:238 blaze: table2 -> table1: 5161 ms main.js:238 blaze: table1 -> table2: 7414 ms main.js:238 blaze: table2 -> table1: 9448 ms main.js:238 viewmodel: null -> other: 151 ms main.js:238 viewmodel: other -> table1: 15573 ms main.js:238 viewmodel: table1 -> other: 3213 ms main.js:238 viewmodel: other -> table1: 17095 ms main.js:238 viewmodel: table1 -> other: 3182 ms main.js:238 viewmodel: other -> table1: 16375 ms main.js:238 viewmodel: table1 -> other: 3753 ms main.js:238 viewmodel: other -> recursive: 106261 ms main.js:238 viewmodel: recursive -> other: 17294 ms main.js:238 viewmodel: other -> recursive: 99806 ms main.js:238 viewmodel: recursive -> other: 13226 ms main.js:238 viewmodel: other -> recursive: 99385 ms main.js:238 viewmodel: recursive -> other: 15117 ms main.js:238 viewmodel: other -> table1: 13720 ms main.js:238 viewmodel: table1 -> table3: 16988 ms main.js:238 viewmodel: table3 -> table1: 19877 ms main.js:238 viewmodel: table1 -> table3: 20706 ms main.js:238 viewmodel: table3 -> table1: 27242 ms main.js:238 viewmodel: table1 -> table3: 18666 ms main.js:238 viewmodel: table3 -> table1: 18674 ms main.js:238 viewmodel: table1 -> table2: 20651 ms main.js:238 viewmodel: table2 -> table1: 26188 ms main.js:238 viewmodel: table1 -> table2: 17672 ms main.js:238 viewmodel: table2 -> table1: 20641 ms main.js:238 viewmodel: table1 -> table2: 23957 ms main.js:238 viewmodel: table2 -> table1: 20232 ms main.js:278 Benchmark ended. Mon Jul 24 2017 14:55:10 GMT+0300 (EEST) main.js:284 Computing minimongo baseline. Mon Jul 24 2017 14:55:10 GMT+0300 (EEST) main.js:293 Done. Mon Jul 24 2017 14:55:12 GMT+0300 (EEST) main.js:297 Result minimongo 496 main.js:313 Result blaze other -> table1 3497.3333333333335 NaN main.js:313 Result blaze table1 -> other 973.6666666666666 NaN main.js:313 Result blaze other -> recursive 15504.666666666666 NaN main.js:313 Result blaze recursive -> other 5366 NaN main.js:313 Result blaze table1 -> table3 7384.666666666667 NaN main.js:313 Result blaze table3 -> table1 7243.666666666667 NaN main.js:313 Result blaze table1 -> table2 7344 NaN main.js:313 Result blaze table2 -> table1 7268.666666666667 NaN main.js:313 Result viewmodel other -> table1 16347.666666666666 NaN main.js:313 Result viewmodel table1 -> other 3382.6666666666665 NaN main.js:313 Result viewmodel other -> recursive 101817.33333333333 NaN main.js:313 Result viewmodel recursive -> other 15212.333333333334 NaN main.js:313 Result viewmodel table1 -> table3 18786.666666666668 NaN main.js:313 Result viewmodel table3 -> table1 21931 NaN main.js:313 Result viewmodel table1 -> table2 20760 NaN main.js:313 Result viewmodel table2 -> table1 22353.666666666668 NaN ```

blaze viewmodel chart

As you can see, ViewModel's results seem to be about 4x-5x larger than those of plain Blaze (for example, rendering table1, Blaze takes about ~3500ms, ViewModel variant takes ~ 16000ms).

However, the biggest difference is in the recursive test, in which Blaze succeeds in approximately 16 000ms, ViewModel takes a whopping ~100 000 ms.

Profiling the execution with Chrome's Dev Tools Performance tab, there seems to be a few time hogs, one related to firing a lot of timers.

Also, this isn't just a benchmarking problem. I had a real world app with a dynamic table with typically ~100 rows. First draft used ViewModel, with approximate rendering time of 16 seconds. After refactoring to pure Blaze the rendering time dropped to about 3-4 seconds.

Maybe there's a low hanging fruit with a tenfold speed improvement for ViewModel in some scenarios?

ManuelDeLeon commented 7 years ago

Yeah, there has to be plenty of low hanging fruit since I've spent exactly 0s optimizing VM =/

Thank you for setting this up! I really appreciate it.

juho commented 7 years ago

+1 this, any improvements you can do Manuel are greatly appreciated!

jamesgibson14 commented 5 years ago

I have a very large app running Blaze and Viewmodel, most of the time it is not an issue, but sometimes I would enjoy some performance benefits. @ManuelDeLeon I don't know how much help I can be but could you point me towards some of those "low hanging fruits" and maybe I can create some pull requests?

ManuelDeLeon commented 5 years ago

I don't have anything in mind right now (haven't worked on the Blaze version in years). You can just create a page and repeat an object hundreds of times and see what the hot paths are. Repeat the same process for other things like bindings, handling events, etc.