-
Notifications
You must be signed in to change notification settings - Fork 2
Add focused changeset scanning benchmark #354
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
Changes from all commits
35b1493
1601b6d
fdd97d3
0ee398f
d14caf8
09f4a4f
a52aeff
61c8259
8f74ff7
ec1e09f
508c806
fa0006f
29e54c0
fa0353c
ca5af82
0f23183
4076e5c
d04d3db
d337a15
be05704
6d539db
479dc31
96c981d
33bf668
1ad77a7
d86ec50
4b98a74
b7b398e
6ccc55b
7686109
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <ECSchema schemaName="QuickPerfScan" alias="qps" version="1.0.0" | ||
| xmlns="http://www.bentley.com/schemas/Bentley.ECXML.3.2"> | ||
| <ECSchemaReference name="BisCore" version="01.00.00" alias="bis"/> | ||
|
|
||
| <ECEntityClass typeName="ScanAspect" modifier="Sealed"> | ||
| <BaseClass>bis:ElementMultiAspect</BaseClass> | ||
| <ECProperty propertyName="Payload" typeName="string"/> | ||
| <ECProperty propertyName="Sequence" typeName="int"/> | ||
| </ECEntityClass> | ||
| </ECSchema> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,15 +4,14 @@ | |
| *--------------------------------------------------------------------------------------------*/ | ||
|
|
||
| import * as fs from "node:fs"; | ||
| import { createRequire } from "node:module"; | ||
| import * as path from "node:path"; | ||
| import { | ||
| canonicalSha256, | ||
| FixtureDescriptor, | ||
| } from "../fixtures/FixtureDescriptor.js"; | ||
| import { quickPath, quickRootDirectory } from "../support/paths.js"; | ||
| import { resolvedVersions } from "../support/versions.js"; | ||
|
|
||
| const localRequire = createRequire(import.meta.url); | ||
| const scale = 25; | ||
| const distribution = { | ||
| base: { | ||
|
|
@@ -42,18 +41,7 @@ const distribution = { | |
| }, | ||
| } as const; | ||
|
|
||
| function packageVersion(packageName: string): string { | ||
| const packageJson = JSON.parse( | ||
| fs.readFileSync(localRequire.resolve(`${packageName}/package.json`), "utf8") | ||
| ) as { version: string }; | ||
| return packageJson.version; | ||
| } | ||
|
|
||
| const generator = { | ||
| coreBackend: packageVersion("@itwin/core-backend"), | ||
| node: process.version, | ||
| transformer: packageVersion("@itwin/imodel-transformer"), | ||
| }; | ||
| const generator = resolvedVersions(); | ||
|
|
||
| const recipeIdentity = (topology: string) => ({ | ||
| schema: "QuickPerf.01.00.00", | ||
|
|
@@ -129,12 +117,108 @@ export const balancedIncrementalSourceOnlyDescriptor: FixtureDescriptor = { | |
| recipeHash: canonicalSha256(recipeIdentity("source-only")), | ||
| }; | ||
|
|
||
| /** | ||
| * Update-heavy multi-changeset source, sized so that scanning it dominates timer noise. | ||
| * | ||
| * Region sizes are derived from `base.elements` by `scanRegionSizes`; see | ||
| * `fixtures/recipes/updateHeavyScan` for what each region proves. | ||
| * | ||
| * Calibrated at 3,520 elements x 20 changesets: the scan measures ~3.46 s with a coefficient of | ||
| * variation of 1.0% over 8 samples, against ~51 ms of per-sample copy, verification and teardown. | ||
| * Scan cost is linear in changed rows at roughly 49 ms per (1,000 elements x changeset), so the | ||
| * shape is a single `scanScale` knob. | ||
| */ | ||
| const scanScale = 16; | ||
| const scanDistribution = { | ||
| base: { | ||
| // Region A (updated throughout) plus region B (updated, then deleted last). | ||
| aspects: 220 * scanScale, | ||
| elements: 220 * scanScale, | ||
| geometricElements: 0, | ||
| relationships: 40 * scanScale, | ||
| }, | ||
| operations: { | ||
| elements: { | ||
| // Regions C and D, both inserted in the first changeset. | ||
| inserts: 30 * scanScale, | ||
| // Every seeded element is updated, plus region C after its insert. | ||
| updates: 240 * scanScale, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🤖 Nambot (powered by gpt-5.6-sol) — These values count distinct IDs touched, not update operations performed. At scale 16 over 20 changesets, the recipe issues about 76,160 element and 70,080 aspect update calls, while emitted samples label 3,840 and 3,520 as Nambot 🤖 (powered by gpt-5.6-sol) |
||
| // Region B at the end, region D at the end. | ||
| deletes: 30 * scanScale, | ||
| }, | ||
| aspects: { | ||
| inserts: 20 * scanScale, | ||
| updates: 220 * scanScale, | ||
| // Region B's owned aspects, cascade-deleted with their elements. | ||
| deletes: 20 * scanScale, | ||
| }, | ||
| relationships: { | ||
| inserts: 10 * scanScale, | ||
| updates: 10 * scanScale, | ||
| deletes: 10 * scanScale, | ||
| }, | ||
| geometryUpdates: 0, | ||
| sourceChangesets: 20, | ||
| }, | ||
| } as const; | ||
|
|
||
| const scanRecipeIdentity = { | ||
| schema: "QuickPerfScan.01.00.00", | ||
| seed: 328, | ||
| topology: "source-only", | ||
| distribution: scanDistribution, | ||
| inputs: { | ||
| recipe: fs.readFileSync( | ||
| quickPath("src", "fixtures", "recipes", "updateHeavyScan.ts"), | ||
| "utf8" | ||
| ), | ||
| schema: fs.readFileSync( | ||
| quickPath("assets", "schemas", "QuickPerfScan.ecschema.xml"), | ||
| "utf8" | ||
| ), | ||
| lockfile: fs.readFileSync( | ||
| path.resolve( | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. eww. |
||
| quickRootDirectory, | ||
| "..", | ||
| "..", | ||
| "..", | ||
| "..", | ||
| "pnpm-lock.yaml" | ||
| ), | ||
| "utf8" | ||
| ), | ||
| }, | ||
| versions: generator, | ||
| }; | ||
|
|
||
| export const updateHeavyScanDescriptor: FixtureDescriptor = { | ||
| id: "update-heavy-scan", | ||
| version: 1, | ||
| label: "update-heavy scan", | ||
| scenarioClaims: [ | ||
| "changeset scanning", | ||
| "changed-instance squashing", | ||
| "aspect lifecycle", | ||
| "relationship lifecycle", | ||
| ], | ||
| layout: { | ||
| kind: "reconstructed", | ||
| topology: "source-only", | ||
| recipe: "update-heavy-scan", | ||
| seed: 328, | ||
| }, | ||
| distribution: scanDistribution, | ||
| generator, | ||
| recipeHash: canonicalSha256(scanRecipeIdentity), | ||
| }; | ||
|
|
||
| const fixtures = new Map<string, FixtureDescriptor>([ | ||
| [balancedIncrementalDescriptor.id, balancedIncrementalDescriptor], | ||
| [ | ||
| balancedIncrementalSourceOnlyDescriptor.id, | ||
| balancedIncrementalSourceOnlyDescriptor, | ||
| ], | ||
| [updateHeavyScanDescriptor.id, updateHeavyScanDescriptor], | ||
| ]); | ||
|
|
||
| export function registerFixtureDescriptor(descriptor: FixtureDescriptor): void { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should also document in a markdown file somewhere what each test schema means.... cuz I see this and I question why its so tiny