cljs-dev

borkdude 2025-11-28T14:01:46.275709Z

@dnolen I found that writing method values as:

function bar(x, ...xs) { return String.prototype.toUpperCase.apply(x, xs); }
makes them more near the performance of direct interop
function direct(x) { return x.toUpperCase(); }
foo (Reflect.apply): 5321.67 ms
bar (call): 4646.54 ms
direct: 4483.81 ms
Any reason not to do this? Can write patch of course, but first it would be good to hear your opinion

borkdude 2025-11-28T14:11:28.600459Z

Meh never mind, bad benchmark probably

borkdude 2025-11-28T14:11:45.210809Z

doens't matter that much:

cljs.user=> (simple-benchmark [f (js/eval "(x, ...xs) => globalThis.String.prototype.toUpperCase.apply(x, xs)")] (f "foo") 100000000)
[f (js/eval "(x, ...xs) => globalThis.String.prototype.toUpperCase.apply(x, xs)")], (f "foo"), 100000000 runs, 1446 msecs
nil
cljs.user=> (simple-benchmark [f (js/eval "(x, ...xs) => Reflect.apply(globalThis.String.prototype.toUpperCase, x, xs)")] (f "foo") 100000000)
[f (js/eval "(x, ...xs) => Reflect.apply(globalThis.String.prototype.toUpperCase, x, xs)")], (f "foo"), 100000000 runs, 1485 msecs
nil

dnolen 2025-11-28T14:12:18.298399Z

Reflect just isn't slow - people need to realize this 🙂

borkdude 2025-11-28T14:13:21.067189Z

in a conversation with @thheller we found that direct interop is just faster, so going with method values wouldn't buy much but slightly worse performance

dnolen 2025-11-28T14:13:59.062399Z

but not by crazy margin

borkdude 2025-11-28T14:14:09.562849Z

that's true

borkdude 2025-11-28T14:14:14.273739Z

cljs.user=> (simple-benchmark [f (js/eval "(x, ...xs) => x.toUpperCase(x)")] (f "foo") 100000000)
[f (js/eval "(x, ...xs) => x.toUpperCase(x)")], (f "foo"), 100000000 runs, 1246 msecs

dnolen 2025-11-28T14:14:29.463219Z

it's just not enough to lose any sleep over in real programs

borkdude 2025-11-28T14:14:35.563089Z

probably true

borkdude 2025-11-28T14:17:48.114919Z

Benchmarking JS is pretty hard. I had chatgpt generate this for me:

function benchmark(fn, label, iterations = 10_000_0000) {
  const start = performance.now();
  for (let i = 0; i < iterations; i++) {
    fn("dude");
  }
  const end = performance.now();
  console.log(`${label}: ${(end - start).toFixed(2)} ms`);
}
But it just mattered with which function you ran it first with. First function fast, second slow (probably de-optimization of some sort). simple-benchmark didn't have this problem at least since it generates a new function + loop every time