3

Is it better using variables which define a function or using a function in the first place? Furthermore, is there a difference for tree-shaking?

I have a lot of calculation (static) intensive helper classes and was wondering what the best (memory/speed) is.

Here the different ways I have in mind:

class MyClass {
  readonly functionA = (v: string | number, maxDeep: number, curDeep: number = 0): string => {
      if (curDeep < maxDeep) {
          return this.functionA(v, maxDeep, curDeep + 1);
      } else {
          return "function A" + v;
      }
  }

  static functionB(v: string | number, maxDeep: number, curDeep: number = 0): string {
      if (curDeep < maxDeep) {
          return MyClass.functionB(v, maxDeep, curDeep + 1);
      } else {
          return "function B" + v;
      }
  }

  functionC(v: string | number, maxDeep: number, curDeep: number = 0): string {
      if (curDeep < maxDeep) {
          return this.functionC(v, maxDeep, curDeep + 1);
      } else {
          return "function C" + v;
      }
  }

  static readonly functionD = (v: string | number, maxDeep: number, curDeep: number = 0): string => {
      if (curDeep < maxDeep) {
          return MyClass.functionD(v, maxDeep, curDeep + 1);
      } else {
          return "function D" + v;
      }
  }
}

I tried using JSBen for measuring a difference but the results seem to be random. enter image description here

Aluan Haddad
  • 29,886
  • 8
  • 72
  • 84
G-wave
  • 41
  • 2

1 Answers1

6

If you are that concerned about performance to optimize at this level, then having a class that only has static methods introduces some totally unnecessary overhead. Classes are designed to be instantiated, and if you don't use that feature you are wasting some computational resources to have those features available.

When I run your examples (MacOS, Chrome 90.0.4430.93) I get this:

enter image description here

What's clear is that static methods have a large performance costs. Where instance methods are guaranteed to be fast. I wish I could tell you why, but I'm sure it has something to do with the fact that classes are design to be instantiated.


Much simpler than that, is a simple object. Let's add these two tests:

const obj = {
  functionE(v, maxDeep, curDeep = 0) {
    //...
  },

  functionF: (v, maxDeep, curDeep = 0) => {
    //...
  }
};

enter image description here

Those run just about as fast as the instance methods. And it's a pattern that makes more sense. There's no classes because there is no instantiation.


But there's an even simpler alternative:

function rawFunctionStatementG(v, maxDeep, curDeep = 0) {
  //...
}

const rawFunctionVarH = (v, maxDeep, curDeep = 0) => {
  //...
};

enter image description here

Performance wise, we have a winner here. I'm fairly sure this is because you never have to look up a property on an object. You have a reference to the function directly, and can execute it without asking any other object where to find it first.

Updated jsben.ch


And as far as tree shaking, this form is by far the best. It's easiest for bundlers to manage whole exported values, rather than trying to slice and dice individual objects.

If you do something like:

// util-fns.ts
export function myFuncA() { /* ... */ }
export function myFuncB() { /* ... */ }

// some consumer file
import { myFuncA } from './util-fns'

Then it's super easy for a bundler to see that myFuncB is never imported or referenced, and could be potentially pruned.


Lastly...

“The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.” – Donald Knuth

It's very unlikely for performance optimizations of this level to matter in the vast majority of javascript applications. So use the data structures that make the most sense for the application, and according to the standards of the codebase/organization. Those things matter so much more.

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • Nice answer. I'd also add that the more performant, exported functions version is far more idiomatic and far easier to maintain. Static methods are actually much harder to reason about because they can be shadowed (people like to call it overriding but it's not) in derived classes which further explains why they are much slower. – Aluan Haddad May 05 '21 at 01:00
  • Thank you very much. Awesome answer. That makes all sense to me, now. Basically classes make only sense if the have at least one non-static member. I did it totally wrong. – G-wave May 06 '21 at 04:47