Skip to content

Commit f756110

Browse files
authored
perf: optimize combo combined layout performance (#226)
* perf: config perf env, add combo-combine test case * perf: add perf test baseline * perf: optimize combo-combined layout, generate perf report * refactor: fix cr issue
1 parent bfb15db commit f756110

File tree

7 files changed

+195
-8
lines changed

7 files changed

+195
-8
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"@antv/g-plugin-3d": "^1.9.34",
4545
"@antv/g-plugin-control": "^1.9.22",
4646
"@antv/g-webgl": "^1.9.37",
47-
"@antv/graphlib": "^2.0.2",
47+
"@antv/graphlib": "^2.0.3",
4848
"@babel/core": "^7.24.0",
4949
"@babel/polyfill": "^7.12.1",
5050
"@babel/preset-env": "^7.24.0",
@@ -67,6 +67,7 @@
6767
"graphology-layout-forceatlas2": "^0.10.1",
6868
"graphology-types": "^0.24.7",
6969
"husky": "^7.0.4",
70+
"iperf": "0.1.0-beta.13",
7071
"jest": "^29.7.0",
7172
"jest-environment-jsdom": "29.5.0",
7273
"lil-gui": "^0.16.0",

packages/layout/src/comboCombined.ts

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import type {
1212
Node,
1313
OutNode,
1414
} from './types';
15+
import { isLayoutWithIterations } from './types';
1516
import { getLayoutBBox, graphTreeDfs, isArray } from './util';
1617
import { handleSingleNodeGraph } from './util/common';
1718

@@ -116,7 +117,7 @@ export class ComboCombinedLayout implements Layout<ComboCombinedLayoutOptions> {
116117
});
117118

118119
// each one in comboNodes is a combo contains the size and child nodes
119-
// comboNodes ncludes the node who has no parent combo
120+
// comboNodes includes the node who has no parent combo
120121
const comboNodes: Map<ID, Node> = new Map();
121122
// the inner layouts, the result positions are stored in comboNodes and their child nodes
122123
const innerGraphLayoutPromises = this.getInnerGraphs(
@@ -214,7 +215,8 @@ export class ComboCombinedLayout implements Layout<ComboCombinedLayoutOptions> {
214215
: new ConcentricLayout();
215216
await outerLayoutPreset.assign(outerLayoutGraph);
216217
}
217-
outerPositions = await outerLayout.execute(outerLayoutGraph, {
218+
219+
const options = {
218220
center,
219221
kg: 5,
220222
preventOverlap: true,
@@ -232,7 +234,13 @@ export class ComboCombinedLayout implements Layout<ComboCombinedLayoutOptions> {
232234
},
233235
}
234236
: {}),
235-
});
237+
};
238+
239+
outerPositions = await executeLayout(
240+
outerLayout,
241+
outerLayoutGraph,
242+
options,
243+
);
236244
}
237245

238246
// move the combos and their child nodes
@@ -400,7 +408,7 @@ export class ComboCombinedLayout implements Layout<ComboCombinedLayoutOptions> {
400408
},
401409
});
402410

403-
let start = Promise.resolve();
411+
let start: Promise<any> = Promise.resolve();
404412

