Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@
## 2024-07-26 - Intermediate String Allocations
**Learning:** Chained `.replace()` calls on strings in Kotlin (e.g. for HTML escaping) allocate an intermediate String at each step, significantly impacting performance and garbage collection on hot paths with many elements.
**Action:** Replace chained `.replace()` calls with a single-pass loop that iterates over characters once, lazily initializing a `StringBuilder` to append the transformed output.
## 2024-05-24 - Optimize URL encoding performance in Kotlin
**Learning:** Chained string manipulations like `.toString(16).padStart(2, '0').toUpperCase()` inside a tight loop create multiple intermediate string objects, leading to high memory allocation and slow execution. Also, using `.forEach` on a ByteArray creates an iterator object overhead compared to a direct loop using `indices`.
**Action:** Replace complex string manipulations for hex encoding with a pre-calculated `HEX_ARRAY` lookup and bitwise operators (`ushr 4` and `and 0x0f`), use `bytes.indices` for loops over primitive arrays to avoid iterator allocation, and pre-size StringBuilders based on expected output length (`bytes.size * 2`) to minimize resizing overhead.
29 changes: 18 additions & 11 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,29 @@ fun String.escapeHtml(): String {
return sb?.toString() ?: this
}

// ⚡ Bolt Performance Optimization: Use pre-calculated hex array and indices loop
// Reduces intermediate string allocations from .toString(16).padStart(2, '0').toUpperCase()
// and avoids iterator creation by using a direct index loop.
internal val HEX_ARRAY = "0123456789ABCDEF".toCharArray()

fun String.urlEncodePath(): String {
val encoded = StringBuilder()
this.toByteArray(Charsets.UTF_8).forEach {
val byte = it.toInt() and 0xff
val isUnreserved = (byte in 'A'.toInt()..'Z'.toInt()) ||
(byte in 'a'.toInt()..'z'.toInt()) ||
(byte in '0'.toInt()..'9'.toInt()) ||
byte == '-'.toInt() ||
byte == '.'.toInt() ||
byte == '_'.toInt() ||
byte == '~'.toInt()
val bytes = this.toByteArray(Charsets.UTF_8)
val encoded = java.lang.StringBuilder(bytes.size * 2)
for (i in bytes.indices) {
val byte = bytes[i].toInt() and 0xff
val isUnreserved = (byte in 65..90) || // 'A'..'Z'
(byte in 97..122) || // 'a'..'z'
(byte in 48..57) || // '0'..'9'
byte == 45 || // '-'
byte == 46 || // '.'
byte == 95 || // '_'
byte == 126 // '~'
if (isUnreserved) {
encoded.append(byte.toChar())
} else {
encoded.append('%')
encoded.append(byte.toString(16).padStart(2, '0').toUpperCase())
encoded.append(HEX_ARRAY[byte ushr 4])
encoded.append(HEX_ARRAY[byte and 0x0f])
}
}
return encoded.toString()
Expand Down
6 changes: 6 additions & 0 deletions src/test/kotlin/html4tree/MainTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -310,4 +310,10 @@ class MainTest {
assertTrue(excluded.contains("test.txt"))
}


@Test
fun testGetHexArray() {
assertEquals('0', HEX_ARRAY[0])
assertEquals('F', HEX_ARRAY[15])
}
}
Loading