jsPerf.app is an online JavaScript performance benchmark test runner & jsperf.com mirror. It is a complete rewrite in homage to the once excellent jsperf.com now with hopefully a more modern & maintainable codebase.
jsperf.com URLs are mirrored at the same path, e.g:
https://jsperf.com/negative-modulo/2
Can be accessed at:
https://jsperf.app/negative-modulo/2
const asc = (a, b) => a - b;
const alignSeries = (series) => {
const timestampsSet = new Set([...series.flatMap((item) => item.map((v) => v[0] / 1000))]);
const timestamps = Array.from(timestampsSet).sort(asc);
const seriesMaps = series.map((s) => new Map(s));
const alignedSeries = series.map(() => new Array(timestamps.length).fill(null));
// the scaleFactors array stores a scaling factor for each series
// to ensure that extremely large values (> Number.MAX_SAFE_INTEGER)
// are scaled down to avoid potential issues with js number precision limits
const scaleFactors = [];
for (let j = 0; j < series.length; j++) {
let maxSeriesValue = Number.NEGATIVE_INFINITY;
for (let i = 0; i < timestamps.length; i++) {
const ts = timestamps[i];
const v = seriesMaps[j].get(ts * 1000);
alignedSeries[j][i] = v ?? null;
maxSeriesValue = v && maxSeriesValue < v ? v : maxSeriesValue;
}
scaleFactors.push(maxSeriesValue > Number.MAX_SAFE_INTEGER ? 1e12 : 1);
}
return {
scaleFactors,
series: alignedSeries,
timestamps,
};
};
const alignSeriesAi = (series) => {
// Early return for empty input
if (!series.length) {
return { scaleFactors: [], series: [], timestamps: [] };
}
// Single pass to collect timestamps and find max values
const timestampSet = new Set();
const maxValues = new Array(series.length).fill(Number.NEGATIVE_INFINITY);
for (let j = 0; j < series.length; j++) {
const s = series[j];
if (!s.length) continue;
for (const point of s) {
const ts = point[0] / 1000;
const value = point[1];
timestampSet.add(ts);
if (value > maxValues[j]) {
maxValues[j] = value;
}
}
}
// Convert Set to sorted array
const timestamps = Array.from(timestampSet).sort((a, b) => a - b);
// Pre-allocate arrays
const alignedSeries = series.map(() => new Array(timestamps.length).fill(null));
const scaleFactors = new Array(series.length);
// Create a single Map for each series to avoid repeated lookups
const seriesMaps = series.map(s => {
const map = new Map();
for (const point of s) {
map.set(point[0] / 1000, point[1]);
}
return map;
});
// Fill in values using the maps
for (let j = 0; j < series.length; j++) {
const seriesMap = seriesMaps[j];
for (let i = 0; i < timestamps.length; i++) {
alignedSeries[j][i] = seriesMap.get(timestamps[i]) ?? null;
}
scaleFactors[j] = maxValues[j] > Number.MAX_SAFE_INTEGER ? 1e12 : 1;
}
return {
scaleFactors,
series: alignedSeries,
timestamps,
};
};
const alignSeriesAi2 = (series) => {
if (series.length === 0) {
return { scaleFactors: [], series: [], timestamps: [] };
}
// Pre-allocate arrays with known sizes
const timestamps = new Set();
const seriesMaps = new Array(series.length);
const alignedSeries = new Array(series.length);
const scaleFactors = new Array(series.length);
// Single pass to collect timestamps and create maps
for (let j = 0; j < series.length; j++) {
const currentSeries = series[j];
const seriesMap = new Map();
let maxSeriesValue = Number.NEGATIVE_INFINITY;
for (let i = 0; i < currentSeries.length; i++) {
const [ts, value] = currentSeries[i];
const normalizedTs = Math.floor(ts / 1000);
timestamps.add(normalizedTs);
seriesMap.set(normalizedTs, value);
if (value > maxSeriesValue) {
maxSeriesValue = value;
}
}
seriesMaps[j] = seriesMap;
scaleFactors[j] = maxSeriesValue > Number.MAX_SAFE_INTEGER ? 1e12 : 1;
}
// Convert Set to sorted array once
const sortedTimestamps = Array.from(timestamps).sort((a, b) => a - b);
const timestampsLength = sortedTimestamps.length;
// Pre-allocate aligned series arrays
for (let j = 0; j < series.length; j++) {
alignedSeries[j] = new Array(timestampsLength).fill(null);
// Single pass through timestamps
for (let i = 0; i < timestampsLength; i++) {
alignedSeries[j][i] = seriesMaps[j].get(sortedTimestamps[i]) ?? null;
}
}
return {
scaleFactors,
series: alignedSeries,
timestamps: sortedTimestamps,
};
};
const alignSeriesNew = (series, options) => {
if (!series.length) {
return { scaleFactors: [], series: [], timestamps: [] };
}
const { since, until } = options || {};
const totalPoints = series.reduce((sum, s) => sum + s.length, 0);
const isZoomedIn = since != null && until != null && until - since < ONE_DAY_MS;
const needsSampling = totalPoints > 50000 && !isZoomedIn;
const timestampMap = new Map(); // timestamp -> index map
const allTimestamps = [];
for (const s of series) {
const len = s.length;
if (!needsSampling) {
for (let i = 0; i < len; i++) {
const ts = s[i][0] / 1000;
if (!timestampMap.has(ts)) {
timestampMap.set(ts, allTimestamps.length);
allTimestamps.push(ts);
}
}
continue;
}
const windowSize = Math.max(10, Math.floor(len / 10000)); // aim for ~10k points
for (let i = 0; i < len; i += windowSize) {
const end = Math.min(i + windowSize, len);
let minPoint = s[i];
let maxPoint = s[i];
const first = s[i];
const firstTs = first[0] / 1000;
if (!timestampMap.has(firstTs)) {
timestampMap.set(firstTs, allTimestamps.length);
allTimestamps.push(firstTs);
}
for (let k = i; k < end; k++) {
const point = s[k];
if (point[1] < minPoint[1]) {
minPoint = point;
}
if (point[1] > maxPoint[1]) {
maxPoint = point;
}
}
const minTs = minPoint[0] / 1000;
if (!timestampMap.has(minTs)) {
timestampMap.set(minTs, allTimestamps.length);
allTimestamps.push(minTs);
}
const maxTs = maxPoint[0] / 1000;
if (!timestampMap.has(maxTs)) {
timestampMap.set(maxTs, allTimestamps.length);
allTimestamps.push(maxTs);
}
}
}
allTimestamps.sort((a, b) => a - b);
for (let i = 0; i < allTimestamps.length; i++) {
timestampMap.set(allTimestamps[i], i);
}
const alignedSeries = new Array(series.length);
// the scaleFactors array stores a scaling factor for each series
// to ensure that extremely large values (> Number.MAX_SAFE_INTEGER)
// are scaled down to avoid potential issues with js number precision limits
const scaleFactors = new Array(series.length);
for (let j = 0; j < series.length; j++) {
const aligned = new Array(allTimestamps.length).fill(null);
let maxVal = Number.NEGATIVE_INFINITY;
for (let k = 0; k < series[j].length; k++) {
const [tsMs, val] = series[j][k];
const ts = tsMs / 1000;
const idx = timestampMap.get(ts);
if (idx !== undefined) {
aligned[idx] = val ?? null;
if (val !== null && val > maxVal) {
maxVal = val;
}
}
}
alignedSeries[j] = aligned;
scaleFactors[j] = maxVal > Number.MAX_SAFE_INTEGER ? 1e12 : 1;
}
return {
scaleFactors,
series: alignedSeries,
timestamps: allTimestamps,
};
};
const generateLargeDataSeries = (length) => {
const series1 = [];
const series2 = [];
const series3 = [];
// generate points 1 hour apart
for (let i = 0; i < length; i++) {
series1.push([1609459200000 + i * 3600 * 1000, i]);
series2.push([1609459200000 + i * 3600 * 1000, i * 2]);
series3.push([1609453200000 + i * 3600 * 1000, i * 3]);
}
return [series1, series2, series3];
}
const data = generateLargeDataSeries(200000)Ready to run.
| Test | Ops/sec | |
|---|---|---|
| normal | | ready |
| ai | | ready |
| ai2 | | ready |
| new | | ready |
You can edit these tests or add more tests to this page by appending /edit to the URL.