405413
// Regard the child nodes in one combo as a graph, and layout them from bottom to top
406414
graphTreeDfs(
@@ -479,14 +487,16 @@ export class ComboCombinedLayout implements Layout<ComboCombinedLayoutOptions> {
479487
// innerGraphLayout.assign(innerGraphCore, innerLayoutOptions);
480488
start = start.then(async () => {
481489
const innerGraphCore = new GraphCore(innerGraphData);
482-
const innerLayout = await innerGraphLayout.assign(
490+
await executeLayout(
491+
innerGraphLayout,
483492
innerGraphCore,
484493
innerLayoutOptions,
494+
true,
485495
);
486496
const { minX, minY, maxX, maxY } = getLayoutBBox(
487497
innerLayoutNodes as OutNode[],
488498
);
489-
// move the innerGraph to [0, 0], for later controled by parent layout
499+
// move the innerGraph to [0, 0], for later controlled by parent layout
490500
const center = { x: (maxX + minX) / 2, y: (maxY + minY) / 2 };
491501
innerGraphData.nodes.forEach((node) => {
492502
node.data.x -= center.x;
@@ -499,7 +509,6 @@ export class ComboCombinedLayout implements Layout<ComboCombinedLayoutOptions> {
499509

500510
comboNodes.get(treeNode.id).data.size = size;
501511
comboNodes.get(treeNode.id).data.nodes = innerLayoutNodes;
502-
return innerLayout;
503512
});
504513
}
505514
return true;
@@ -512,3 +521,18 @@ export class ComboCombinedLayout implements Layout<ComboCombinedLayoutOptions> {
512521
return innerLayoutPromises;
513522
}
514523
}
524+
525+
async function executeLayout(
526+
layout: Layout<any>,
527+
graph: Graph,
528+
options: Record<string, any>,
529+
assign?: boolean,
530+
) {
531+
if (isLayoutWithIterations(layout)) {
532+
layout.execute(graph, options);
533+
layout.stop();
534+
return layout.tick(options.iterations ?? 300);
535+
}
536+
if (assign) return await layout.assign(graph, options);
537+
return await layout.execute(graph, options);
538+
}

perf.config.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineConfig } from 'iperf';
2+
import path from 'path';
3+
4+
export default defineConfig({
5+
perf: {
6+
report: {},
7+
},
8+
resolve: {
9+
alias: {
10+
'@antv/layout': path.resolve(__dirname, './packages/layout/src'),
11+
},
12+
},
13+
});

perf/combo-combined.perf.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Graph } from '@antv/graphlib';
2+
import type { LayoutMapping } from '@antv/layout';
3+
import { ComboCombinedLayout } from '@antv/layout';
4+
import type { Test } from 'iperf';
5+
6+
export const comboCombined: Test = async ({ perf, container }) => {
7+
const data = await fetch(
8+
'https://assets.antv.antgroup.com/g6/combo.json',
9+
).then((res) => res.json());
10+
11+
const nodes = [
12+
...data.nodes.map((node: any) => ({
13+
...node,
14+
data: { _isCombo: false },
15+
})),
16+
...data.combos.map((combo: any) => ({
17+
...combo,
18+
data: { _isCombo: true },
19+
})),
20+
];
21+
const edges = data.edges.map((edge: any) => ({
22+
...edge,
23+
id: `${edge.source}-${edge.target}`,
24+
}));
25+
26+
const graph = new Graph({ nodes, edges });
27+
graph.attachTreeStructure('combo');
28+
data.nodes.forEach((node: any) => {
29+
if (node.combo) graph.setParent(node.id, node.combo, 'combo');
30+
});
31+
32+
const layout = new ComboCombinedLayout({});
33+
34+
let result: LayoutMapping;
35+
36+
await perf.evaluate('combo combined', async () => {
37+
result = await layout.execute(graph);
38+
});
39+
40+
const canvas = document.createElement('canvas');
41+
container.appendChild(canvas);
42+
const ctx = canvas.getContext('2d');
43+
const [width, height] = [500, 500];
44+
canvas.width = width;
45+
canvas.height = height;
46+
47+
result.nodes.forEach((node) => {
48+
const {
49+
data: { x, y },
50+
} = node;
51+
ctx.beginPath();
52+
ctx.arc(x + 200, y, 5, 0, Math.PI * 2);
53+
ctx.fill();
54+
});
55+
};
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"version": "1.0",
3+
"device": {
4+
"os": {
5+
"arch": "arm64",
6+
"distro": "macOS",
7+
"serial": "9821ed36011eee5abf6c71d6fc2c03fb4bf4655e674c56b7f50e2560cb6e924a"
8+
},
9+
"cpu": {
10+
"manufacturer": "Apple",
11+
"brand": "M1 Pro",
12+
"speed": 2.4,
13+
"cores": 10
14+
},
15+
"memory": {
16+
"total": 16384,
17+
"free": 32.96875
18+
},
19+
"gpu": {
20+
"vendor": "Apple",
21+
"model": "Apple M1 Pro",
22+
"cores": "16"
23+
}
24+
},
25+
"repo": "a7ec123a4112a8995c869daf084f51a7ffee8f94",
26+
"client": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/128.0.6613.18 Safari/537.36",
27+
"reports": {
28+
"comboCombined": {
29+
"time": [
30+
{
31+
"min": 529.2999999970198,
32+
"max": 539.7000000029802,
33+
"median": 530.2000000029802,
34+
"avg": 530.9250000026077,
35+
"variance": 4.824374999050051,
36+
"reliable": true,
37+
"key": "combo combined"
38+
}
39+
],
40+
"status": "passed"
41+
}
42+
}
43+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{
2+
"version": "1.0",
3+
"device": {
4+
"os": {
5+
"arch": "arm64",
6+
"distro": "macOS",
7+
"serial": "9821ed36011eee5abf6c71d6fc2c03fb4bf4655e674c56b7f50e2560cb6e924a"
8+
},
9+
"cpu": {
10+
"manufacturer": "Apple",
11+
"brand": "M1 Pro",
12+
"speed": 2.4,
13+
"cores": 10
14+
},
15+
"memory": {
16+
"total": 16384,
17+
"free": 121.796875
18+
},
19+
"gpu": {
20+
"vendor": "Apple",
21+
"model": "Apple M1 Pro",
22+
"cores": "16"
23+
}
24+
},
25+
"repo": "ee6714c42b02ba09912e17bc405d2607caf25e69",
26+
"client": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/128.0.6613.18 Safari/537.36",
27+
"reports": {
28+
"comboCombined": {
29+
"time": [
30+
{
31+
"min": 2.800000011920929,
32+
"max": 19,
33+
"median": 3.6000000089406967,
34+
"avg": 4.500000001862645,
35+
"variance": 6.094999999850989,
36+
"reliable": false,
37+
"key": "combo combined"
38+
}
39+
],
40+
"status": "passed"
41+
}
42+
}
43+
}

perf/tsconfig.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"extends": "../tsconfig.json",
3+
"compilerOptions": {
4+
"paths": {
5+
"@antv/layout": ["../packages/layout/src/index.ts"]
6+
}
7+
},
8+
}

0 commit comments

Comments
 (0)