From 77634e511178e2f20d52fc0d2141323a6df28b3a Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 9 Jul 2026 03:25:25 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[=EC=84=B1=EB=8A=A5=20?= =?UTF-8?q?=EA=B0=9C=EC=84=A0]=20=EB=B6=88=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20I/O=20?= =?UTF-8?q?=EB=B0=8F=20=EC=A0=95=EB=A0=AC=20=EC=97=B0=EC=82=B0=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * `process_dir` 내에서 파일 시스템 호출(예: `Files.isDirectory`) 전 저비용의 파일 제외 검사(문자열 기반)를 먼저 수행하도록 단락 평가(short-circuit) 적용 * `process_ignore_file`에서 불필요한 `sorted()` 제거 및 정규식 매치 시 `break` 추가로 조기 종료 구현 --- .jules/bolt.md | 3 +++ src/main/kotlin/html4tree/main.kt | 30 +++++++++++++++++++----------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index a9253e7..48cb88e 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -12,3 +12,6 @@ ## 2024-07-08 - URL Encoding String Allocation Bottleneck **Learning:** `byte.toString(16).padStart(2, '0').toUpperCase()` inside a loop allocating up to 3 strings per reserved byte in a hot path causes significant GC pressure. This is a common but dangerous anti-pattern in Kotlin when processing large strings or numerous files in directory crawlers. **Action:** Replace chained string operations with direct character mapping and bitwise operations (`ushr`, `and`) when building formatted hex output, which avoids intermediate string creation entirely. Ensure 100% branch coverage with test inputs spanning both < 10 and > 9 hex values. +## 2024-07-09 - Short-circuiting File IO Operations and Avoiding Repeated Allocations +**Learning:** In Kotlin/Java, checking file properties (like `isDirectory` or `isSymbolicLink`) via `java.nio.file.Files` requires allocating `Path` objects (via `toPath()`) and performs expensive native OS stat calls. When processing a directory listing, performing these IO operations before checking if the file is excluded creates significant overhead. +**Action:** Always short-circuit filesystem checks by testing against exclusion lists (using cheap in-memory operations like `name` access) BEFORE calling methods that touch the filesystem. Additionally, cache the result of `toPath()` in a local variable if it needs to be used multiple times for the same file. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 7339326..e0497dc 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -129,13 +129,16 @@ fun process_ignore_file(curr_dir: File): Set { } } - curr_dir.list()?.sorted()?.forEach { + // ⚡ Bolt Performance Optimization: Remove sorting and add early exit + // Sorting is unnecessary for a set, and we can stop regex matching once a match is found. + curr_dir.list()?.forEach { val current = it - ignored_regexes.forEach { regex -> + for (regex in ignored_regexes) { if(regex.matches(current)){ files_to_exclude.add(current) + break } - } + } } } @@ -205,14 +208,19 @@ fun process_dir(curr_dir: File){ val dir_files: MutableList = curr_dir.listFiles()?.toMutableList() ?: mutableListOf() dir_files.sortWith(compareBy ({it.name}) ) dir_files.forEach { - val isLinkedDirectory = Files.isDirectory(it.toPath(), LinkOption.NOFOLLOW_LINKS) - if((it.getName() !in exclude) && (isLinkedDirectory || !it.isDirectory()) && !Files.isSymbolicLink(it.toPath())) { - val fileName = it.getName() - val encodedHref = if (isLinkedDirectory) { "./${fileName.urlEncodePath()}/" } else { "./${fileName.urlEncodePath()}" } - val ariaLabel = "${fileName} ${if (isLinkedDirectory) { "디렉토리" } else { "파일" }}".escapeHtml() - val icon = if (isLinkedDirectory) { "📁" } else { "▸" } - l.append("""
  • ${fileName.escapeHtml()}
  • """) - l.append('\n') + val fileName = it.getName() + if (fileName !in exclude) { + // ⚡ Bolt Performance Optimization: Short-circuit file system calls + // Only call toPath() and query file system properties if the file is not excluded. + val path = it.toPath() + val isLinkedDirectory = Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS) + if((isLinkedDirectory || !it.isDirectory()) && !Files.isSymbolicLink(path)) { + val encodedHref = if (isLinkedDirectory) { "./${fileName.urlEncodePath()}/" } else { "./${fileName.urlEncodePath()}" } + val ariaLabel = "${fileName} ${if (isLinkedDirectory) { "디렉토리" } else { "파일" }}".escapeHtml() + val icon = if (isLinkedDirectory) { "📁" } else { "▸" } + l.append("""
  • ${fileName.escapeHtml()}
  • """) + l.append('\n') + } } }