From c7ba22b7e881dc5f0c7f99dfa5d67390bc46fdf6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Fri, 25 Aug 2023 12:25:06 +0200 Subject: [PATCH] perf: switch to SMA and SMM in performance statistics MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Jérôme Benoit --- src/performance/PerformanceStatistics.ts | 16 +++++---- src/utils/CircularArray.ts | 2 +- src/utils/Constants.ts | 2 +- src/utils/StatisticUtils.ts | 43 +++++++++++++++++------- src/utils/index.ts | 2 +- test/utils/CircularArray.test.ts | 4 +-- test/utils/StatisticUtils.test.ts | 9 ++++- 7 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/performance/PerformanceStatistics.ts b/src/performance/PerformanceStatistics.ts index c5748922..281075c7 100644 --- a/src/performance/PerformanceStatistics.ts +++ b/src/performance/PerformanceStatistics.ts @@ -21,6 +21,7 @@ import { Configuration, Constants, JSONStringifyWithMapSupport, + average, buildPerformanceStatisticsMessage, extractTimeSeriesValues, formatDurationSeconds, @@ -230,9 +231,6 @@ export class PerformanceStatistics { ); this.statistics.statisticsData.get(entryName)!.totalTimeMeasurement = (this.statistics.statisticsData.get(entryName)?.totalTimeMeasurement ?? 0) + entry.duration; - this.statistics.statisticsData.get(entryName)!.avgTimeMeasurement = - this.statistics.statisticsData.get(entryName)!.totalTimeMeasurement! / - this.statistics.statisticsData.get(entryName)!.timeMeasurementCount!; this.statistics.statisticsData.get(entryName)?.measurementTimeSeries instanceof CircularArray ? this.statistics.statisticsData .get(entryName) @@ -242,22 +240,28 @@ export class PerformanceStatistics { timestamp: entry.startTime, value: entry.duration, })); + this.statistics.statisticsData.get(entryName)!.avgTimeMeasurement = average( + extractTimeSeriesValues( + this.statistics.statisticsData.get(entryName)!.measurementTimeSeries!, + ), + ); this.statistics.statisticsData.get(entryName)!.medTimeMeasurement = median( extractTimeSeriesValues( - this.statistics.statisticsData.get(entryName)!.measurementTimeSeries as TimestampedData[], + this.statistics.statisticsData.get(entryName)!.measurementTimeSeries!, ), ); this.statistics.statisticsData.get(entryName)!.ninetyFiveThPercentileTimeMeasurement = nthPercentile( extractTimeSeriesValues( - this.statistics.statisticsData.get(entryName)!.measurementTimeSeries as TimestampedData[], + this.statistics.statisticsData.get(entryName)!.measurementTimeSeries!, ), 95, ); this.statistics.statisticsData.get(entryName)!.stdDevTimeMeasurement = stdDeviation( extractTimeSeriesValues( - this.statistics.statisticsData.get(entryName)!.measurementTimeSeries as TimestampedData[], + this.statistics.statisticsData.get(entryName)!.measurementTimeSeries!, ), + this.statistics.statisticsData.get(entryName)!.avgTimeMeasurement, ); if ( Configuration.getConfigurationSection( diff --git a/src/utils/CircularArray.ts b/src/utils/CircularArray.ts index bd768673..4fafe528 100644 --- a/src/utils/CircularArray.ts +++ b/src/utils/CircularArray.ts @@ -1,6 +1,6 @@ // Copyright Jerome Benoit. 2021-2023. All Rights Reserved. -const DEFAULT_CIRCULAR_ARRAY_SIZE = 1024; +export const DEFAULT_CIRCULAR_ARRAY_SIZE = 1024; /** * Array with a maximum length and shifting items when full. diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts index 8ab45e63..60379320 100644 --- a/src/utils/Constants.ts +++ b/src/utils/Constants.ts @@ -24,7 +24,7 @@ export class Constants { static readonly SEMVER_PATTERN = '^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$'; - static readonly DEFAULT_CIRCULAR_BUFFER_CAPACITY = 1024; + static readonly DEFAULT_CIRCULAR_BUFFER_CAPACITY = 4096; static readonly DEFAULT_HASH_ALGORITHM = 'sha384'; diff --git a/src/utils/StatisticUtils.ts b/src/utils/StatisticUtils.ts index a2d906eb..3f587caa 100644 --- a/src/utils/StatisticUtils.ts +++ b/src/utils/StatisticUtils.ts @@ -1,5 +1,29 @@ import { isEmptyArray, isNullOrUndefined } from './Utils'; +/** + * Computes the average of the given data set. + * + * @param dataSet - Data set. + * @returns The average of the given data set. + * @internal + */ +export const average = (dataSet: number[]): number => { + if (Array.isArray(dataSet) && dataSet.length === 0) { + return 0; + } + if (Array.isArray(dataSet) && dataSet.length === 1) { + return dataSet[0]; + } + return dataSet.reduce((accumulator, nb) => accumulator + nb, 0) / dataSet.length; +}; + +/** + * Computes the median of the given data set. + * + * @param dataSet - Data set. + * @returns The median of the given data set. + * @internal + */ export const median = (dataSet: number[]): number => { if (isEmptyArray(dataSet)) { return 0; @@ -40,16 +64,11 @@ export const nthPercentile = (dataSet: number[], percentile: number): number => return sortedDataSet[percentileIndexInteger]; }; -export const stdDeviation = (dataSet: number[]): number => { - let totalDataSet = 0; - for (const data of dataSet) { - totalDataSet += data; - } - const dataSetMean = totalDataSet / dataSet.length; - let totalGeometricDeviation = 0; - for (const data of dataSet) { - const deviation = data - dataSetMean; - totalGeometricDeviation += deviation * deviation; - } - return Math.sqrt(totalGeometricDeviation / dataSet.length); +export const stdDeviation = (dataSet: number[], dataSetAverage?: number): number => { + dataSetAverage = dataSetAverage ?? average(dataSet); + const geometricDeviation = dataSet.reduce((accumulator, nb) => { + const deviation = nb - dataSetAverage!; + return accumulator + deviation * deviation; + }, 0); + return Math.sqrt(geometricDeviation / dataSet.length); }; diff --git a/src/utils/index.ts b/src/utils/index.ts index 1cdd45ee..5915ae77 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -55,5 +55,5 @@ export { sleep, validateUUID, } from './Utils'; -export { median, nthPercentile, stdDeviation } from './StatisticUtils'; +export { average, median, nthPercentile, stdDeviation } from './StatisticUtils'; export { logger } from './Logger'; diff --git a/test/utils/CircularArray.test.ts b/test/utils/CircularArray.test.ts index 18910d8c..34839dcb 100644 --- a/test/utils/CircularArray.test.ts +++ b/test/utils/CircularArray.test.ts @@ -1,6 +1,6 @@ import { expect } from 'expect'; -import { CircularArray } from '../../src/utils/CircularArray'; +import { CircularArray, DEFAULT_CIRCULAR_ARRAY_SIZE } from '../../src/utils/CircularArray'; describe('CircularArray test suite', () => { it('Verify that circular array can be instantiated', () => { @@ -10,7 +10,7 @@ describe('CircularArray test suite', () => { it('Verify circular array default size at instance creation', () => { const circularArray = new CircularArray(); - expect(circularArray.size).toBe(1024); + expect(circularArray.size).toBe(DEFAULT_CIRCULAR_ARRAY_SIZE); }); it('Verify that circular array size can be set at instance creation', () => { diff --git a/test/utils/StatisticUtils.test.ts b/test/utils/StatisticUtils.test.ts index 2910c32d..6fb30488 100644 --- a/test/utils/StatisticUtils.test.ts +++ b/test/utils/StatisticUtils.test.ts @@ -1,8 +1,15 @@ import { expect } from 'expect'; -import { median, nthPercentile, stdDeviation } from '../../src/utils/StatisticUtils'; +import { average, median, nthPercentile, stdDeviation } from '../../src/utils/StatisticUtils'; describe('StatisticUtils test suite', () => { + it('Verify average()', () => { + expect(average([])).toBe(0); + expect(average([0.08])).toBe(0.08); + expect(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02, 5.03])).toBe(3.1642857142857146); + expect(average([0.25, 4.75, 3.05, 6.04, 1.01, 2.02])).toBe(2.8533333333333335); + }); + it('Verify median()', () => { expect(median([])).toBe(0); expect(median([0.08])).toBe(0.08); -- 2.34.1