Skip to content

Commit

Permalink
fix: improve string join performance (#1433)
Browse files Browse the repository at this point in the history
* fix: improve string join performance

* fix: improve string ops performance

* fix: improve string join performance

* refactor
  • Loading branch information
kazupon authored Jun 17, 2023
1 parent 55e378c commit 3593d80
Show file tree
Hide file tree
Showing 10 changed files with 213 additions and 150 deletions.
30 changes: 30 additions & 0 deletions benchmark/compile.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createCommonJS } from 'mlly'
import { baseCompile } from '@intlify/message-compiler'

const { require } = createCommonJS(import.meta.url)
const { Suite } = require('benchmark')

async function main() {
console.log(`compilation:`)
console.log()

new Suite('compilation')
.add(`compile simple message`, () => {
baseCompile(`hello world`)
})
.add(`compile complex message`, () => {
baseCompile(`@.caml:{'no apples'} 0 | {0} apple 0 | {n} apples 0`)
})
.on('error', event => {
console.log(String(event.target))
})
.on('cycle', event => {
console.log(String(event.target))
})
.run()
}

main().catch(err => {
console.error(err)
process.exit(1)
})
119 changes: 54 additions & 65 deletions benchmark/complex.mjs
Original file line number Diff line number Diff line change
@@ -1,84 +1,73 @@
import { baseCompile } from '@intlify/message-compiler'
import { createCommonJS } from 'mlly'
import {
translate,
createCoreContext,
clearCompileCache
} from '@intlify/core-base'
import { createI18n } from 'vue-i18n'
import convertHrtime from 'convert-hrtime'
import { resolve, dirname } from 'pathe'
import { readJson } from './utils.mjs'

