Skip to content
33 changes: 32 additions & 1 deletion JetStreamDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -1323,7 +1323,7 @@ class AsyncBenchmark extends DefaultBenchmark {
get runnerCode() {
return `
async function doRun() {
const benchmark = new Benchmark(${this.iterations});
const benchmark = new Benchmark(${JSON.stringify(this.benchmarkArguments)});
await benchmark.init?.();
const results = [];
const benchmarkName = "${this.name}";
Expand Down Expand Up @@ -2534,6 +2534,37 @@ let BENCHMARKS = [
];


const INTL_TESTS = [
"DateTimeFormat",
"ListFormat",
"RelativeTimeFormat",
"NumberFormat",
"PluralRules",
];
const INTL_BENCHMARKS = [];
for (const test of INTL_TESTS) {
const benchmark = new AsyncBenchmark({
name: `${test}-intl`,
files: [
"./intl/src/helper.js",
`./intl/src/${test}.js`,
"./intl/benchmark.js",
],
iterations: 2,
worstCaseCount: 1,
deterministicRandom: true,
tags: ["Javascript", "intl"],
});
INTL_BENCHMARKS.push(benchmark);
}
BENCHMARKS.push(
new GroupedBenchmark({
name: "intl",
tags: ["Javascript", "intl"],
}, INTL_BENCHMARKS));



// SunSpider tests
const SUNSPIDER_TESTS = [
"3d-cube",
Expand Down
55 changes: 55 additions & 0 deletions intl/benchmark.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2025 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

class Benchmark {
iterationCount;
verbose;
lastResult;
totalLength = 0;
expectedMinLength = 0;

constructor({ iterationCount, verbose = false } = {}) {
this.iterationCount = iterationCount;
this.verbose = verbose;
}

runIteration() {
// See implementations in src/.
const { lastResult, totalLength, expectedMinLength } = runTest(
this.verbose
);
this.lastResult = lastResult;
this.totalLength += totalLength;
this.expectedMinLength = expectedMinLength;
}

validate() {
const expectedMinTotalLength = this.expectedMinLength * this.iterationCount;
console.assert(
this.totalLength >= expectedMinTotalLength,
`Invalid totalLength = ${this.totalLength}, expected >= ${expectedMinTotalLength}`
);
}
}
77 changes: 77 additions & 0 deletions intl/src/DateTimeFormat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
function generateRandomDates(count) {
const firstDate = new Date(1800, 11, 5, 13, 6);
let currentTimeStamp = firstDate.getTime();
const dates = [];

for (let i = 0; i < count; i++) {
dates.push(new Date(currentTimeStamp));
currentTimeStamp += 1234569;
}
return dates;
}

const DATE_STYLE_OPTIONS = ["full", "long", "medium", "short"];
const TIME_STYLE_OPTIONS = ["full", "long", "medium", "short"];

function* dateTimeFormatOptions() {
for (const locale of LOCALES) {
for (const dateStyle of DATE_STYLE_OPTIONS) {
for (const timeStyle of TIME_STYLE_OPTIONS) {
yield { locale, dateStyle, timeStyle };
}
}
}
}

function runTest(verbose = false) {
let totalLength = 0;
let lastFormatResult;
let lastFormatPartResult;
let lastFormatRangeResult;
const dates = generateRandomDates(100);
let dateIndex = 0;

const FORMAT_COUNT = 17;
const FORMAT_RANGE_COUNT = 7;
for (const { locale, dateStyle, timeStyle } of shuffleOptions(
dateTimeFormatOptions
)) {
const options = { dateStyle, timeStyle };
if (verbose) {
console.log(locale, JSON.stringify(options));
}
const formatter = new Intl.DateTimeFormat(locale, options);
for (let i = 0; i < FORMAT_COUNT; i++) {
let date = dates[dateIndex % dates.length];
lastFormatResult = formatter.format(date);
totalLength += lastFormatResult.length;
if (verbose) {
console.log(date, lastFormatResult);
}
dateIndex++;

date = dates[dateIndex % dates.length];
lastFormatPartResult = formatter.formatToParts(date);
for (const part of lastFormatPartResult) {
totalLength += part.value.length;
}
dateIndex++;
}
let dateRangeStart = dates[0];
for (let i = 0; i < FORMAT_RANGE_COUNT; i++) {
const date = dates[dateIndex % dates.length];
if (dateRangeStart < date) {
lastFormatRangeResult = formatter.formatRange(dateRangeStart, date);
if (verbose) {
console.log(dateRangeStart, date, lastFormatRangeResult);
}
}
dateRangeStart = date;
}
}
return {
lastResult: lastFormatResult + lastFormatRangeResult,
totalLength,
expectedMinLength: 438_000,
};
}
47 changes: 47 additions & 0 deletions intl/src/ListFormat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const LISTS = [
["One"],
["1", "2"],
["Motorcycle", "Bus", "Car"],
LOCALES,
new Array(100).fill(9).map((_, index) => index.toString()),
];

function* listOptions() {
const styleOptions = ["long", "short", "narrow"];
const typeOptions = ["conjunction", "disjunction", "unit"];
for (const locale of LOCALES) {
for (const style of styleOptions) {
for (const type of typeOptions) {
yield { locale, style, type };
}
}
}
}

function runTest(verbose = false) {
let lastResult;
let totalLength = 0;
const LIST_FORMAT_COUNT = 10;
let listIndex = 0;
for (const { locale, style, type } of shuffleOptions(listOptions)) {
const options = { style, type };
if (verbose) {
console.log(locale, JSON.stringify(options));
}
const formatter = new Intl.ListFormat(locale, options);
for (let i = 0; i < LIST_FORMAT_COUNT; i++) {
const list = LISTS[listIndex % LISTS.length];
listIndex++;
lastResult = formatter.format(list);
totalLength += lastResult.length;
const formatPartsResult = formatter.formatToParts(list);
if (verbose) {
console.log(value, lastResult);
}
for (const part of formatPartsResult) {
totalLength += part.value.length;
}
}
}
return { lastResult, totalLength, expectedMinLength: 506_000 };
}
96 changes: 96 additions & 0 deletions intl/src/NumberFormat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
const CURRENCIES = ["USD", "EUR", "JPY", "INR", "NGN"];

const NUMBER_UNITS = [
"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",
"microsecond",
"mile",
"mile-scandinavian",
"milliliter",
"millimeter",
"millisecond",
"minute",
"month",
"nanosecond",
"ounce",
"percent",
"petabyte",
"pound",
"second",
"stone",
"terabit",
"terabyte",
"week",
"yard",
"year",
];

function* numberFormatOptions() {
const currencyDisplayOptions = ["symbol", "narrowSymbol", "code", "name"];
const unitDisplayOptions = ["short", "long", "narrow"];

for (const locale of LOCALES) {
for (const currency of CURRENCIES) {
for (const currencyDisplay of currencyDisplayOptions) {
yield { locale, style: "currency", currency, currencyDisplay };
}
}
for (const unit of NUMBER_UNITS.slice(0, 20)) {
for (const unitDisplay of unitDisplayOptions) {
yield { locale, style: "unit", unit, unitDisplay };
}
}
yield { locale, style: "decimal" };
yield { locale, style: "percent" };
}
}

function runTest(verbose = false) {
let lastResult;
let totalLength = 0;
const NUMBER_FORMAT_COUNT = 10;
let counter = 1;
for (const options of shuffleOptions(numberFormatOptions).slice(0, 200)) {
const formatter = new Intl.NumberFormat(options.locale, options);
if (verbose) {
console.log(options.locale, JSON.stringify(options));
}
for (let i = 0; i < NUMBER_FORMAT_COUNT; i++) {
counter += 599;
const value = counter % 10_000;
lastResult = formatter.format(value);
if (verbose) {
console.log(value, lastResult);
}
totalLength += lastResult.length;
const formatPartsResult = formatter.formatToParts(value);
for (const part of formatPartsResult) {
totalLength += part.value.length;
}
}
}
return { lastResult, totalLength, expectedMinLength: 40_000 };
}
38 changes: 38 additions & 0 deletions intl/src/PluralRules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
function* pluralRulesOptions() {
const typeOptions = ["cardinal", "ordinal"];
for (const locale of LOCALES) {
for (const type of typeOptions) {
yield { locale, type };
}
}
}

function runTest(verbose = false) {
let lastResult;
let totalLength = 0;
const PLURAL_RULES_COUNT = 1000;
for (const { locale, type } of shuffleOptions(pluralRulesOptions)) {
if (verbose) {
console.log(locale, type);
}
const formatter = new Intl.PluralRules(locale, { type });
let i = 0;
for (let value = 0; value < 4; value++) {
lastResult = formatter.select(value);
totalLength += lastResult.length;
if (verbose) {
console.log(value, lastResult);
}
i++;
}
for (; i < PLURAL_RULES_COUNT; i++) {
const value = Math.floor(Math.random() * 1000);
lastResult = formatter.select(value);
totalLength += lastResult.length;
if (verbose) {
console.log(value, lastResult);
}
}
}
return { lastResult, totalLength, expectedMinLength: 244_000 };
}
Loading
Loading