From 2f0022f9123f179926ac1a3f8242a0fa2e9aacd4 Mon Sep 17 00:00:00 2001 From: Max Richter Date: Fri, 26 Apr 2024 17:57:32 +0200 Subject: [PATCH] feat: refactor performance collection --- app/src/lib/performance/Monitor.svelte | 37 ++- .../lib/performance/PerformanceViewer.svelte | 286 ++++++++++++------ app/src/lib/performance/store.ts | 40 +-- app/src/lib/result-viewer/Viewer.svelte | 55 ++-- app/src/lib/runtime-executor.ts | 18 +- app/src/routes/+page.svelte | 30 +- packages/types/src/components.ts | 13 +- 7 files changed, 306 insertions(+), 173 deletions(-) diff --git a/app/src/lib/performance/Monitor.svelte b/app/src/lib/performance/Monitor.svelte index 0ba767b..c942f0c 100644 --- a/app/src/lib/performance/Monitor.svelte +++ b/app/src/lib/performance/Monitor.svelte @@ -1,9 +1,32 @@
-

Runtime Execution

- {min}ms - {max}ms +

{title}

+ {Math.floor(min || 0)}{type} + {getMax(max)}{type} - + {#key points} + + {/key}
diff --git a/app/src/lib/performance/PerformanceViewer.svelte b/app/src/lib/performance/PerformanceViewer.svelte index 29364d0..e2248fb 100644 --- a/app/src/lib/performance/PerformanceViewer.svelte +++ b/app/src/lib/performance/PerformanceViewer.svelte @@ -1,168 +1,265 @@ -{#key data} - {#if browser} - +{#key $activeType && data} + {#if $activeType === "cache-hit"} + + {:else} + {/if}
- +
- {#if data.runs.length !== 0} + {#if data.length !== 0}

General

+ - - {#each getPerformanceData(!lastRunOnly) as [key, value]} + {#each getPerformanceData(!showAverage) as [key, value]} - {/each} - - - - - - +

Nodes

- {#each getNodePerformanceData(!lastRunOnly) as [key, value]} + + + + + {#each getNodePerformanceData(!showAverage) as [key, value]} - {/each} - {#if viewer.runs.length} -

Viewer

+

Viewer

+ + + + + + + + + {#each getViewerPerformanceData(!showAverage) as [key, value]} - - + + - - - - - {#each getViewerPerformanceData(!lastRunOnly) as [key, value]} - - - - - {/each} - {/if} + {/each}
- {Math.floor(getTotalPerformance(!lastRunOnly) * 100) / 100}msms + ($activeType = "total")} + > + total({Math.floor(1000 / getTotalPerformance(showAverage))}fps) total
- {Math.floor(value * 100) / 100}ms + {round(value)}ms + ($activeType = key)} + > {key}
{getCacheRatio(!lastRunOnly)} cache hit
{data.runs.length}{data.length} Samples
{getCacheRatio(!showAverage)}% ($activeType = "cache-hit")}>cache hits
- {Math.floor(value * 100) / 100}ms + {round(value)}ms + + ($activeType = key)} + > {key.split("/").slice(-1).join("/")}
{humanizeNumber(getLast("total-vertices"))}Vertices
{humanizeNumber(getLast("total-faces"))}Faces
{humanizeNumber(viewer.runs.at(-1)?.["total-vertices"])}Vertices + {round(value)}ms + ($activeType = key)} + > + {key.split("/").slice(-1).join("/")} +
{humanizeNumber(viewer.runs.at(-1)?.["total-faces"])}Faces
- {Math.floor(value * 100) / 100}ms - - {key.split("/").slice(-1).join("/")} -
{:else}

No runs available

@@ -185,6 +282,9 @@ padding-right: 10px; padding-block: 5px; } + td.active { + font-weight: bold; + } tr > td:nth-child(1) { text-align: right; } diff --git a/app/src/lib/performance/store.ts b/app/src/lib/performance/store.ts index 6dbbe48..63f9019 100644 --- a/app/src/lib/performance/store.ts +++ b/app/src/lib/performance/store.ts @@ -1,26 +1,24 @@ import { readable, type Readable } from "svelte/store"; -export type PerformanceData = { - total: Record; - runs: Record[]; -} +export type PerformanceData = Record[]; export interface PerformanceStore extends Readable { startRun(): void; stopRun(): void; addPoint(name: string, value?: number): void; + mergeData(data: PerformanceData[number]): void; get: () => PerformanceData; } export function createPerformanceStore(): PerformanceStore { - let data: PerformanceData = { total: {}, runs: [] }; + let data: PerformanceData = []; let currentRun: Record | undefined; let set: (v: PerformanceData) => void; - const { subscribe } = readable({ total: {}, runs: [] }, (_set) => { + const { subscribe } = readable([], (_set) => { set = _set; }); @@ -30,19 +28,8 @@ export function createPerformanceStore(): PerformanceStore { function stopRun() { if (currentRun) { - // Calculate total - Object.keys(currentRun).forEach((name) => { - if (!currentRun?.[name]?.length) return; - let runTotal = currentRun[name].reduce((a, b) => a + b, 0) / currentRun[name].length; - if (!data.total[name]) { - data.total[name] = runTotal; - } else { - data.total[name] = (data.total[name] + runTotal) / 2; - } - }); - - data.runs.push(currentRun); - data.runs = data.runs.slice(-100); + data.push(currentRun); + data = data.slice(-100); currentRun = undefined; if (set) set(data); } @@ -58,11 +45,26 @@ export function createPerformanceStore(): PerformanceStore { return data; } + function mergeData(newData: PerformanceData[number]) { + + let r = currentRun; + if (!r) return; + + Object.keys(newData).forEach((name) => { + if (name in r) { + r[name].push(...newData[name]); + } else { + r[name] = newData[name]; + } + }); + } + return { subscribe, startRun, stopRun, addPoint, + mergeData, get } } diff --git a/app/src/lib/result-viewer/Viewer.svelte b/app/src/lib/result-viewer/Viewer.svelte index 9faff43..824fde4 100644 --- a/app/src/lib/result-viewer/Viewer.svelte +++ b/app/src/lib/result-viewer/Viewer.svelte @@ -17,9 +17,6 @@ let geometries: BufferGeometry[] = []; let lines: Vector3[][] = []; - let totalVertices = 0; - let totalFaces = 0; - function fastArrayHash(arr: ArrayBuffer) { let ints = new Uint8Array(arr); @@ -41,14 +38,11 @@ geometry = new BufferGeometry(), ): BufferGeometry { // Extract data from the encoded array - let index = 0; - const geometryType = encodedData[index++]; + let index = 1; + // const geometryType = encodedData[index++]; const vertexCount = encodedData[index++]; const faceCount = encodedData[index++]; - totalVertices += vertexCount; - totalFaces += faceCount; - // Indices const indicesEnd = index + faceCount * 3; const indices = encodedData.subarray(index, indicesEnd); @@ -62,10 +56,24 @@ ); index = index + vertexCount * 3; let hash = fastArrayHash(vertices); + let posAttribute = geometry.getAttribute( + "position", + ) as BufferAttribute | null; + if (geometry.userData?.hash === hash) { return geometry; } + if (posAttribute && posAttribute.count === vertexCount) { + posAttribute.set(vertices, 0); + posAttribute.needsUpdate = true; + } else { + geometry.setAttribute( + "position", + new Float32BufferAttribute(vertices, 3), + ); + } + const normals = new Float32Array( encodedData.buffer, index * 4, @@ -81,20 +89,6 @@ geometry.setIndex([...indices]); } - let posAttribute = geometry.getAttribute( - "position", - ) as BufferAttribute | null; - - if (posAttribute && posAttribute.count === vertexCount) { - posAttribute.set(vertices, 0); - posAttribute.needsUpdate = true; - } else { - geometry.setAttribute( - "position", - new Float32BufferAttribute(vertices, 3), - ); - } - const normalsAttribute = geometry.getAttribute( "normal", ) as BufferAttribute | null; @@ -172,15 +166,14 @@ return positions; } - export const update = function updateGeometries(result: Int32Array) { + export let result: Int32Array; + $: result && updateGeometries(); + function updateGeometries() { let a = performance.now(); const inputs = parse_args(result); let b = performance.now(); perf?.addPoint("split-result", b - a); - totalVertices = 0; - totalFaces = 0; - if ($AppSettings.showStemLines) { a = performance.now(); lines = inputs @@ -194,11 +187,17 @@ perf?.addPoint("create-lines", b - a); } + let totalVertices = 0; + let totalFaces = 0; + a = performance.now(); geometries = inputs .map((input, i) => { if (input[0] === 1) { - return createGeometryFromEncodedData(input, geometries[i]); + let geo = createGeometryFromEncodedData(input); + totalVertices += geo.userData.vertexCount; + totalFaces += geo.userData.faceCount; + return geo; } }) .filter(Boolean) as BufferGeometry[]; @@ -206,7 +205,7 @@ perf?.addPoint("create-geometries", b - a); perf?.addPoint("total-vertices", totalVertices); perf?.addPoint("total-faces", totalFaces); - }; + } diff --git a/app/src/lib/runtime-executor.ts b/app/src/lib/runtime-executor.ts index 9a2dee2..a0e0ce2 100644 --- a/app/src/lib/runtime-executor.ts +++ b/app/src/lib/runtime-executor.ts @@ -45,7 +45,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { perf?: PerformanceStore; - constructor(private registry: NodeRegistry, private cache?: RuntimeCache) { } + constructor(private registry: NodeRegistry, private cache?: RuntimeCache) { } private async getNodeDefinitions(graph: Graph) { @@ -215,6 +215,7 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { let inputHash = `node-${node.id}-${fastHashArrayBuffer(encoded_inputs)}`; b = performance.now(); this.perf?.addPoint("hash-inputs", b - a); + let cachedValue = this.cache?.get(inputHash); if (cachedValue !== undefined) { log.log(`Using cached value for ${node_type.id || node.id}`); @@ -253,6 +254,10 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { this.perf?.stopRun(); + if (this.cache) { + this.cache.size = sortedNodes.length * 2; + } + return res as unknown as Int32Array; } @@ -261,15 +266,18 @@ export class MemoryRuntimeExecutor implements RuntimeExecutor { export class MemoryRuntimeCache implements RuntimeCache { - private cache: Record = {}; + private cache: [string, unknown][] = []; + size = 50; + get(key: string): T | undefined { - return this.cache[key] as T; + return this.cache.find(([k]) => k === key)?.[1] as T; } set(key: string, value: T): void { - this.cache[key] = value; + this.cache.push([key, value]); + this.cache = this.cache.slice(-this.size); } clear(): void { - this.cache = {}; + this.cache = []; } } diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte index 70891f9..b07fd65 100644 --- a/app/src/routes/+page.svelte +++ b/app/src/routes/+page.svelte @@ -20,18 +20,15 @@ import GraphSettings from "$lib/settings/panels/GraphSettings.svelte"; import NestedSettings from "$lib/settings/panels/NestedSettings.svelte"; import { createPerformanceStore } from "$lib/performance"; - import { type PerformanceData } from "$lib/performance/store"; const nodeRegistry = new RemoteNodeRegistry(""); const workerRuntime = new WorkerRuntimeExecutor(); - let performanceData: PerformanceData; - let viewerPerformance = createPerformanceStore(); + let performanceStore = createPerformanceStore(); - let res: Int32Array; let activeNode: Node | undefined; - let updateViewer: (arg: Int32Array) => void; + let graphResult: Int32Array; let graph = localStorage.getItem("graph") ? JSON.parse(localStorage.getItem("graph")!) @@ -69,23 +66,21 @@ try { let a = performance.now(); // res = await remoteRuntime.execute(_graph, _settings); - let res = await workerRuntime.execute(_graph, _settings); + graphResult = await workerRuntime.execute(_graph, _settings); let b = performance.now(); - updateViewer(res); let perfData = await workerRuntime.getPerformanceData(); - let lastRun = perfData.runs?.at(-1); + let lastRun = perfData.at(-1); if (lastRun) { - perfData.total["worker-transfer"] = b - a - lastRun.runtime[0]; lastRun["worker-transfer"] = [b - a - lastRun.runtime[0]]; + performanceStore.mergeData(lastRun); } - performanceData = perfData; isWorking = false; } catch (error) { console.log("errors", error); } - viewerPerformance.stopRun(); - viewerPerformance.startRun(); + performanceStore.stopRun(); + performanceStore.startRun(); if (unfinished) { let d = unfinished; @@ -115,8 +110,8 @@ @@ -168,11 +163,8 @@ hidden={!$AppSettings.showPerformancePanel} icon="i-tabler-brand-speedtest" > - {#if performanceData} - + {#if $performanceStore} + {/if} ) => Promise; } -export interface RuntimeCache { +export interface RuntimeCache { + + /** + * The maximum number of items that can be stored in the cache + * @remarks When the cache size exceeds this value, the oldest items should be removed + */ + size: number; + /** * Get the value for the given key * @param key - The key to get the value for * @returns The value for the given key, or undefined if no such value exists */ - get: (key: string) => unknown | undefined; + get: (key: string) => T | undefined; /** * Set the value for the given key * @param key - The key to set the value for * @param value - The value to set */ - set: (key: string, value: unknown) => void; + set: (key: string, value: T) => void; /** * Clear the cache */