From c9fd8188d1cd15228b0174dd3ee64bde38b0f383 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:53:12 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=94=94=EB=A0=89=ED=86=A0?= =?UTF-8?q?=EB=A6=AC=20=ED=81=AC=EB=A1=A4=EB=A7=81=20=EB=B0=8F=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=20=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1=20=EC=84=B1?= =?UTF-8?q?=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 불필요한 I/O 호출 및 파일 객체 생성을 줄이고 불필요한 정렬 작업을 제거하여 성능을 향상시켰습니다. - `process_ignore_file`에서 불필요한 `.sorted()` 호출을 제거하고 `.any`를 사용하여 제외 규칙 검사 최적화 (O(N log N) 오버헤드 감소). - `process_dir`에서 파일이 제외 목록에 있는지 먼저 확인하여, 제외된 파일에 대한 불필요한 `Path` 객체 생성 및 비용이 높은 `Files.isDirectory`, `Files.isSymbolicLink` I/O 시스템 호출을 방지. - 불필요한 `it.toPath()` 중복 호출 제거. - `.jules/bolt.md`에 성능 관련 주요 학습 내용(Critical Learnings) 추가. --- .jules/bolt.md | 8 ++++++++ src/main/kotlin/html4tree/main.kt | 30 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index a9253e7..cf9c417 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -12,3 +12,11 @@ ## 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-29 - Excessive File Object and I/O Operations in Filter Loops +**Learning:** In directory crawlers, calling `.listFiles()` loads `File` objects for every item, and subsequently calling `it.toPath()` and expensive I/O operations (`Files.isDirectory`, `Files.isSymbolicLink`) *before* checking exclusions creates significant overhead on large directories with many excluded files. +**Action:** Always check file names against exclusion sets (`if (fileName !in exclude)`) *before* allocating `Path` objects or executing I/O checks. + +## 2024-07-29 - Unnecessary Sorting in Set Accumulation +**Learning:** Calling `.sorted()` on a directory listing (`curr_dir.list()?.sorted()?.forEach`) before adding elements to a `Set` or checking against a list of RegExes wastes O(N log N) time and allocates unnecessary arrays. +**Action:** Omit `.sorted()` when order is irrelevant (e.g., when populating an exclusion `Set`) and use `.any` to short-circuit regex checking. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index ebab59d..8e05f93 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -129,13 +129,10 @@ fun process_ignore_file(curr_dir: File): Set { } } - curr_dir.list()?.sorted()?.forEach { - val current = it - ignored_regexes.forEach { regex -> - if(regex.matches(current)){ - files_to_exclude.add(current) - } - } + curr_dir.list()?.forEach { current -> + if (ignored_regexes.any { it.matches(current) }) { + files_to_exclude.add(current) + } } } @@ -215,14 +212,17 @@ 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) { + 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') + } } }