Skip to content

Commit 4c5b501

Browse files
committed
refactor: remove config of string codec threshold
BREAKING CHANGES: Calls to `TextDecoder.decode`, `TextEncoder.encode` have a fixed cost. This cost outperforms the native perf to decode/encode small strings. _bare-ts_ uses a custom implementation to decode and encode small strs. The choice between custom and native codecs is based on thresholds. These threshold were configurable via `textDecoderThreshold` and `textEncoderThreshold` config properties. This is not clear whether these configurations are worth to expose. Most of decoded and encoded strings are small. Fixed thresholds seem fair enough.
1 parent 859b06b commit 4c5b501

File tree

7 files changed

+56
-53
lines changed

7 files changed

+56
-53
lines changed

Diff for: CHANGELOG.md

+13
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,19 @@ This project adheres to [Semantic Versioning][semver].
2020
+ bare.readUnsafeU8FixedArray(bc, 5)
2121
```
2222

23+
- BREAKING CHANGES: remove `textDecoderThreshold` and `textEncoderThreshold` configuration
24+
25+
Calls to native `TextDecoder.decode` and `TextEncoder.encode` have a fixed cost.
26+
This cost outperforms the native performance to decode and encode small strings.
27+
28+
_bare-ts_ uses a custom implementation to decode and encode small strings.
29+
The choice between the custom and the native codecs is based on thresholds.
30+
These threshold were configurable via `textDecoderThreshold` and `textEncoderThreshold` config properties.
31+
32+
This is not clear whether this configuration is worth to expose.
33+
Most of decoded and encoded strings are small.
34+
Fixed thresholds seem fair enough.
35+
2336
- Assertions and development mode
2437

2538
Previously, bare-ts enabled a few assertions to check some function arguments.

Diff for: src/codec/string.ts

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { BareError } from "../core/bare-error.js"
22
import { type ByteCursor, check, reserve } from "../core/byte-cursor.js"
33
import { DEV, assert } from "../util/assert.js"
4-
import { INVALID_UTF8_STRING } from "../util/constants.js"
4+
import {
5+
INVALID_UTF8_STRING,
6+
TEXT_DECODER_THRESHOLD,
7+
TEXT_ENCODER_THRESHOLD,
8+
} from "../util/constants.js"
59
import { isU32 } from "../util/validator.js"
610
import { readUintSafe32, writeUintSafe32 } from "./primitive.js"
711
import { readUnsafeU8FixedArray, writeU8FixedArray } from "./u8-array.js"
@@ -11,7 +15,7 @@ export function readString(bc: ByteCursor): string {
1115
}
1216

1317
export function writeString(bc: ByteCursor, x: string): void {
14-
if (x.length < bc.config.textEncoderThreshold) {
18+
if (x.length < TEXT_ENCODER_THRESHOLD) {
1519
const byteLen = utf8ByteLength(x)
1620
writeUintSafe32(bc, byteLen)
1721
reserve(bc, byteLen)
@@ -27,7 +31,7 @@ export function readFixedString(bc: ByteCursor, byteLen: number): string {
2731
if (DEV) {
2832
assert(isU32(byteLen))
2933
}
30-
if (byteLen < bc.config.textDecoderThreshold) {
34+
if (byteLen < TEXT_DECODER_THRESHOLD) {
3135
return readUtf8Js(bc, byteLen)
3236
}
3337
try {
@@ -38,7 +42,7 @@ export function readFixedString(bc: ByteCursor, byteLen: number): string {
3842
}
3943

4044
export function writeFixedString(bc: ByteCursor, x: string): void {
41-
if (x.length < bc.config.textEncoderThreshold) {
45+
if (x.length < TEXT_ENCODER_THRESHOLD) {
4246
const byteLen = utf8ByteLength(x)
4347
reserve(bc, byteLen)
4448
writeUtf8Js(bc, x)

Diff for: src/core/config.ts

-8
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,15 @@ import { isU32 } from "../util/validator.js"
55
export type Config = {
66
readonly initialBufferLength: number
77
readonly maxBufferLength: number
8-
readonly textDecoderThreshold: number
9-
readonly textEncoderThreshold: number
108
}
119

1210
export function Config({
1311
initialBufferLength = 1024,
1412
maxBufferLength = 1024 * 1024 * 32 /* 32 MiB */,
15-
textDecoderThreshold = 256,
16-
textEncoderThreshold = 256,
1713
}): Config {
1814
if (DEV) {
1915
assert(isU32(initialBufferLength), TOO_LARGE_NUMBER)
2016
assert(isU32(maxBufferLength), TOO_LARGE_NUMBER)
21-
assert(isU32(textDecoderThreshold), TOO_LARGE_NUMBER)
22-
assert(isU32(textEncoderThreshold), TOO_LARGE_NUMBER)
2317
assert(
2418
initialBufferLength <= maxBufferLength,
2519
"initialBufferLength must be lower than or equal to maxBufferLength",
@@ -28,7 +22,5 @@ export function Config({
2822
return {
2923
initialBufferLength,
3024
maxBufferLength,
31-
textDecoderThreshold,
32-
textEncoderThreshold,
3325
}
3426
}

Diff for: src/util/constants.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
export const TEXT_DECODER_THRESHOLD = 256
2+
export const TEXT_ENCODER_THRESHOLD = 256
3+
14
export const INT_SAFE_MAX_BYTE_COUNT = 8
25
export const UINT_MAX_BYTE_COUNT = 10
36
export const UINT_SAFE32_MAX_BYTE_COUNT = 5

Diff for: tests/codec/_util.d.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
import { ByteCursor, Config } from "@bare-ts/lib"
2-
3-
export function fromConfigBytes(
4-
partConfig: Partial<Config>,
5-
...rest: number[]
6-
): ByteCursor
1+
import { ByteCursor } from "@bare-ts/lib"
72

83
export function fromBytes(...rest: number[]): ByteCursor
94

Diff for: tests/codec/_util.js

-10
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
import { ByteCursor, Config } from "@bare-ts/lib"
22

3-
/**
4-
*
5-
* @param {Partial<Config>} partConfig
6-
* @param {...number[]} rest
7-
* @returns {ByteCursor}
8-
*/
9-
export function fromConfigBytes(partConfig, ...rest) {
10-
return new ByteCursor(Uint8Array.from(rest), Config(partConfig))
11-
}
12-
133
/**
144
*
155
* @param {...number[]} rest

Diff for: tests/codec/string.test.js

+31-25
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
11
import * as bare from "@bare-ts/lib"
22
import { default as test } from "oletus"
33

4-
import { fromBytes, fromConfigBytes, toBytes } from "./_util.js"
4+
import { fromBytes, toBytes } from "./_util.js"
55

6-
test("bare.readString", (t) => {
7-
let bc = fromConfigBytes(
8-
{ textDecoderThreshold: 0 },
9-
/* byteLength */ 4,
10-
98,
11-
97,
12-
114,
13-
101,
6+
test("bare.readFixedString", (t) => {
7+
let bc = fromBytes(
8+
..."b"
9+
.repeat(300)
10+
.split("")
11+
.map((s) => s.charCodeAt(0)),
12+
)
13+
t.deepEqual(
14+
bare.readFixedString(bc, 300),
15+
"b".repeat(300),
16+
"can natively read ASCII",
1417
)
15-
t.deepEqual(bare.readString(bc), "bare", "can natively read ASCII")
1618

17-
bc = fromBytes(/* byteLength */ 4, 98, 97, 114, 101)
19+
bc = fromBytes(98, 97, 114, 101)
20+
t.deepEqual(bare.readFixedString(bc, 4), "bare", "can read ASCII")
21+
})
22+
23+
test("bare.readString", (t) => {
24+
let bc = fromBytes(/* byteLength */ 4, 98, 97, 114, 101)
1825
t.deepEqual(bare.readString(bc), "bare", "can read ASCII")
1926

2027
bc = fromBytes(/* byteLength */ 6, 0xc3, 0xa9, 0xc3, 0xa0, 0xc3, 0xb9)
@@ -167,16 +174,25 @@ test("bare.readString", (t) => {
167174
)
168175
})
169176

170-
test("bare.writeString", (t) => {
171-
let bc = fromConfigBytes({ textEncoderThreshold: 0 })
172-
bare.writeString(bc, "bare")
177+
test("bare.writeFixedString", (t) => {
178+
let bc = fromBytes()
179+
bare.writeFixedString(bc, "b".repeat(300))
173180
t.deepEqual(
174181
toBytes(bc),
175-
[/* byteLength */ 4, 98, 97, 114, 101],
182+
"b"
183+
.repeat(300)
184+
.split("")
185+
.map((s) => s.charCodeAt(0)),
176186
"can natively write ASCII",
177187
)
178188

179189
bc = fromBytes()
190+
bare.writeFixedString(bc, "bare")
191+
t.deepEqual(toBytes(bc), [98, 97, 114, 101], "can write ASCII")
192+
})
193+
194+
test("bare.writeString", (t) => {
195+
let bc = fromBytes()
180196
bare.writeString(bc, "bare")
181197
t.deepEqual(
182198
toBytes(bc),
@@ -270,13 +286,3 @@ test("bare.writeString", (t) => {
270286
],
271287
)
272288
})
273-
274-
test("bare.writeFixedString", (t) => {
275-
let bc = fromConfigBytes({ textEncoderThreshold: 0 })
276-
bare.writeFixedString(bc, "bare")
277-
t.deepEqual(toBytes(bc), [98, 97, 114, 101], "can natively write ASCII")
278-
279-
bc = fromBytes()
280-
bare.writeFixedString(bc, "bare")
281-
t.deepEqual(toBytes(bc), [98, 97, 114, 101], "can write ASCII")
282-
})

0 commit comments

Comments
 (0)