Skip to content
Closed
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
4 changes: 4 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@
## 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-07-28 - Number Formatting Object Allocations
**Learning:** Using built-in chained methods like `.toString(16).padStart(2, '0').toUpperCase()` inside a tight loop for URL encoding bytes creates excessive short-lived `String` objects, degrading performance and increasing GC pressure.
**Action:** Replace high-frequency byte-to-hex formatting with direct lookup arrays and bitwise operations (`hex[byte ushr 4]` and `hex[byte and 0x0F]`) to emit characters directly without intermediate objects.
27 changes: 21 additions & 6 deletions src/main/kotlin/html4tree/main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -80,25 +80,40 @@ fun String.escapeHtml(): String {
return sb?.toString() ?: this
}

// ⚡ Bolt Performance Optimization: Single-pass loop with lazy StringBuilder and no intermediate strings
// 💡 What: Lazy StringBuilder allocation and direct hex conversion using bitwise operations
// 🎯 Why: Previously, chained string methods (toString.padStart.toUpperCase) created multiple short-lived objects per encoded byte, and StringBuilder was always allocated.
// 📊 Impact: Significantly reduces GC pressure and memory allocations when encoding URLs, especially for ASCII paths which now allocate nothing.
// 🔬 Measurement: Verify with profiler during generation on large directory trees with many special characters.
fun String.urlEncodePath(): String {
val encoded = StringBuilder()
this.toByteArray(Charsets.UTF_8).forEach {
val byte = it.toInt() and 0xff
var encoded: StringBuilder? = null
val bytes = this.toByteArray(Charsets.UTF_8)
for (i in bytes.indices) {
val byte = bytes[i].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()

if (isUnreserved) {
encoded.append(byte.toChar())
encoded?.append(byte.toChar())
} else {
if (encoded == null) {
encoded = StringBuilder(bytes.size + 16)
for (j in 0 until i) {
encoded.append(bytes[j].toInt().toChar())
}
}
encoded.append('%')
encoded.append(byte.toString(16).padStart(2, '0').toUpperCase())
val hex = "0123456789ABCDEF"
encoded.append(hex[byte ushr 4])
encoded.append(hex[byte and 0x0F])
}
}
return encoded.toString()
return encoded?.toString() ?: this
}

fun process_ignore_file(curr_dir: File): Set<String> {
Expand Down
Loading