Skip to content

Commit

Permalink
fix: improved performance of formatters (#818)
Browse files Browse the repository at this point in the history
  • Loading branch information
semoal committed Nov 3, 2020
1 parent 49f45b2 commit 22667ad
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 7 deletions.
3 changes: 1 addition & 2 deletions packages/core/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ const defaultFormats = (
return (ctx) => {
const msg = isFunction(message) ? message(ctx) : message
const norm = Array.isArray(msg) ? msg : [msg]
const formatter = new Intl.NumberFormat(locales)
const valueStr = formatter.format(value)
const valueStr = number(locales)(value)
return norm.map((m) => (isString(m) ? m.replace("#", valueStr) : m))
}
}
Expand Down
66 changes: 66 additions & 0 deletions packages/core/src/formats.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { date, number } from "./formats"

describe("@lingui/core/formats", () => {
it("number formatter is memoized", async () => {
const firstRunt0 = performance.now()
number("es", {})(10000)
const firstRunt1 = performance.now()
const firstRunResult = firstRunt1 - firstRunt0

const seconddRunt0 = performance.now()
number("es", {})(10000)
const seconddRunt1 = performance.now()
const secondRunResult = seconddRunt1 - seconddRunt0

expect(secondRunResult).toBeLessThan(firstRunResult)
})
it("date formatter is memoized", async () => {
const firstRunt0 = performance.now()
date("es", {})(new Date())
const firstRunt1 = performance.now()
const firstRunResult = firstRunt1 - firstRunt0

const seconddRunt0 = performance.now()
date("es", {})(new Date())
const seconddRunt1 = performance.now()
const secondRunResult = seconddRunt1 - seconddRunt0

expect(secondRunResult).toBeLessThan(firstRunResult)
})

it("date memoized function is faster than the not memoized function", () => {
const loopt0 = performance.now()
for (let i = 0; i < 1000; i++) {
date("es", {})(new Date())
}
const loopt1 = performance.now()
const memoizedDateResult = loopt1 - loopt0

const loop0 = performance.now()
for (let i = 0; i < 1000; i++) {
date("es", {}, false)(new Date())
}
const loop1 = performance.now()
const withoutMemoizeResult = loop1 - loop0

expect(memoizedDateResult).toBeLessThan(withoutMemoizeResult)
})

it("number memoized function is faster than the not memoized function", () => {
const loopt0 = performance.now()
for (let i = 0; i < 1000; i++) {
number("es", {})(999666)
}
const loopt1 = performance.now()
const memoizedNumberResult = loopt1 - loopt0

const loop0 = performance.now()
for (let i = 0; i < 1000; i++) {
number("es", {}, false)(999666)
}
const loop1 = performance.now()
const withoutMemoizeResult = loop1 - loop0

expect(memoizedNumberResult).toBeLessThan(withoutMemoizeResult)
})
})
49 changes: 44 additions & 5 deletions packages/core/src/formats.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,60 @@
import { isString } from "./essentials"
import { Locales } from "./i18n"

/** Memoized cache */
const numberFormats = new Map<string, Intl.NumberFormat>()
const dateFormats = new Map<string, Intl.DateTimeFormat>()

export function date(
locales: Locales,
format: Intl.DateTimeFormatOptions = {}
format: Intl.DateTimeFormatOptions = {},
memoize: boolean = true,
): (value: string | Date) => string {
const formatter = new Intl.DateTimeFormat(locales, format)
return (value) => {
if (isString(value)) value = new Date(value)
if (memoize) {
const key = cacheKey<Intl.DateTimeFormatOptions>(locales, format)
if (dateFormats.has(key)) {
return dateFormats.get(key).format(value)
}

const formatter = new Intl.DateTimeFormat(locales, format)
dateFormats.set(key, formatter)
return formatter.format(value)
}

const formatter = new Intl.DateTimeFormat(locales, format)
return formatter.format(value)
}
}

export function number(
locales: Locales,
format: Intl.NumberFormatOptions = {}
format: Intl.NumberFormatOptions = {},
memoize: boolean = true,
): (value: number) => string {
const formatter = new Intl.NumberFormat(locales, format)
return (value) => formatter.format(value)
return (value) => {
if (memoize) {
const key = cacheKey<Intl.NumberFormatOptions>(locales, format)
if (numberFormats.has(key)) {
return numberFormats.get(key).format(value)
}

const formatter = new Intl.NumberFormat(locales, format)
numberFormats.set(key, formatter)
return formatter.format(value)
}

const formatter = new Intl.NumberFormat(locales, format)
return formatter.format(value)
}
}

/** Memoize helpers */
function cacheKey<T>(
locales?: string | string[],
options: T = {} as T,
) {
const localeKey = Array.isArray(locales) ? locales.sort().join('-') : locales
return `${localeKey}-${JSON.stringify(options)}`
}

1 comment on commit 22667ad

@vercel
Copy link

@vercel vercel bot commented on 22667ad Nov 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.