Merge pull request #1274 from sveltejs/fix-perf-regression

A new list diffing algorithm
pull/1279/head
Rich Harris 7 years ago committed by GitHub
commit 89c0864c81
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

3
.gitignore vendored

@ -17,4 +17,5 @@ src/generators/dom/shared.ts
package-lock.json package-lock.json
.idea/ .idea/
*.iml *.iml
store.umd.js store.umd.js
yarn-error.log

@ -246,18 +246,11 @@ export default class EachBlock extends Node {
} }
) { ) {
const key = block.getUniqueName('key'); const key = block.getUniqueName('key');
const blocks = block.getUniqueName(`${each}_blocks`);
const lookup = block.getUniqueName(`${each}_lookup`); const lookup = block.getUniqueName(`${each}_lookup`);
const iteration = block.getUniqueName(`${each}_iteration`);
const head = block.getUniqueName(`${each}_head`);
const last = block.getUniqueName(`${each}_last`);
const expected = block.getUniqueName(`${each}_expected`);
const keep = block.getUniqueName(`${each}_keep`);
const mounts = block.getUniqueName(`${each}_mounts`);
const next_iteration = block.getUniqueName(`${each}_next_iteration`);
block.addVariable(blocks, '[]');
block.addVariable(lookup, `@blankObject()`); block.addVariable(lookup, `@blankObject()`);
block.addVariable(head);
block.addVariable(last);
if (this.children[0].isDomNode()) { if (this.children[0].isDomNode()) {
this.block.first = this.children[0].var; this.block.first = this.children[0].var;
@ -274,15 +267,9 @@ export default class EachBlock extends Node {
block.builders.init.addBlock(deindent` block.builders.init.addBlock(deindent`
for (var #i = 0; #i < ${each_block_value}.${length}; #i += 1) { for (var #i = 0; #i < ${each_block_value}.${length}; #i += 1) {
var ${key} = ${each_block_value}[#i].${this.key}; var ${key} = ${each_block_value}[#i].${this.key};
var ${iteration} = ${lookup}[${key}] = ${create_each_block}(#component, ${key}, @assign({}, state, { ${blocks}[#i] = ${lookup}[${key}] = ${create_each_block}(#component, ${key}, @assign({}, state, {
${this.contextProps.join(',\n')} ${this.contextProps.join(',\n')}
})); }));
if (${last}) ${last}.next = ${iteration};
${iteration}.last = ${last};
${last} = ${iteration};
if (#i === 0) ${head} = ${iteration};
} }
`); `);
@ -291,29 +278,17 @@ export default class EachBlock extends Node {
const anchorNode = parentNode ? 'null' : 'anchor'; const anchorNode = parentNode ? 'null' : 'anchor';
block.builders.create.addBlock(deindent` block.builders.create.addBlock(deindent`
var ${iteration} = ${head}; for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].c();
while (${iteration}) {
${iteration}.c();
${iteration} = ${iteration}.next;
}
`); `);
if (parentNodes) { if (parentNodes) {
block.builders.claim.addBlock(deindent` block.builders.claim.addBlock(deindent`
var ${iteration} = ${head}; for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].l(${parentNodes});
while (${iteration}) {
${iteration}.l(${parentNodes});
${iteration} = ${iteration}.next;
}
`); `);
} }
block.builders.mount.addBlock(deindent` block.builders.mount.addBlock(deindent`
var ${iteration} = ${head}; for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].${mountOrIntro}(${initialMountNode}, ${anchorNode});
while (${iteration}) {
${iteration}.${mountOrIntro}(${initialMountNode}, ${anchorNode});
${iteration} = ${iteration}.next;
}
`); `);
const dynamic = this.block.hasUpdateMethod; const dynamic = this.block.hasUpdateMethod;
@ -321,31 +296,21 @@ export default class EachBlock extends Node {
block.builders.update.addBlock(deindent` block.builders.update.addBlock(deindent`
var ${each_block_value} = ${snippet}; var ${each_block_value} = ${snippet};
@updateKeyedEach(#component, ${key}, changed, "${this.key}", ${dynamic}, ${each_block_value}, ${head}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", function(#i) { ${blocks} = @updateKeyedEach(${blocks}, #component, changed, "${this.key}", ${dynamic}, ${each_block_value}, ${lookup}, ${updateMountNode}, ${String(this.block.hasOutroMethod)}, ${create_each_block}, "${mountOrIntro}", function(#i) {
return @assign({}, state, { return @assign({}, state, {
${this.contextProps.join(',\n')} ${this.contextProps.join(',\n')}
}); });
}); });
${head} = ${lookup}[${each_block_value}[0] && ${each_block_value}[0].${this.key}];
`); `);
if (!parentNode) { if (!parentNode) {
block.builders.unmount.addBlock(deindent` block.builders.unmount.addBlock(deindent`
var ${iteration} = ${head}; for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].u();
while (${iteration}) {
${iteration}.u();
${iteration} = ${iteration}.next;
}
`); `);
} }
block.builders.destroy.addBlock(deindent` block.builders.destroy.addBlock(deindent`
var ${iteration} = ${head}; for (#i = 0; #i < ${blocks}.length; #i += 1) ${blocks}[#i].d();
while (${iteration}) {
${iteration}.d();
${iteration} = ${iteration}.next;
}
`); `);
} }

