Bug 1714100 - Add an Intl.DateTimeFormat microbenchmark;r=dminor,nordzilla

Differential Revision: https://phabricator.services.mozilla.com/D116657
This commit is contained in:
Greg Tatum 2021-06-08 20:27:16 +00:00
parent 1a7c5a8df1
commit a40da379a7
7 changed files with 295 additions and 0 deletions

View File

@ -0,0 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
module.exports = {
extends: ["plugin:mozilla/xpcshell-test"],
};

20
intl/benchmarks/README.md Normal file
View File

@ -0,0 +1,20 @@
# Intl Performance Microbenchmarks
This folder contains micro benchmarks using the [mozperftest][] suite.
[mozperftest](https://firefox-source-docs.mozilla.org/testing/perfdocs/mozperftest.html)
## Recording profiles for the Firefox Profiler
```sh
# Run the perftest as an xpcshell test.
MOZ_PROFILER_STARTUP=1 \
MOZ_PROFILER_SHUTDOWN=/path/to/perf-profile.json \
./mach xpcshell-test intl/benchmarks/perftest_dateTimeFormat.js
# Install the profiler-symbol-server tool.
cargo install profiler-symbol-server
# Open the path to the file.
profiler-symbol-server --open /path/to/perf-profile.json
```

131
intl/benchmarks/head.js Normal file
View File

@ -0,0 +1,131 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Create an interface to measure iterations for a micro benchmark. These iterations
* will then be reported to the perftest runner.
*
* @param {string} metricName
*/
function measureIterations(metricName) {
let accumulatedTime = 0;
let iterations = 0;
let now = 0;
return {
/**
* Start a measurement.
*/
start() {
now = Cu.now();
},
/**
* Stop a measurement, and record the elapsed time.
*/
stop() {
accumulatedTime += Cu.now() - now;
iterations++;
},
/**
* Report the metrics to perftest after finishing the microbenchmark.
*/
reportMetrics() {
const metrics = {};
metrics[metricName + " iterations"] = iterations;
metrics[metricName + " accumulatedTime"] = accumulatedTime;
metrics[metricName + " perCallTime"] = accumulatedTime / iterations;
info("perfMetrics", metrics);
},
};
}
let _seed = 123456;
/**
* A cheap and simple pseudo-random number generator that avoids adding new dependencies.
* This function ensures tests are repeatable, but can be fed random configurations.
*
* https://en.wikipedia.org/wiki/Linear_congruential_generator
*
* It has the following distribution for the first 100,000 runs:
*
* 0.0 - 0.1: 9948
* 0.1 - 0.2: 10037
* 0.2 - 0.3: 10049
* 0.3 - 0.4: 10041
* 0.4 - 0.5: 10036
* 0.5 - 0.6: 10085
* 0.6 - 0.7: 9987
* 0.7 - 0.8: 9872
* 0.8 - 0.9: 10007
* 0.9 - 1.0: 9938
*
* @returns {number} float values ranged 0-1
*/
function prng() {
_seed = Math.imul(_seed, 22695477) + 1;
return (_seed >> 1) / 0x7fffffff + 0.5;
}
/**
* The distribution of locales. The number represents the ratio of total users in that
* locale. The numbers should add up to ~1.0.
*
* https://sql.telemetry.mozilla.org/dashboard/firefox-localization
*/
const localeDistribution = {
"en-US": 0.373,
de: 0.129,
fr: 0.084,
"zh-CN": 0.053,
ru: 0.048,
"es-ES": 0.047,
pl: 0.041,
"pt-BR": 0.034,
it: 0.028,
"en-GB": 0.027,
ja: 0.019,
"es-MX": 0.014,
nl: 0.01,
cs: 0.009,
hu: 0.008,
id: 0.006,
"en-CA": 0.006,
"es-AR": 0.006,
tr: 0.005,
el: 0.005,
"zh-TW": 0.005,
fi: 0.005,
"sv-SE": 0.004,
"pt-PT": 0.004,
sk: 0.003,
ar: 0.003,
vi: 0.003,
"es-CL": 0.002,
th: 0.002,
da: 0.002,
bg: 0.002,
ro: 0.002,
"nb-NO": 0.002,
ko: 0.002,
};
/**
* Go through the top Firefox locales, and pick one at random that is representative
* of the Firefox population as of 2021-06-03. It uses a pseudo-random number generator
* to make the results repeatable.
*
* @returns {string} locale
*/
function pickRepresentativeLocale() {
const n = prng();
let ratio = 1;
for (const [locale, representation] of Object.entries(localeDistribution)) {
ratio -= representation;
if (n > ratio) {
return locale;
}
}
// In case we fall through the "for" loop, return the most common locale.
return "en-US";
}

View File

@ -0,0 +1 @@
[perftest_dateTimeFormat.js]

View File

@ -0,0 +1,122 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// @ts-check
var perfMetadata = {
owner: "Internationalization Team",
name: "Intl.DateTimeFormat",
description: "Test the speed of the Intl.DateTimeFormat implementation.",
options: {
default: {
perfherder: true,
perfherder_metrics: [
{
name: "Intl.DateTimeFormat constructor iterations",
unit: "iterations",
},
{ name: "Intl.DateTimeFormat constructor accumulatedTime", unit: "ms" },
{ name: "Intl.DateTimeFormat constructor perCallTime", unit: "ms" },
{
name: "Intl.DateTimeFormat.prototype.format iterations",
unit: "iterations",
},
{
name: "Intl.DateTimeFormat.prototype.format accumulatedTime",
unit: "ms",
},
{
name: "Intl.DateTimeFormat.prototype.format perCallTime",
unit: "ms",
},
],
verbose: true,
},
},
tags: ["intl", "ecma402"],
};
add_task(function measure_date() {
const measureConstructor = measureIterations(
"Intl.DateTimeFormat constructor"
);
const measureFormat = measureIterations(
"Intl.DateTimeFormat.prototype.format"
);
// Re-use the config between runs.
const fieldOptions = {
weekday: ["narrow", "short", "long"],
era: ["narrow", "short", "long"],
year: ["2-digit", "numeric"],
month: ["2-digit", "numeric", "narrow", "short", "long"],
day: ["2-digit", "numeric"],
hour: ["2-digit", "numeric"],
minute: ["2-digit", "numeric"],
second: ["2-digit", "numeric"],
timeZoneName: ["short", "long"],
};
const config = {};
function randomizeConfig(name, chance) {
const option = fieldOptions[name];
if (prng() < chance) {
config[name] = option[Math.floor(option.length * prng())];
} else {
delete config[name];
}
}
let date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738));
// Split each step of the benchmark into separate JS functions so that performance
// profiles are easy to analyze.
function benchmarkDateTimeFormatConstructor() {
for (let i = 0; i < 1000; i++) {
// Create a random configuration powered by a pseudo-random number generator. This
// way the configurations will be the same between 2 different runs.
const locale = pickRepresentativeLocale();
randomizeConfig("year", 0.5);
randomizeConfig("month", 0.5);
randomizeConfig("day", 0.5);
randomizeConfig("hour", 0.5);
randomizeConfig("minute", 0.5);
// Set the following to some lower probabilities:
randomizeConfig("second", 0.2);
randomizeConfig("timeZoneName", 0.2);
randomizeConfig("weekday", 0.2);
randomizeConfig("era", 0.1);
// Measure the constructor.
measureConstructor.start();
const formatter = Intl.DateTimeFormat(locale, config);
// Also include one format operation to ensure the constructor is de-lazified.
formatter.format(date);
measureConstructor.stop();
benchmarkFormatOperation(formatter);
}
}
const start = Date.UTC(2000);
const end = Date.UTC(2030);
const dateDiff = end - start;
function benchmarkFormatOperation(formatter) {
// Measure the format operation.
for (let j = 0; j < 100; j++) {
date = new Date(start + prng() * dateDiff);
measureFormat.start();
formatter.format(date);
measureFormat.stop();
}
}
benchmarkDateTimeFormatConstructor();
measureConstructor.reportMetrics();
measureFormat.reportMetrics();
ok(true);
});

View File

@ -0,0 +1,8 @@
[DEFAULT]
head = head.js
# Add perftests here as it's useful to run them as xpcshell tests, but we don't need them
# to be run in CI.
[perftest_dateTimeFormat.js]
skip-if = true

View File

@ -37,6 +37,10 @@ EXPORTS += [
"../third_party/rust/shift_or_euc_c/include/shift_or_euc.h",
]
PERFTESTS_MANIFESTS += ["benchmarks/perftest.ini"]
XPCSHELL_TESTS_MANIFESTS += ["benchmarks/xpcshell.ini"]
with Files("**"):
BUG_COMPONENT = ("Core", "Internationalization")