From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- intl/benchmarks/README.md | 20 +++ intl/benchmarks/head.js | 131 +++++++++++++++++ intl/benchmarks/perftest.toml | 9 ++ intl/benchmarks/perftest_dateTimeFormat.js | 122 ++++++++++++++++ intl/benchmarks/perftest_locale.js | 162 +++++++++++++++++++++ intl/benchmarks/perftest_numberFormat.js | 221 +++++++++++++++++++++++++++++ intl/benchmarks/perftest_pluralRules.js | 119 ++++++++++++++++ intl/benchmarks/xpcshell.toml | 17 +++ 8 files changed, 801 insertions(+) create mode 100644 intl/benchmarks/README.md create mode 100644 intl/benchmarks/head.js create mode 100644 intl/benchmarks/perftest.toml create mode 100644 intl/benchmarks/perftest_dateTimeFormat.js create mode 100644 intl/benchmarks/perftest_locale.js create mode 100644 intl/benchmarks/perftest_numberFormat.js create mode 100644 intl/benchmarks/perftest_pluralRules.js create mode 100644 intl/benchmarks/xpcshell.toml (limited to 'intl/benchmarks') diff --git a/intl/benchmarks/README.md b/intl/benchmarks/README.md new file mode 100644 index 0000000000..386cd1f182 --- /dev/null +++ b/intl/benchmarks/README.md @@ -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 +``` diff --git a/intl/benchmarks/head.js b/intl/benchmarks/head.js new file mode 100644 index 0000000000..96250484a8 --- /dev/null +++ b/intl/benchmarks/head.js @@ -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"; +} diff --git a/intl/benchmarks/perftest.toml b/intl/benchmarks/perftest.toml new file mode 100644 index 0000000000..8983b162f8 --- /dev/null +++ b/intl/benchmarks/perftest.toml @@ -0,0 +1,9 @@ +[DEFAULT] + +["perftest_dateTimeFormat.js"] + +["perftest_locale.js"] + +["perftest_numberFormat.js"] + +["perftest_pluralRules.js"] diff --git a/intl/benchmarks/perftest_dateTimeFormat.js b/intl/benchmarks/perftest_dateTimeFormat.js new file mode 100644 index 0000000000..ffb75d0464 --- /dev/null +++ b/intl/benchmarks/perftest_dateTimeFormat.js @@ -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); +}); diff --git a/intl/benchmarks/perftest_locale.js b/intl/benchmarks/perftest_locale.js new file mode 100644 index 0000000000..aac624127c --- /dev/null +++ b/intl/benchmarks/perftest_locale.js @@ -0,0 +1,162 @@ +/* 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.Locale", + description: "Test the speed of the Intl.Locale implementation.", + options: { + default: { + perfherder: true, + perfherder_metrics: [ + { + name: "Intl.Locale constructor iterations", + unit: "iterations", + }, + { name: "Intl.Locale constructor accumulatedTime", unit: "ms" }, + { name: "Intl.Locale constructor perCallTime", unit: "ms" }, + + { + name: "Intl.Locale.prototype accessors iterations", + unit: "iterations", + }, + { + name: "Intl.Locale.prototype accessors accumulatedTime", + unit: "ms", + }, + { + name: "Intl.Locale.prototype accessors perCallTime", + unit: "ms", + }, + + { + name: "Intl.Locale.maximize operation iterations", + unit: "iterations", + }, + { + name: "Intl.Locale.maximize operation accumulatedTime", + unit: "ms", + }, + { + name: "Intl.Locale.maximize operation perCallTime", + unit: "ms", + }, + ], + verbose: true, + }, + }, + tags: ["intl", "ecma402"], +}; + +const maximizeLocales = [ + "en-US", + "en-GB", + "es-AR", + "it", + "zh-Hans-CN", + "de-AT", + "pl", + "fr-FR", + "de-AT", + "sr-Cyrl-SR", + "nb-NO", + "fr-FR", + "mk", + "uk", + "und-PL", + "und-Latn-AM", + "ug-Cyrl", + "sr-ME", + "mn-Mong", + "lif-Limb", + "gan", + "zh-Hant", + "yue-Hans", + "unr", + "unr-Deva", + "und-Thai-CN", + "ug-Cyrl", + "en-Latn-DE", + "pl-FR", + "de-CH", + "tuq", + "sr-ME", + "ng", + "klx", + "kk-Arab", + "en-Cyrl", + "und-Cyrl-UK", + "und-Arab", + "und-Arab-FO", +]; + +add_task(function measure_locale() { + const measureConstructor = measureIterations("Intl.Locale constructor"); + const measureAccessors = measureIterations("Intl.Locale.prototype accessors"); + const measureMaximize = measureIterations("Intl.Locale.maximize operation"); + + // 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 localeString = pickRepresentativeLocale(); + + // Measure the constructor. + measureConstructor.start(); + const locale = new Intl.Locale(localeString); + measureConstructor.stop(); + + benchmarkAccessors(locale); + } + } + + const accessors = [ + "basename", + "calendar", + "caseFirst", + "collation", + "hourCycle", + "numeric", + "numberingSystem", + "language", + "script", + "region", + ]; + + function benchmarkAccessors(locale) { + for (let j = 0; j < 100; j++) { + measureAccessors.start(); + for (let accessor in accessors) { + locale[accessor]; + } + measureAccessors.stop(); + } + } + + function benchmarkMaximize() { + let locales = []; + for (let localeString of maximizeLocales) { + locales.push(new Intl.Locale(localeString)); + } + for (let j = 0; j < 10000; j++) { + measureMaximize.start(); + for (let locale of locales) { + locale.maximize(); + } + measureMaximize.stop(); + } + } + + benchmarkDateTimeFormatConstructor(); + benchmarkMaximize(); + measureConstructor.reportMetrics(); + measureAccessors.reportMetrics(); + measureMaximize.reportMetrics(); + + ok(true); +}); diff --git a/intl/benchmarks/perftest_numberFormat.js b/intl/benchmarks/perftest_numberFormat.js new file mode 100644 index 0000000000..c2a254c6ab --- /dev/null +++ b/intl/benchmarks/perftest_numberFormat.js @@ -0,0 +1,221 @@ +/* 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.NumberFormat", + description: "Test the speed of the Intl.NumberFormat implementation.", + options: { + default: { + perfherder: true, + perfherder_metrics: [ + { + name: "Intl.NumberFormat constructor iterations", + unit: "iterations", + }, + { name: "Intl.NumberFormat constructor accumulatedTime", unit: "ms" }, + { name: "Intl.NumberFormat constructor perCallTime", unit: "ms" }, + + { + name: "Intl.NumberFormat.prototype.format iterations", + unit: "iterations", + }, + { + name: "Intl.NumberFormat.prototype.format accumulatedTime", + unit: "ms", + }, + { + name: "Intl.NumberFormat.prototype.format perCallTime", + unit: "ms", + }, + + { + name: "Intl.NumberFormat.prototype.formatToParts iterations", + unit: "iterations", + }, + { + name: "Intl.NumberFormat.prototype.formatToParts accumulatedTime", + unit: "ms", + }, + { + name: "Intl.NumberFormat.prototype.formatToParts perCallTime", + unit: "ms", + }, + ], + verbose: true, + }, + }, + tags: ["intl", "ecma402"], +}; + +add_task(function measure_numberformat() { + const measureConstructor = measureIterations("Intl.NumberFormat constructor"); + const measureFormat = measureIterations("Intl.NumberFormat.prototype.format"); + const measureFormatToParts = measureIterations( + "Intl.NumberFormat.prototype.formatToParts" + ); + + // Re-use the config between runs. + + const styles = ["decimal", "percent", "currency", "unit"]; + + const numberStyles = [ + "arab", + "arabext", + "bali", + "beng", + "deva", + "fullwide", + "gujr", + "guru", + "hanidec", + "khmr", + "knda", + "laoo", + "latn", + "limb", + "mlym", + "mong", + "mymr", + "orya", + "tamldec", + "telu", + "thai", + "tibt", + ]; + + const decimalOptions = { + notation: ["scientific", "engineering", "compact"], + useGroup: [true, false], + }; + + const currencyOptions = { + currency: ["USD", "CAD", "EUR", "Yen", "MXN", "SAR", "INR", "CNY", "IDR"], + currencyDisplay: ["symbol", "narrowSymbol", "code", "name"], + currencySign: ["accounting", "standard"], + }; + + const unitOptions = { + unit: [ + "acre", + "bit", + "byte", + "celsius", + "centimeter", + "day", + "degree", + "fahrenheit", + "fluid-ounce", + "foot", + "gallon", + "gigabit", + "gigabyte", + "gram", + "hectare", + "hour", + "inch", + "kilobit", + "kilobyte", + "kilogram", + "kilometer", + "liter", + "megabit", + "megabyte", + "meter", + "mile", + "mile-scandinavian", + "milliliter", + "millimeter", + "millisecond", + "minute", + "month", + "ounce", + "percent", + "petabyte", + "pound", + "second", + "stone", + "terabit", + "terabyte", + "week", + "yard", + "year", + "meter-per-second", + "kilometer-per-hour", + ], + unitDisplay: ["long", "short", "narrow"], + }; + + function choose(options) { + return options[Math.floor(options.length * prng())]; + } + + function randomizeConfig(config, options) { + for (let option in options) { + config[option] = choose(options[option]); + } + } + + // Split each step of the benchmark into separate JS functions so that performance + // profiles are easy to analyze. + + function benchmarkNumberFormatConstructor() { + 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(); + const style = choose(styles); + const nu = choose(numberStyles); + let config = { + style, + nu, + }; + if (style == "decimal") { + randomizeConfig(config, decimalOptions); + } else if (style == "currency") { + randomizeConfig(config, currencyOptions); + } else if (style == "unit") { + randomizeConfig(config, unitOptions); + } + + // Measure the constructor. + measureConstructor.start(); + const formatter = Intl.NumberFormat(locale, config); + // Also include one format operation to ensure the constructor is de-lazified. + formatter.format(0); + measureConstructor.stop(); + + benchmarkFormatOperation(formatter); + benchmarkFormatToPartsOperation(formatter); + } + } + + function benchmarkFormatOperation(formatter) { + // Measure the format operation. + for (let j = 0; j < 100; j++) { + let num = -1e6 + prng() * 2e6; + measureFormat.start(); + formatter.format(num); + measureFormat.stop(); + } + } + + function benchmarkFormatToPartsOperation(formatter) { + // Measure the formatToParts operation. + for (let j = 0; j < 100; j++) { + let num = -1e6 + prng() * 2e6; + measureFormatToParts.start(); + formatter.formatToParts(num); + measureFormatToParts.stop(); + } + } + + benchmarkNumberFormatConstructor(); + measureConstructor.reportMetrics(); + measureFormat.reportMetrics(); + measureFormatToParts.reportMetrics(); + + ok(true); +}); diff --git a/intl/benchmarks/perftest_pluralRules.js b/intl/benchmarks/perftest_pluralRules.js new file mode 100644 index 0000000000..956dd09561 --- /dev/null +++ b/intl/benchmarks/perftest_pluralRules.js @@ -0,0 +1,119 @@ +/* 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.PluralRules", + description: "Test the speed of the Intl.PluralRules implementation.", + options: { + default: { + perfherder: true, + perfherder_metrics: [ + { + name: "Intl.PluralRules constructor iterations", + unit: "iterations", + }, + { name: "Intl.PluralRules constructor accumulatedTime", unit: "ms" }, + { name: "Intl.PluralRules constructor perCallTime", unit: "ms" }, + + { + name: "Intl.PluralRules.prototype.select iterations", + unit: "iterations", + }, + { + name: "Intl.PluralRules.prototype.select accumulatedTime", + unit: "ms", + }, + { + name: "Intl.PluralRules.prototype.select perCallTime", + unit: "ms", + }, + + { + name: "Intl.PluralRules pluralCategories iterations", + unit: "iterations", + }, + { + name: "Intl.PluralRules pluralCategories accumulatedTime", + unit: "ms", + }, + { + name: "Intl.PluralRules pluralCategories perCallTime", + unit: "ms", + }, + ], + verbose: true, + }, + }, + tags: ["intl", "ecma402"], +}; + +add_task(function measure_pluralrules() { + const measureConstructor = measureIterations("Intl.PluralRules constructor"); + const measureSelect = measureIterations("Intl.PluralRules.prototype.select"); + const measurePluralCategories = measureIterations( + "Intl.PluralRules pluralCategories" + ); + + // Re-use the config between runs. + + const fieldOptions = { + type: ["cardinal", "ordinal"], + }; + + 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]; + } + } + + // Split each step of the benchmark into separate JS functions so that performance + // profiles are easy to analyze. + + function benchmarkPluralRulesConstructor() { + 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("type", 0.5); + + // Measure the constructor. + measureConstructor.start(); + const pr = new Intl.PluralRules(locale, config); + measureConstructor.stop(); + + benchmarkSelectOperation(pr); + benchmarkPluralCategories(pr); + } + } + + function benchmarkSelectOperation(pr) { + // Measure the select operation. + for (let j = 0; j < 1000; j++) { + // TODO: We may want to extend this to non-integer values in the future. + const num = Math.floor(prng() * 10000); + measureSelect.start(); + pr.select(num); + measureSelect.stop(); + } + } + + function benchmarkPluralCategories(pr) { + measurePluralCategories.start(); + pr.resolvedOptions().pluralCategories; + measurePluralCategories.stop(); + } + + benchmarkPluralRulesConstructor(); + measureConstructor.reportMetrics(); + measureSelect.reportMetrics(); + measurePluralCategories.reportMetrics(); + + ok(true); +}); diff --git a/intl/benchmarks/xpcshell.toml b/intl/benchmarks/xpcshell.toml new file mode 100644 index 0000000000..5de3504502 --- /dev/null +++ b/intl/benchmarks/xpcshell.toml @@ -0,0 +1,17 @@ +[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"] + +["perftest_locale.js"] +skip-if = ["true"] + +["perftest_numberFormat.js"] +skip-if = ["true"] + +["perftest_pluralRules.js"] +skip-if = ["true"] -- cgit v1.2.3