@ -1,80 +1,99 @@
import { assign } from './utils.js'; export function destroyBlock(block, lookup) {
block.u();
export function destroyIteration(iteration, lookup) { block.d();
var first = iteration.first lookup[block.key] = null;
if (first && first.parentNode) {
iteration.u();
}
iteration.d();
lookup[iteration.key] = null;
} }
export function outroAndDestroyIteration(iteration, lookup) { export function outroAndDestroyBlock(block, lookup) {
iteration.o(function() { block.o(function() {
iteration.u(); destroyBlock(block, lookup);
iteration.d();
lookup[iteration.key] = null;
}); });
} }
// TODO is it possible to avoid mounting iterations that are export function updateKeyedEach(old_blocks, component, changed, key_prop, dynamic, list, lookup, node, has_outro, create_each_block, intro_method, get_context) {
// already in the right place? var o = old_blocks.length;
export function updateKeyedEach(component, key, changed, key_prop, dynamic, list, head, lookup, node, has_outro, create_each_block, intro_method, get_context) { var n = list.length;
var keep = {};
var i = o;
var old_indexes = {};
while (i--) old_indexes[old_blocks[i].key] = i;
var i = list.length; var new_blocks = [];
var new_lookup = {};
var deltas = {};
var i = n;
while (i--) { while (i--) {
var key = list[i][key_prop]; var key = list[i][key_prop];
var iteration = lookup[key]; var block = lookup[key];
if (iteration) { if (!block) {
if (dynamic) iteration.p(changed, get_context(i)); block = create_each_block(component, key, get_context(i));
} else { block.c();
iteration = lookup[key] = create_each_block(component, key, get_context(i)); } else if (dynamic) {
iteration.c(); block.p(changed, get_context(i));
} }
lookup[key] = iteration; new_blocks[i] = new_lookup[key] = block;
keep[key] = 1;
if (key in old_indexes) deltas[key] = Math.abs(i - old_indexes[key]);
} }
var destroy = has_outro var next = null;
? outroAndDestroyIteration
: destroyIteration; var will_move = {};
var did_move = {};
iteration = head; var destroy = has_outro ? outroAndDestroyBlock : destroyBlock;
while (iteration) {
if (!keep[iteration.key]) destroy(iteration, lookup); function insert(block) {
iteration = iteration.next; block[intro_method](node, next && next.first);
next = lookup[block.key] = block;
n--;
} }
var next = null; while (o && n) {
var new_block = new_blocks[n - 1];
var old_block = old_blocks[o - 1];
var new_key = new_block.key;
var old_key = old_block.key;
i = list.length; if (new_block === old_block) {
while (i--) { // do nothing
key = list[i][key_prop]; next = new_block;
iteration = lookup[key]; o--;
n--;
}
else if (!new_lookup[old_key]) {
// remove old block
destroy(old_block, lookup);
o--;
}
else if (!lookup[new_key] || will_move[new_key]) {
insert(new_block);
}
var anchor; else if (did_move[old_key]) {
o--;
if (has_outro) { } else if (deltas[new_key] > deltas[old_key]) {
var next_key = next && next.key; did_move[new_key] = true;
var neighbour = iteration.next; insert(new_block);
var anchor_key;
while (neighbour && anchor_key != next_key && !keep[anchor_key]) {
anchor = neighbour && neighbour.first;
neighbour = neighbour.next;
anchor_key = neighbour && neighbour.key;
}
} else { } else {
anchor = next && next.first; will_move[old_key] = true;
o--;
} }
}
iteration[intro_method](node, anchor); while (o--) {
var old_block = old_blocks[o];
iteration.next = next; if (!new_lookup[old_block.key]) destroy(old_block, lookup);
if (next) next.last = iteration;
next = iteration;
} }
while (n) insert(new_blocks[n - 1]);
return new_blocks;
} }

@ -53,6 +53,8 @@ export default {
test('hdnkjougmrvftewsqpailcb'); test('hdnkjougmrvftewsqpailcb');
test('bidhfacge'); test('bidhfacge');
test('kgjnempcboaflidh'); test('kgjnempcboaflidh');
test('fekbijachgd');
test('kdmlgfbicheja');
// then, we party // then, we party
for (let i = 0; i < 100; i += 1) test(permute()); for (let i = 0; i < 100; i += 1) test(permute());

Loading…
Cancel
Save