Skip to content

Update deps, add additional unit tests, add signals-polyfill and signia, update results #13

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 46 commits into
base: main
Choose a base branch
from

Conversation

transitive-bullshit
Copy link

@transitive-bullshit transitive-bullshit commented Sep 13, 2024

This PR rebases on top of #12 by @NullVoxPopuli. It updates all deps and contains the changes I needed to get everything working.

  • updates all deps to latest (except for compostate since when I updated to latest it no longer worked)
  • adds TC39 Signals Proposal polyfill framework via Add tc39 stage 0 proposal #12
  • adds the signia framework
  • adds the valtio framework
  • adds some additional unit tests to ensure correctness of the framework adapters
  • changes mobx and @vue/reactivity to use shallow signals (to make sure we're comparing apples to apples)
  • adds some additional unit tests to ensure all frameworks are working as expected
  • fixes several bugs and inconsistencies with the existing framework implementations
  • adds a prettier config which matches the existing formatting
  • updates the readme results (preview here)

For some reason several frameworks are hanging or throwing errors on the cellx benchmark, so I've disabled it for now. The same goes for the largest dynamic benchmark, which I've commented out for now.

Framework average benchmark results

@NullVoxPopuli
Copy link

Looks like we have some iterating to do on the performance of the signal polyfill.

@transitive-bullshit
Copy link
Author

Added Svelte v5 as well

ts-reactivity-benchmark-results-3

@milomg
Copy link
Owner

milomg commented Sep 16, 2024

Wow these new results are really exciting!

@milomg
Copy link
Owner

milomg commented Sep 24, 2024

Quick update: I'm aiming to test and merge this weekend

@robbiespeed
Copy link

I don't think this should merge until the callcount assertions are uncommented out, and cellx bench is re-enabled.

Was there a reason dynamic bench was reduced from 5 to 1 runs? If some FWs are failing 5 runs of some dynamic tests then that could be hinting at a memory leak in those FWs.

If there's a certain benchmark that is commonly failing for some frameworks then maybe a case can be added to the configs like testCellX.

@transitive-bullshit
Copy link
Author

I don't think this should merge until the callcount assertions are uncommented out, and cellx bench is re-enabled.

Was there a reason dynamic bench was reduced from 5 to 1 runs? If some FWs are failing 5 runs of some dynamic tests then that could be hinting at a memory leak in those FWs.

If there's a certain benchmark that is commonly failing for some frameworks then maybe a case can be added to the configs like testCellX.

Great feedback & catches. I had disabled certain things while debugging different frameworks and not everything got put back in the toy bin :) Will update shortly.

@milomg
Copy link
Owner

milomg commented Oct 6, 2024

My current changes in addition to the commented out checks:

  • Remove (globalThis as any).__DEV__ = true;
  • Remove funding.yml
  • Add back in cellx but turned off 2500 and 5000 for now

@transitive-bullshit
Copy link
Author

transitive-bullshit commented Oct 6, 2024

Was there a reason dynamic bench was reduced from 5 to 1 runs? If some FWs are failing 5 runs of some dynamic tests then that could be hinting at a memory leak in those FWs.

I think the reason I changed this is because some of these frameworks take 10+ seconds for 1 run on the dynamic tests, so it's a minute per test per framework which is pretty annoying to wait for. It's not nearly as bad for the better optimized frameworks, but TC39 signals polyfill and angular signals are a pain to benchmark, making the tweak => iteration cycle difficult. Maybe in a follow-up PR, we can address how long it takes to run a benchmark end-to-end? I changed it back to 5 runs for now.

I don't think this should merge until the callcount assertions are uncommented out, and cellx bench is re-enabled.

Done.

My current changes in addition to the commented out checks:

  • Remove (globalThis as any).__DEV__ = true;
  • Remove funding.yml
  • Add back in cellx but turned off 2500 and 5000 for now

Great catches; done.

