Skip to content

Commit 3593d80

Browse files
authored
fix: improve string join performance (#1433)
* fix: improve string join performance * fix: improve string ops performance * fix: improve string join performance * refactor
1 parent 55e378c commit 3593d80

File tree

10 files changed

+213
-150
lines changed

10 files changed

+213
-150
lines changed

benchmark/compile.mjs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { createCommonJS } from 'mlly'
2+
import { baseCompile } from '@intlify/message-compiler'
3+
4+
const { require } = createCommonJS(import.meta.url)
5+
const { Suite } = require('benchmark')
6+
7+
async function main() {
8+
console.log(`compilation:`)
9+
console.log()
10+
11+
new Suite('compilation')
12+
.add(`compile simple message`, () => {
13+
baseCompile(`hello world`)
14+
})
15+
.add(`compile complex message`, () => {
16+
baseCompile(`@.caml:{'no apples'} 0 | {0} apple 0 | {n} apples 0`)
17+
})
18+
.on('error', event => {
19+
console.log(String(event.target))
20+
})
21+
.on('cycle', event => {
22+
console.log(String(event.target))
23+
})
24+
.run()
25+
}
26+
27+
main().catch(err => {
28+
console.error(err)
29+
process.exit(1)
30+
})

benchmark/complex.mjs

Lines changed: 54 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,84 +1,73 @@
1-
import { baseCompile } from '@intlify/message-compiler'
1+
import { createCommonJS } from 'mlly'
22
import {
33
translate,
44
createCoreContext,
55
clearCompileCache
66
} from '@intlify/core-base'
77
import { createI18n } from 'vue-i18n'
8-
import convertHrtime from 'convert-hrtime'
98
import { resolve, dirname } from 'pathe'
109
import { readJson } from './utils.mjs'
1110

12-
async function run() {
11+
const { require } = createCommonJS(import.meta.url)
12+
const { Suite } = require('benchmark')
13+
14+
async function main() {
1315
const data = await readJson(resolve(dirname('.'), './benchmark/complex.json'))
1416
const len = Object.keys(data).length
1517

16-
console.log('complex pattern ...')
17-
18-
console.log(`compile time: ${len} resources`)
19-
let start = convertHrtime(process.hrtime.bigint())
20-
for (const [, source] of Object.entries(data)) {
21-
baseCompile(source)
22-
}
23-
let end = convertHrtime(process.hrtime.bigint())
24-
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
25-
18+
console.log(`complex pattern on ${len} resources:`)
2619
console.log()
2720

28-
console.log(`resolve time with core: ${len} resources`)
29-
const ctx = createCoreContext({
30-
locale: 'en',
31-
modifiers: {
32-
caml: val => val
33-
},
34-
messages: {
35-
en: data
36-
}
37-
})
38-
start = convertHrtime(process.hrtime.bigint())
39-
for (const [key] of Object.entries(data)) {
40-
translate(ctx, key, 2)
41-
}
42-
end = convertHrtime(process.hrtime.bigint())
43-
console.log(`sec: ${end.seconds - start.seconds}`)
44-
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
21+
let i18n
4522

46-
clearCompileCache()
47-
console.log()
23+
new Suite('complex pattern')
24+
.add(`resolve time with core`, () => {
25+
const ctx = createCoreContext({
26+
locale: 'en',
27+
modifiers: {
28+
caml: val => val
29+
},
30+
messages: {
31+
en: data
32+
}
33+
})
34+
for (const [key] of Object.entries(data)) {
35+
translate(ctx, key, 2)
36+
}
37+
})
38+
.add(`resolve time on composition`, () => {
39+
clearCompileCache()
4840

49-
console.log(`resolve time on composition: ${len} resources`)
50-
const i18n = createI18n({
51-
legacy: false,
52-
locale: 'en',
53-
modifiers: {
54-
caml: val => val
55-
},
56-
messages: {
57-
en: data
58-
}
59-
})
60-
start = convertHrtime(process.hrtime.bigint())
61-
for (const [key] of Object.entries(data)) {
62-
i18n.global.t(key, 2)
63-
}
64-
end = convertHrtime(process.hrtime.bigint())
65-
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
41+
i18n = createI18n({
42+
legacy: false,
43+
locale: 'en',
44+
modifiers: {
45+
caml: val => val
46+
},
47+
messages: {
48+
en: data
49+
}
50+
})
6651

67-
console.log(
68-
`resolve time on composition with compile cache: ${len} resources`
69-
)
70-
start = convertHrtime(process.hrtime.bigint())
71-
for (const [key] of Object.entries(data)) {
72-
i18n.global.t(key, 2)
73-
}
74-
end = convertHrtime(process.hrtime.bigint())
75-
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
52+
for (const [key] of Object.entries(data)) {
53+
i18n.global.t(key, 2)
54+
}
55+
})
56+
.add(`resolve time on composition with compile cache`, () => {
57+
for (const [key] of Object.entries(data)) {
58+
i18n.global.t(key, 2)
59+
}
60+
})
61+
.on('error', event => {
62+
console.log(String(event.target))
63+
})
64+
.on('cycle', event => {
65+
console.log(String(event.target))
66+
})
67+
.run()
7668
}
7769

78-
;(async () => {
79-
try {
80-
await run()
81-
} catch (e) {
82-
console.error(e)
83-
}
84-
})()
70+
main().catch(err => {
71+
console.error(err)
72+
process.exit(1)
73+
})

benchmark/index.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ function run(pattner) {
1919

2020
;(async () => {
2121
try {
22-
for (const p of ['simple', 'complex']) {
22+
for (const p of ['compile', 'simple', 'complex']) {
2323
await run(p)
2424
}
2525
} catch (e) {

benchmark/simple.mjs

Lines changed: 49 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,66 @@
1-
import { baseCompile } from '@intlify/message-compiler'
1+
import { createCommonJS } from 'mlly'
22
import {
33
translate,
44
createCoreContext,
55
clearCompileCache
66
} from '@intlify/core-base'
77
import { createI18n } from 'vue-i18n'
8-
import convertHrtime from 'convert-hrtime'
98
import { resolve, dirname } from 'pathe'
109
import { readJson } from './utils.mjs'
1110

12-
async function run() {
13-
const simpleData = await readJson(
14-
resolve(dirname('.'), './benchmark/simple.json')
15-
)
16-
const len = Object.keys(simpleData).length
11+
const { require } = createCommonJS(import.meta.url)
12+
const { Suite } = require('benchmark')
1713

18-
console.log('simple pattern ...')
19-
20-
console.log(`compile time: ${len} resources`)
21-
let start = convertHrtime(process.hrtime.bigint())
22-
for (const [, source] of Object.entries(simpleData)) {
23-
baseCompile(source)
24-
}
25-
let end = convertHrtime(process.hrtime.bigint())
26-
console.log(`sec: ${end.seconds - start.seconds}`)
27-
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
14+
async function main() {
15+
const data = await readJson(resolve(dirname('.'), './benchmark/simple.json'))
16+
const len = Object.keys(data).length
2817

18+
console.log(`simple pattern on ${len} resources:`)
2919
console.log()
3020

31-
console.log(`resolve time with core: ${len} resources`)
32-
const ctx = createCoreContext({
33-
locale: 'en',
34-
messages: {
35-
en: simpleData
36-
}
37-
})
38-
start = convertHrtime(process.hrtime.bigint())
39-
for (const [key] of Object.entries(simpleData)) {
40-
translate(ctx, key)
41-
}
42-
end = convertHrtime(process.hrtime.bigint())
43-
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
44-
45-
clearCompileCache()
46-
console.log()
21+
let i18n
4722

48-
console.log(`resolve time on composition: ${len} resources`)
49-
const i18n = createI18n({
50-
legacy: false,
51-
locale: 'en',
52-
messages: {
53-
en: simpleData
54-
}
55-
})
56-
start = convertHrtime(process.hrtime.bigint())
57-
for (const [key] of Object.entries(simpleData)) {
58-
i18n.global.t(key)
59-
}
60-
end = convertHrtime(process.hrtime.bigint())
61-
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
23+
new Suite('complex pattern')
24+
.add(`resolve time with core`, () => {
25+
const ctx = createCoreContext({
26+
locale: 'en',
27+
messages: {
28+
en: data
29+
}
30+
})
31+
for (const [key] of Object.entries(data)) {
32+
translate(ctx, key)
33+
}
34+
})
35+
.add(`resolve time on composition`, () => {
36+
clearCompileCache()
6237

63-
console.log(
64-
`resolve time on composition with compile cache: ${len} resources`
65-
)
66-
start = convertHrtime(process.hrtime.bigint())
67-
for (const [key] of Object.entries(simpleData)) {
68-
i18n.global.t(key)
69-
}
70-
end = convertHrtime(process.hrtime.bigint())
71-
console.log(`ms: ${end.milliseconds - start.milliseconds}`)
38+
i18n = createI18n({
39+
legacy: false,
40+
locale: 'en',
41+
messages: {
42+
en: data
43+
}
44+
})
45+
for (const [key] of Object.entries(data)) {
46+
i18n.global.t(key)
47+
}
48+
})
49+
.add(`resolve time on composition with compile cache`, () => {
50+
for (const [key] of Object.entries(data)) {
51+
i18n.global.t(key)
52+
}
53+
})
54+
.on('error', event => {
55+
console.log(String(event.target))
56+
})
57+
.on('cycle', event => {
58+
console.log(String(event.target))
59+
})
60+
.run()
7261
}
7362

74-
;(async () => {
75-
try {
76-
await run()
77-
} catch (e) {
78-
console.error(e)
79-
}
80-
})()
63+
main().catch(err => {
64+
console.error(err)
65+
process.exit(1)
66+
})

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,9 @@
8181
"@vitest/coverage-c8": "^0.31.1",
8282
"algoliasearch": "^4.9.0",
8383
"api-docs-gen": "^0.4.0",
84+
"benchmark": "^2.1.4",
8485
"brotli": "^1.3.2",
8586
"bumpp": "^9.1.0",
86-
"convert-hrtime": "^5.0.0",
8787
"cross-env": "^7.0.3",
8888
"esbuild-register": "^3.0.0",
8989
"eslint": "^8.41.0",
@@ -99,6 +99,7 @@
9999
"lint-staged": "^12.0.0",
100100
"listhen": "^1.0.4",
101101
"minimist": "^1.2.5",
102+
"mlly": "^1.3.0",
102103
"npm-run-all": "^4.1.5",
103104
"opener": "^1.5.2",
104105
"pathe": "^1.0.0",

packages/core-base/src/runtime.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import {
55
isObject,
66
isString,
77
isArray,
8-
isPlainObject
8+
isPlainObject,
9+
join
910
} from '@intlify/shared'
1011
import { HelperNameMap } from '@intlify/message-compiler'
1112
import { Path } from './resolver'
@@ -120,7 +121,7 @@ const DEFAULT_MODIFIER = (str: string): string => str
120121
const DEFAULT_MESSAGE = (ctx: MessageContext<string>): string => '' // eslint-disable-line
121122
export const DEFAULT_MESSAGE_DATA_TYPE = 'text'
122123
const DEFAULT_NORMALIZE = (values: string[]): string =>
123-
values.length === 0 ? '' : values.join('')
124+
values.length === 0 ? '' : join(values)
124125
const DEFAULT_INTERPOLATE = toDisplayString
125126

126127
function pluralDefault(choice: number, choicesLength: number): number {

packages/message-compiler/src/generator.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isString } from '@intlify/shared'
1+
import { isString, join } from '@intlify/shared'
22
import { SourceMapGenerator, RawSourceMap } from 'source-map'
33
import {
44
ResourceNode,
@@ -292,7 +292,10 @@ export const generate = (
292292

293293
if (helpers.length > 0) {
294294
generator.push(
295-
`const { ${helpers.map(s => `${s}: _${s}`).join(', ')} } = ctx`
295+
`const { ${join(
296+
helpers.map(s => `${s}: _${s}`),
297+
', '
298+
)} } = ctx`
296299
)
297300
generator.newline()
298301
}

packages/shared/src/utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,13 @@ export const toDisplayString = (val: unknown): string => {
166166
: String(val)
167167
}
168168

169+
export function join(items: string[], separator = ''): string {
170+
return items.reduce(
171+
(str, item, index) => (index === 0 ? str + item : str + separator + item),
172+
''
173+
)
174+
}
175+
169176
const RANGE = 2
170177

171178
export function generateCodeFrame(

0 commit comments

Comments
 (0)