4

I work on a project that leans on an app-level provide for global state management. At first, the provided object was small, but it has grown with the project.

How much does app performance suffer for injecting this object where it's data is needed? Short of implementing a global state management tool, like Pinia, is it worthwhile to decompose the large object we're providing into individual objects to provide "chunks" of data where its needed?

Zach M.
  • 74
  • 8
  • It has zero performance impact. You inject it once per instance. There shouldn't be a considerable difference in performance between one vs many stores due to how reactivity works. Design goes first in this case – Estus Flask Oct 20 '22 at 13:25

2 Answers2

3

I've done some basic benchmarking wondering if I should provide/inject frequently used variables or if I should access them from a Pinia store in a big number of components.

The results suggest that there is no substantial performance difference.

My first test case is very basic: Provide/inject a single bool vs get it from a Pinia store in a component and render the component many times. 50k component renders took the time varying between 20-24s, same while using provide/inject and while using Pinia. There is no consistent difference to say any of the two are faster/slower.

In the second test I've used a larger object, an array of roughly 1MiB data (measured as printed in white-space-less JSON). The difference again is non-significant. I've rendered 50k component and it took about the same time, between 19-26s both with inject and with store access.

The components in each case rendered the boolean casted value of the subject variable, so their render time was not inflated with the large data vs the small boolean.

After all this I've concluded that there is really no meaningful difference between provide/inject vs. a Pinia store. The only apparent difference is that with larger data the performance is less stable, with the small data it is more predictable. No matter how many times I've repeated the tests for the boolean, the time was always between 20-24s, with the larger data I've got some outliers such as 19s or 26s. Again nothing consistent, it is likely just the fluctuations of my actual CPU usage, and it is not related to the usage of provide/inject vs. Pinia store.

I've did the tests with Chrome v110 (x86_64) on macOS using vue@3.2.47 & pinia@2.0.32.

Test code I've used:

<template>
  <div>Inject {{ number }}: {{ injected ? 'yes' : 'no' }}</div>
</template>
<script setup lang="ts">
  export interface Props {
    number?: number,
  }
  const props = withDefaults( defineProps<Props>(), {
    number: 0,
  })
  import { inject } from 'vue'
  const injected = inject('inject-test')
</script>
<template>
  <div>Store get {{ number }}: {{ testStore.test ? 'yes' : 'no' }}</div>
</template>
<script setup lang="ts">
  export interface Props {
    number?: number,
  }
  const props = withDefaults( defineProps<Props>(), {
    number: 0,
  })
  import { useTestStore } from 'stores/test'
  const testStore = useTestStore()
</script>
<template>
  <div v-if="testStore">
    <h1>Store get</h1>
    <pre>Start: {{ start() }}</pre>
    <div class="test">
      <StoreGet v-for="n in 50000"
                :key="n"
                :number="n"
      />
    </div>
    <pre>End: {{ end() }}</pre>
    <pre>Duration: {{ endTime - startTime }} seconds</pre>
  </div>
  <div v-else>
    <h1>Inject</h1>
    <pre>Start: {{ start() }}</pre>
    <div class="test">
      <Inject v-for="n in 50000"
              :key="n"
              :number="n"
      />
    </div>
    <pre>End: {{ end() }}</pre>
    <pre>Duration: {{ endTime - startTime }} seconds</pre>
  </div>
</template>

<script setup lang="ts">
  import { provide } from 'vue'
  import Inject from './test/Inject.vue'
  import StoreGet from './test/StoreGet.vue'

  // Roughly 1M of json data
  import { large } from '@sbnc/datasets/largeTest'

  // Comment one or the other:
  const testData = true
  //const testData = large

  // Choose which one to test:
  const testStore = true // set false to test inject

  provide( 'inject-test', testData)

  import { useTestStore } from 'stores/test'
  const testStore = useTestStore()
  testStore.test = testData

  let startTime
  let endTime

  function start() {
    startTime = performance.now()
    return startTime
  }
  function end() {
    endTime = performance.now()
    return endTime
  }
</script>
Bence Szalai
  • 768
  • 8
  • 20
1

Since it's global, I guess it's quite big memory wise. Take a few hours to setup Pinia, will be faster/easier since it will come with only the parts you need.

We can't give you an exact number to your solution since it depends of a lot of things mostly.

PS: it can also be totally irrelevant and coming from another part of your code tbh.

kissu
  • 40,416
  • 14
  • 65
  • 133
  • At the end of the day, we'd be storing the same data in a Pinia store as we do in this globally provided object. With that, can you explain why/how Pinia is the better practice here? ie What do you mean that "it will come with only the parts you need"? I'm confident that moving to Pinia is the better practice here. I'm hesitant, mostly because I'm inexperienced with state management tools. – Zach M. Oct 20 '22 at 15:02
  • 1
    @ZachM here are the reasons as of why use Pinia: https://pinia.vuejs.org/introduction.html#why-should-i-use-pinia Mainly, you will have a simple API (compared to Vuex), with types, devtools to see in realtime what is happening across your whole app. As for the parts, it's namespaced by default so you will only need to import the parts needed from the store while still being explicit like `useCounterStore` rather than trying to know what is available. If your app is growing in size and complexity, using a store will greatly improve your journey if used only when needed: local state is also fine – kissu Oct 20 '22 at 15:20
  • I guess "it will come with only the parts you need" means that you can set up multiple independent stores for different parts of your state, and and only import those you need. I don't think it'll have much benefit performance-wise, as from the stores you would anyway only get the variables needed. And by splitting the global store to parts you may end up with components with multiple store imports, which is a slight overhead. But these are negligible. The real differentiator would rather be how effective provide/inject vs import and use store is. Probably only benchmarking can tell. – Bence Szalai Mar 02 '23 at 07:46