See db852c5 (#13) for details. There was still one kairo assertion which was failing for multiple frameworks (on the unstable test), so I added a comment there.

@transitive-bullshit
Copy link
Author

transitive-bullshit commented Oct 6, 2024

After making these changes, here's the latest output from pnpm bench: out.csv

We may want to consider automating the creation of the benchmark results graph as a follow-up to this PR. I've been manually uploading the CSV into ChatGPT with the following prompt:

Attached is a spreadsheet containing benchmark results for several JavaScript frameworks. Every framework contains the same set of benchmark test results, with time given in milliseconds.

Analyze the benchmark results and create a bar chart comparing the framework's average times across all tests, with the bars sorted by average time, with the fastest at the top. Frameworks should be on the y-axis and time should be on the x-axis. There should only be one set of y-axis labels on the left-hand-side of the graph. Use a color palette where each bar's hue is a uniform range from green to red, with the lowest bars being green and the highest bars being red.

@@ -10,7 +10,7 @@ import { fastestTest } from "./util/benchRepeat";
*/
export async function dynamicBench(
frameworkTest: FrameworkInfo,
testRepeats = 1
testRepeats = 5
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changing it back to the original value

@milomg
Copy link
Owner

milomg commented Oct 6, 2024

Yeah, for now I was just replacing the CSV file here with an updated one, but it should be possible to create a chartjs/d3 script to autogenerate results

@@ -22,7 +22,7 @@ export const solidFramework: ReactiveFramework = {
read: () => memo(),
};
},
effect: (fn) => createEffect(fn),
effect: (fn) => createRenderEffect(fn),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same reason as above; the new unit tests weren't passing without it.


for (let i = 0; i < iterations; i++) {
sum = readLeaves.reduce((total, leaf) => leaf.read() + total, 0);
} else {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This allows for an algorithm that defers all changes until after a batch is finished to only walk the graph once instead of iterations times (especially because we have no verification on what leaf.read returns.

Would it be possible to have all frameworks use the same path? (and in a future change I'll add assertions to ensure that every framework's leaf.read is actually getting the right result)

@@ -16,7 +16,10 @@ export const reactivelyFramework: ReactiveFramework = {
read: () => r.get(),
};
},
effect: (fn) => new Reactive(fn, true),
effect: (fn) => {
fn();
Copy link
Owner

@milomg milomg Oct 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? Eagerly evaluating effects should probably be done by calling the required function in the framework (stabilize, although that should be called by batch anyways) instead of calling the effect function twice (which gives reactively and oby a disadvantage if the effect is slow because they can't memoize it)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the new unit tests that I added to ensure we're comparing apples to apples across the different frameworks only work with this added.

happy to change it, but I feel like it's important for the unit tests across all frameworks to work the same way.

Copy link
Owner

@milomg milomg Oct 6, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with having tests to ensure that the benchmark is fair, although I think the unit tests may be more strict than we expect. Afaik we don't have any benchmarks that read or write during the build stage (they always return functions that are run outside of the build stage).

Then, making Reactively's withBuild run a stabilize tick after construction should get the effects to run as intended without needing every effect to run twice

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to pass tests for me

export const reactivelyFramework: ReactiveFramework = {
  name: "@reactively",
  signal: (initialValue) => {
    const r = new Reactive(initialValue);
    return {
      write: (v) => r.set(v),
      read: () => r.get(),
    };
  },
  computed: (fn) => {
    const r = new Reactive(fn);
    return {
      read: () => r.get(),
    };
  },
  effect: (fn) => {
    return new Reactive(fn, true);
  },
  withBatch: (fn) => {
    fn();
    stabilize();
  },
  withBuild: (fn) => {
    const out = fn();
    stabilize();
    return out;
  },
};

@milomg
Copy link
Owner

milomg commented Mar 23, 2025

I've been trying to incorporate features from this PR into the main branch. Is there anything else that's missing for you (if so I'd love to add it), or can I close this PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants