Skip to content
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

[WIP] Bitmask-based change tracking #3945

Merged
merged 46 commits into from
Nov 23, 2019
Merged

[WIP] Bitmask-based change tracking #3945

merged 46 commits into from
Nov 23, 2019

Conversation

Rich-Harris
Copy link
Member

Another crack at #1943. First task is to confirm that it's possible and see how it affects the output; after that, need to figure out how to handle components with more than 31 changeable top-level values

@stalkerg
Copy link
Contributor

how to handle components with more than 31 changeable top-level values

Add another one bitmask? At least, PostgresDB does the same things for nulls in tuples.

@tomcon
Copy link

tomcon commented Nov 19, 2019

@Rich-Harris
Copy link
Member Author

BigInt isn't really an option, it's not supported widely enough. One day. The most likely option at this point is an array of bitmasks

@Rich-Harris
Copy link
Member Author

Getting there. All the runtime tests are passing, but there are a few js tests where weird-looking code is being generated. Also, I carelessly broke sourcemaps, which will take a little bit of unpicking.

@Conduitry
Copy link
Member

Sometime in the past when discussing bitmasks, we'd mention having comments next to ctx[123] and 123 & dirty corresponding to the old ctx key names, so that the code isn't so mysterious looking. Is this something it would be possible to add without too many changes? If the code that generates these is fairly localized, it seems like hopefully this would mostly be a question of whether code-red can support something like that.

@Rich-Harris
Copy link
Member Author

Yes. I've pushed a change to that effect, but... well:

-if (dirty & 2) set_data(t4, ctx[1]);
+if (dirty & /* _id9uocqrtmw00_1_ */ 2) set_data(t4, ctx[1]);

Not quite sure why it's not replacing the comment content — can't seem to reproduce inside code-red.

@Rich-Harris
Copy link
Member Author

okay fixed. i think this is basically done now?

@Conduitry
Copy link
Member

Do you think we could use comments where we're accessing ctx[123] as well, and not just where we're doing the bit operation stuff?

@Rich-Harris
Copy link
Member Author

yep — done

@Conduitry
Copy link
Member

It looks like the failing tests are from precisely those mysterious changes to style-related things in a few tests. It seems like something weird happened when you ran the tests locally to update the expected js samples? I have no idea.

@Conduitry
Copy link
Member

Pushed an update. I'm at a loss as to what could've happened. The tests pass locally for me now - hopefully they will here as well.

@Conduitry
Copy link
Member

#3977 was actually responsible for the weird expected.js changes here. If the very first thing the compiler compiles doesn't have component styles, weird stuff happens. During CI, this is normally hidden, since css is alphabetically first, and so the first test that is run does have component styles.

I bet when you were running the tests locally just before committing the changes to the expected.js files, you using describe.only to only run the js tests, or were otherwise doing something that caused the first compiled component to not have component styles.

@Rich-Harris
Copy link
Member Author

Good news — I just rebuilt svelte.dev with this branch, and the combined weight of all the minified JS chunks is 3.8% smaller. Zipped, the difference is less (1.3%), but still not nothing. Given how much of the weight of the site is accounted for by CodeMirror, I'm encouraged by these numbers.

I haven't done any performance testing, but based on microbenchmarks of object lookups vs bitwise operations, this is basically guaranteed to be faster, especially since we're no longer constantly changing the shape of the changed object. And each component will use slightly less memory as well. Win all round.

I'll merge this in, and then maybe start testing another hypothesis: since it's now nice and easy to say 'everything changed' (by setting the bitmask to -1), it may be possible to unify initial creation and update, which would remove a lot of duplicated code. Not totally straightforward — need to think about transitions, and how to create a component without running its <script> block until it first gets props — but worth exploring.

@RedHatter
Copy link
Contributor

These changes look great! Smaller bundle size and better performance. They do cause an issue with svelte-devtools though. Now that the ctx object is a simple array I have no way to map the values back to their original names. For example, previously a user might see something like this

State
string: "testing"
number: 5

Now in 3.16 they would instead see

State
0: "testing"
1: 5

I'm not sure how to solve this. Could some sort of ctx index to local name map be exposed in dev mode, similar to how the props work?

Devtools is still "usable" you can still see and change the state it's just completely unnamed. Let me know if you have any ideas.

@Rich-Harris
Copy link
Member Author

@RedHatter I think that's the right approach, yeah (a lookup provided in dev mode). Mind raising an issue?

(for future reference: I don't get email notifications of activity on issues and PRs, so comments on stuff that's closed/merged are very likely to pass me by. I only happened to see this because of an ancient open tab 😀 )

@champkeh
Copy link

champkeh commented May 8, 2021

@Rich-Harris
Hi Harris, you mention this in #1943

I was wrong about being able to represent up to 53 values in a bitmask. JS bitwise operators work in 32 bits, which means we can represent up to 31 values. I don't know if that's a dealbreaker

I confuse about why 32bit integer only represent up to 31 values? Isn't it 32 values?

I know the most significant bit set 1 will cause this number negative, but that seems no matter, we don't need compare the number sign, we just use & to check its bit.

I also look through this comments and #1943 comments, no one seems to have mentioned it, only @mrkishi provide follow code example:

// i and j could be calculated at runtime from index,
// with i = (index / 32) | 0 and j = index % 32,
// but there's no reason to
function make_dirty(component, i, j) {
  if (component.$$.dirty.length === 0) {
  	dirty_components.push(component);
  	schedule_update();
  }
  component.$$.dirty[i] |= (1 << j);
}

but this code suppose one number present 32 values (i = (index / 32) | 0; j = index % 32)

in current source, the function make_dirty is this:

function make_dirty(component, i) {
	if (component.$$.dirty[0] === -1) {
		dirty_components.push(component);
		schedule_update();
		component.$$.dirty.fill(0);
	}
	component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
}

So, I want to know if there are other reason that 32bit only present 31 values.
(sorry for my pool english)

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.

8 participants