forked from dapplion/benchmark
-
Notifications
You must be signed in to change notification settings - Fork 2
feat: dynamic sampling time #27
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
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import Debug from "debug"; | ||
| import {BenchmarkOpts, ConvergenceCheckFn} from "../../types.js"; | ||
| import {calcMean, calcMedian, calcVariance, filterOutliers, OutlierSensitivity, sortData} from "../../utils/math.js"; | ||
|
|
||
| const debug = Debug("@chainsafe/benchmark/convergence"); | ||
|
|
||
| export function createCVConvergenceCriteria( | ||
| startMs: number, | ||
| {maxMs, maxRuns, minRuns, minMs, convergeFactor}: Required<BenchmarkOpts> | ||
| ): ConvergenceCheckFn { | ||
| let lastConvergenceSample = startMs; | ||
| let sampleEveryMs = Math.min(100, minMs); | ||
| const minSamples = Math.max(5, minRuns); | ||
| const maxSamplesForCV = 1000; | ||
|
|
||
| return function canTerminate(runIdx: number, totalNs: bigint, runsNs: bigint[]): boolean { | ||
| const currentMs = Date.now(); | ||
| const elapsedMs = currentMs - startMs; | ||
| const timeSinceLastCheck = currentMs - lastConvergenceSample; | ||
| const mustStop = elapsedMs >= maxMs || runIdx >= maxRuns; | ||
| const mayStop = elapsedMs >= minMs && runIdx >= minRuns && runIdx >= minSamples; | ||
|
|
||
| debug( | ||
| "trying to converge benchmark via cv mustStop=%o, mayStop=%o, timeSinceLastCheck=%o", | ||
| mustStop, | ||
| mayStop, | ||
| timeSinceLastCheck | ||
| ); | ||
|
|
||
| // Must stop | ||
| if (mustStop) return true; | ||
| if (!mayStop) return false; | ||
|
|
||
| // Only attempt to compute the confidence interval every sampleEveryMs | ||
| if (timeSinceLastCheck < sampleEveryMs) { | ||
| // If last call was wade 50% faster than the sampleEveryMs, let's reduce the sample interval to 10% | ||
| if (sampleEveryMs > 2 && sampleEveryMs / 2 > timeSinceLastCheck) { | ||
| sampleEveryMs -= sampleEveryMs * 0.1; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| if (timeSinceLastCheck < sampleEveryMs) return false; | ||
| lastConvergenceSample = currentMs; | ||
| // For all statistical calculations we don't want to loose the precision so have to convert to numbers first | ||
| const samples = filterOutliers(sortData(runsNs.map((n) => Number(n))), true, OutlierSensitivity.Mild); | ||
|
|
||
| // If CV does not stabilize we fallback to the median approach | ||
| if (runsNs.length > maxSamplesForCV) { | ||
| const median = calcMedian(samples, true); | ||
| const mean = calcMean(samples); | ||
| const medianFactor = Math.abs(Number(mean - median)) / Number(median); | ||
|
|
||
| debug("checking convergence median convergeFactor=%o, medianFactor=%o", convergeFactor, medianFactor); | ||
|
|
||
| return medianFactor < convergeFactor; | ||
| } | ||
|
|
||
| const mean = calcMean(samples); | ||
| const variance = calcVariance(samples, mean); | ||
| const cv = Math.sqrt(variance) / mean; | ||
|
|
||
| debug( | ||
| "checking convergence via cv convergeFactor=%o, cv=%o, samples=%o, outliers=%o", | ||
| convergeFactor, | ||
| cv, | ||
| runsNs.length, | ||
| runsNs.length - samples.length | ||
| ); | ||
|
|
||
| return cv < convergeFactor; | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import Debug from "debug"; | ||
| import {BenchmarkOpts, ConvergenceCheckFn} from "../../types.js"; | ||
|
|
||
| const debug = Debug("@chainsafe/benchmark/convergence"); | ||
|
|
||
| export function createLinearConvergenceCriteria( | ||
| startMs: number, | ||
| {maxMs, maxRuns, minRuns, minMs, convergeFactor}: Required<BenchmarkOpts> | ||
| ): ConvergenceCheckFn { | ||
| let prevAvg0 = 0; | ||
| let prevAvg1 = 0; | ||
| let lastConvergenceSample = startMs; | ||
| const sampleEveryMs = 100; | ||
|
|
||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| return function canTerminate(runIdx: number, totalNs: bigint, _runNs: bigint[]): boolean { | ||
| const currentMs = Date.now(); | ||
| const elapsedMs = currentMs - startMs; | ||
| const timeSinceLastCheck = currentMs - lastConvergenceSample; | ||
| const mustStop = elapsedMs >= maxMs || runIdx >= maxRuns; | ||
| const mayStop = elapsedMs >= minMs && runIdx >= minRuns; | ||
|
|
||
| debug( | ||
| "trying to converge benchmark via confidence-interval mustStop=%o, mayStop=%o, timeSinceLastCheck=%o", | ||
| mustStop, | ||
| mayStop, | ||
| timeSinceLastCheck | ||
| ); | ||
|
|
||
| // Must stop | ||
| if (mustStop) return true; | ||
|
|
||
| // When is a good time to stop a benchmark? A naive answer is after N milliseconds or M runs. | ||
| // This code aims to stop the benchmark when the average fn run time has converged at a value | ||
| // within a given convergence factor. To prevent doing expensive math to often for fast fn, | ||
| // it only takes samples every `sampleEveryMs`. It stores two past values to be able to compute | ||
| // a very rough linear and quadratic convergence.a | ||
| if (timeSinceLastCheck <= sampleEveryMs) return false; | ||
|
|
||
| lastConvergenceSample = currentMs; | ||
| const avg = Number(totalNs / BigInt(runIdx)); | ||
|
|
||
| // Compute convergence (1st order + 2nd order) | ||
| const a = prevAvg0; | ||
| const b = prevAvg1; | ||
| const c = avg; | ||
|
|
||
| if (mayStop) { | ||
| // Approx linear convergence | ||
| const convergence1 = Math.abs(c - a); | ||
| // Approx quadratic convergence | ||
| const convergence2 = Math.abs(b - (a + c) / 2); | ||
| // Take the greater of both to enforce linear and quadratic are below convergeFactor | ||
| const convergence = Math.max(convergence1, convergence2) / a; | ||
|
|
||
| debug("checking convergence convergeFactor=%o, convergence=%o", convergeFactor, convergence); | ||
|
|
||
| // Okay to stop + has converged, stop now | ||
| if (convergence < convergeFactor) return true; | ||
| } | ||
|
|
||
| prevAvg0 = prevAvg1; | ||
| prevAvg1 = avg; | ||
| return false; | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
This is the dynamic logic to reduce the sampling interval.