From 9187486a5fb96d6c2bac6e4ecd44f7c8e028f723 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Tue, 7 Jul 2026 20:57:28 +0000 Subject: [PATCH 1/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20urlEncodePath()=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94=20-=20=EB=AC=B8=EC=9E=90=EC=97=B4?= =?UTF-8?q?=20=ED=95=A0=EB=8B=B9=20=EB=B0=8F=20=EB=A3=A8=ED=94=84=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 불필요한 문자열 객체 생성을 유발하는 `.toString(16).padStart(2, '0').toUpperCase()` 체이닝 대신 미리 계산된 `HEX_ARRAY`를 사용하도록 수정. 또한 `forEach` 대신 `bytes.indices` 루프를 사용하고 `StringBuilder`의 크기를 미리 할당하여 속도와 메모리 효율성 개선. --- .jules/bolt.md | 3 +++ src/main/kotlin/html4tree/main.kt | 29 +++++++++++++++++---------- src/test/kotlin/html4tree/MainTest.kt | 6 ++++++ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 6904de2..81ed0ea 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -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. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 1710d99..42a9d1d 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -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. +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() diff --git a/src/test/kotlin/html4tree/MainTest.kt b/src/test/kotlin/html4tree/MainTest.kt index a3f8c76..6078b8e 100644 --- a/src/test/kotlin/html4tree/MainTest.kt +++ b/src/test/kotlin/html4tree/MainTest.kt @@ -310,4 +310,10 @@ class MainTest { assertTrue(excluded.contains("test.txt")) } + + @Test + fun testGetHexArray() { + assertEquals('0', HEX_ARRAY[0]) + assertEquals('F', HEX_ARRAY[15]) + } } From de4bddfdb3cf6d2893f8efb5faac0e6a2bb2fd73 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Wed, 8 Jul 2026 09:39:02 +0000 Subject: [PATCH 2/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20urlEncodePath()=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94=20-=20=EB=AC=B8=EC=9E=90=EC=97=B4?= =?UTF-8?q?=20=ED=95=A0=EB=8B=B9=20=EB=B0=8F=20=EB=A3=A8=ED=94=84=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 불필요한 문자열 객체 생성을 유발하는 `.toString(16).padStart(2, '0').toUpperCase()` 체이닝 대신 미리 계산된 `HEX_ARRAY`를 사용하도록 수정. 또한 `forEach` 대신 `bytes.indices` 루프를 사용하고 `StringBuilder`의 크기를 미리 할당하여 속도와 메모리 효율성 개선. From 6e0c605b9591efdeacc29d1c2ec28ee9a5d4e79c Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 9 Jul 2026 09:31:50 +0000 Subject: [PATCH 3/3] =?UTF-8?q?=E2=9A=A1=20Bolt:=20urlEncodePath()=20?= =?UTF-8?q?=EC=B5=9C=EC=A0=81=ED=99=94=20-=20=EB=AC=B8=EC=9E=90=EC=97=B4?= =?UTF-8?q?=20=ED=95=A0=EB=8B=B9=20=EB=B0=8F=20=EB=A3=A8=ED=94=84=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 불필요한 문자열 객체 생성을 유발하는 `.toString(16).padStart(2, '0').toUpperCase()` 체이닝 대신 미리 계산된 `HEX_ARRAY`를 사용하도록 수정. 또한 `forEach` 대신 `bytes.indices` 루프를 사용하고 `StringBuilder`의 크기를 미리 할당하여 속도와 메모리 효율성 개선. --- src/main/kotlin/html4tree/main.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 42a9d1d..21d81a7 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -83,7 +83,7 @@ fun String.escapeHtml(): String { // ⚡ 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. -val HEX_ARRAY = "0123456789ABCDEF".toCharArray() +internal val HEX_ARRAY = "0123456789ABCDEF".toCharArray() fun String.urlEncodePath(): String { val bytes = this.toByteArray(Charsets.UTF_8)