async function run() {
const { require } = createCommonJS(import.meta.url)
const { Suite } = require('benchmark')

async function main() {
const data = await readJson(resolve(dirname('.'), './benchmark/complex.json'))
const len = Object.keys(data).length

console.log('complex pattern ...')

console.log(`compile time: ${len} resources`)
let start = convertHrtime(process.hrtime.bigint())
for (const [, source] of Object.entries(data)) {
baseCompile(source)
}
let end = convertHrtime(process.hrtime.bigint())
console.log(`ms: ${end.milliseconds - start.milliseconds}`)

console.log(`complex pattern on ${len} resources:`)
console.log()

console.log(`resolve time with core: ${len} resources`)
const ctx = createCoreContext({
locale: 'en',
modifiers: {
caml: val => val
},
messages: {
en: data
}
})
start = convertHrtime(process.hrtime.bigint())
for (const [key] of Object.entries(data)) {
translate(ctx, key, 2)
}
end = convertHrtime(process.hrtime.bigint())
console.log(`sec: ${end.seconds - start.seconds}`)
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
let i18n

clearCompileCache()
console.log()
new Suite('complex pattern')
.add(`resolve time with core`, () => {
const ctx = createCoreContext({
locale: 'en',
modifiers: {
caml: val => val
},
messages: {
en: data
}
})
for (const [key] of Object.entries(data)) {
translate(ctx, key, 2)
}
})
.add(`resolve time on composition`, () => {
clearCompileCache()

console.log(`resolve time on composition: ${len} resources`)
const i18n = createI18n({
legacy: false,
locale: 'en',
modifiers: {
caml: val => val
},
messages: {
en: data
}
})
start = convertHrtime(process.hrtime.bigint())
for (const [key] of Object.entries(data)) {
i18n.global.t(key, 2)
}
end = convertHrtime(process.hrtime.bigint())
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
i18n = createI18n({
legacy: false,
locale: 'en',
modifiers: {
caml: val => val
},
messages: {
en: data
}
})

console.log(
`resolve time on composition with compile cache: ${len} resources`
)
start = convertHrtime(process.hrtime.bigint())
for (const [key] of Object.entries(data)) {
i18n.global.t(key, 2)
}
end = convertHrtime(process.hrtime.bigint())
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
for (const [key] of Object.entries(data)) {
i18n.global.t(key, 2)
}
})
.add(`resolve time on composition with compile cache`, () => {
for (const [key] of Object.entries(data)) {
i18n.global.t(key, 2)
}
})
.on('error', event => {
console.log(String(event.target))
})
.on('cycle', event => {
console.log(String(event.target))
})
.run()
}

;(async () => {
try {
await run()
} catch (e) {
console.error(e)
}
})()
main().catch(err => {
console.error(err)
process.exit(1)
})
2 changes: 1 addition & 1 deletion benchmark/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ function run(pattner) {

;(async () => {
try {
for (const p of ['simple', 'complex']) {
for (const p of ['compile', 'simple', 'complex']) {
await run(p)
}
} catch (e) {
Expand Down
112 changes: 49 additions & 63 deletions benchmark/simple.mjs
Original file line number Diff line number Diff line change
@@ -1,80 +1,66 @@
import { baseCompile } from '@intlify/message-compiler'
import { createCommonJS } from 'mlly'
import {
translate,
createCoreContext,
clearCompileCache
} from '@intlify/core-base'
import { createI18n } from 'vue-i18n'
import convertHrtime from 'convert-hrtime'
import { resolve, dirname } from 'pathe'
import { readJson } from './utils.mjs'

async function run() {
const simpleData = await readJson(
resolve(dirname('.'), './benchmark/simple.json')
)
const len = Object.keys(simpleData).length
const { require } = createCommonJS(import.meta.url)
const { Suite } = require('benchmark')

console.log('simple pattern ...')

console.log(`compile time: ${len} resources`)
let start = convertHrtime(process.hrtime.bigint())
for (const [, source] of Object.entries(simpleData)) {
baseCompile(source)
}
let end = convertHrtime(process.hrtime.bigint())
console.log(`sec: ${end.seconds - start.seconds}`)
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
async function main() {
const data = await readJson(resolve(dirname('.'), './benchmark/simple.json'))
const len = Object.keys(data).length

console.log(`simple pattern on ${len} resources:`)
console.log()

console.log(`resolve time with core: ${len} resources`)
const ctx = createCoreContext({
locale: 'en',
messages: {
en: simpleData
}
})
start = convertHrtime(process.hrtime.bigint())
for (const [key] of Object.entries(simpleData)) {
translate(ctx, key)
}
end = convertHrtime(process.hrtime.bigint())
console.log(`ms: ${end.milliseconds - start.milliseconds}`)

clearCompileCache()
console.log()
let i18n

console.log(`resolve time on composition: ${len} resources`)
const i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: simpleData
}
})
start = convertHrtime(process.hrtime.bigint())
for (const [key] of Object.entries(simpleData)) {
i18n.global.t(key)
}
end = convertHrtime(process.hrtime.bigint())
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
new Suite('complex pattern')
.add(`resolve time with core`, () => {
const ctx = createCoreContext({
locale: 'en',
messages: {
en: data
}
})
for (const [key] of Object.entries(data)) {
translate(ctx, key)
}
})
.add(`resolve time on composition`, () => {
clearCompileCache()

console.log(
`resolve time on composition with compile cache: ${len} resources`
)
start = convertHrtime(process.hrtime.bigint())
for (const [key] of Object.entries(simpleData)) {
i18n.global.t(key)
}
end = convertHrtime(process.hrtime.bigint())
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
i18n = createI18n({
legacy: false,
locale: 'en',
messages: {
en: data
}
})
for (const [key] of Object.entries(data)) {
i18n.global.t(key)
}
})
.add(`resolve time on composition with compile cache`, () => {
for (const [key] of Object.entries(data)) {
i18n.global.t(key)
}
})
.on('error', event => {
console.log(String(event.target))
})
.on('cycle', event => {
console.log(String(event.target))
})
.run()
}

;(async () => {
try {
await run()
} catch (e) {
console.error(e)
}
})()
main().catch(err => {
console.error(err)
process.exit(1)
})
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@
"@vitest/coverage-c8": "^0.31.1",
"algoliasearch": "^4.9.0",
"api-docs-gen": "^0.4.0",
"benchmark": "^2.1.4",
"brotli": "^1.3.2",
"bumpp": "^9.1.0",
"convert-hrtime": "^5.0.0",
"cross-env": "^7.0.3",
"esbuild-register": "^3.0.0",
"eslint": "^8.41.0",
Expand All @@ -99,6 +99,7 @@
"lint-staged": "^12.0.0",
"listhen": "^1.0.4",
"minimist": "^1.2.5",
"mlly": "^1.3.0",
"npm-run-all": "^4.1.5",
"opener": "^1.5.2",
"pathe": "^1.0.0",
Expand Down
5 changes: 3 additions & 2 deletions packages/core-base/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
isObject,
isString,
isArray,
isPlainObject
isPlainObject,
join
} from '@intlify/shared'
import { HelperNameMap } from '@intlify/message-compiler'
import { Path } from './resolver'
Expand Down Expand Up @@ -120,7 +121,7 @@ const DEFAULT_MODIFIER = (str: string): string => str
const DEFAULT_MESSAGE = (ctx: MessageContext<string>): string => '' // eslint-disable-line
export const DEFAULT_MESSAGE_DATA_TYPE = 'text'
const DEFAULT_NORMALIZE = (values: string[]): string =>
values.length === 0 ? '' : values.join('')
values.length === 0 ? '' : join(values)
const DEFAULT_INTERPOLATE = toDisplayString

function pluralDefault(choice: number, choicesLength: number): number {
Expand Down
7 changes: 5 additions & 2 deletions packages/message-compiler/src/generator.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isString } from '@intlify/shared'
import { isString, join } from '@intlify/shared'
import { SourceMapGenerator, RawSourceMap } from 'source-map'
import {
ResourceNode,
Expand Down Expand Up @@ -292,7 +292,10 @@ export const generate = (

if (helpers.length > 0) {
generator.push(
`const { ${helpers.map(s => `${s}: _${s}`).join(', ')} } = ctx`
`const { ${join(
helpers.map(s => `${s}: _${s}`),
', '
)} } = ctx`
)
generator.newline()
}
Expand Down
7 changes: 7 additions & 0 deletions packages/shared/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,13 @@ export const toDisplayString = (val: unknown): string => {
: String(val)
}

export function join(items: string[], separator = ''): string {
return items.reduce(
(str, item, index) => (index === 0 ? str + item : str + separator + item),
''
)
}

const RANGE = 2

export function generateCodeFrame(
Expand Down
Loading

0 comments on commit 3593d80

Please sign in to comment.