From 66e18cbc0668069b2a7a130779bd486d6295d21e Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 7 Jul 2026 20:57:16 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20urlEncodePath()?= =?UTF-8?q?=EC=9D=98=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=ED=95=A0=EB=8B=B9=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 루프 내에서 `byte.toString(16).padStart(2, '0').toUpperCase()`를 사용할 때 발생하는 불필요한 다중 중간 문자열 객체 할당을 방지하기 위해, 비트 연산 및 미리 정의된 16진수 문자 배열을 사용하여 `urlEncodePath()`의 인코딩 성능을 개선했습니다. 또한 인코딩이 필요 없는 문자열에 대해서는 `StringBuilder`를 초기화하지 않도록 지연 할당을 적용했습니다. --- .jules/bolt.md | 4 ++++ src/main/kotlin/html4tree/main.kt | 34 ++++++++++++++++++------------- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 6904de2..095476e 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -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-08-01 - Hex Formatting Allocations in Loops +**Learning:** Using `byte.toString(16).padStart(2, '0').toUpperCase()` to format hex strings inside a loop generates multiple intermediate object allocations per byte (string, padded string, upper-cased string). In paths like URL encoding, this significantly degrades performance and increases GC pressure. +**Action:** Replace dynamic string methods with bitwise shifts and array lookups (`hexChars[byte ushr 4]`, `hexChars[byte and 0x0F]`) to write percent-encoded characters directly to a `StringBuilder`. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 1710d99..f14a515 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -80,25 +80,31 @@ fun String.escapeHtml(): String { return sb?.toString() ?: this } +// ⚡ Bolt Performance Optimization: Single-pass loop with lazy StringBuilder +// Avoids multiple string allocations during byte.toString(16).padStart(2, '0').toUpperCase() +// and skips StringBuilder creation if no encoding is needed. 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) + var sb: StringBuilder? = null + val hexChars = "0123456789ABCDEF" + for (i in bytes.indices) { + val byte = bytes[i].toInt() and 0xff + val char = byte.toChar() + val isUnreserved = (char in 'A'..'Z') || (char in 'a'..'z') || (char in '0'..'9') || + char == '-' || char == '.' || char == '_' || char == '~' if (isUnreserved) { - encoded.append(byte.toChar()) + sb?.append(char) } else { - encoded.append('%') - encoded.append(byte.toString(16).padStart(2, '0').toUpperCase()) + if (sb == null) { + sb = StringBuilder(bytes.size + 16) + for (j in 0 until i) sb.append(bytes[j].toChar()) + } + sb.append('%') + sb.append(hexChars[byte ushr 4]) + sb.append(hexChars[byte and 0x0F]) } } - return encoded.toString() + return sb?.toString() ?: this } fun process_ignore_file(curr_dir: File): Set { From a0e517a904d8cf58aa041d94a8528ac540086990 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:14:09 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20urlEncodePath()?= =?UTF-8?q?=EC=9D=98=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=ED=95=A0=EB=8B=B9=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 루프 내에서 `byte.toString(16).padStart(2, '0').toUpperCase()`를 사용할 때 발생하는 불필요한 다중 중간 문자열 객체 할당을 방지하기 위해, 비트 연산 및 미리 정의된 16진수 문자 배열을 사용하여 `urlEncodePath()`의 인코딩 성능을 개선했습니다. 또한 인코딩이 필요 없는 문자열에 대해서는 `StringBuilder`를 초기화하지 않도록 지연 할당을 적용했습니다. From bfcf77ed5a3ad50a7ce5a8bc549d939f72b23566 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 9 Jul 2026 10:04:09 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20urlEncodePath()?= =?UTF-8?q?=EC=9D=98=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=ED=95=A0=EB=8B=B9=20?= =?UTF-8?q?=EC=84=B1=EB=8A=A5=20=EC=B5=9C=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 루프 내에서 `byte.toString(16).padStart(2, '0').toUpperCase()`를 사용할 때 발생하는 불필요한 다중 중간 문자열 객체 할당을 방지하기 위해, 비트 연산 및 미리 정의된 16진수 문자 배열을 사용하여 `urlEncodePath()`의 인코딩 성능을 개선했습니다. 또한 인코딩이 필요 없는 문자열에 대해서는 `StringBuilder`를 초기화하지 않도록 지연 할당을 적용했습니다.