From b97dc525f496826b7ae0d3b97b4e86ba8ab5d998 Mon Sep 17 00:00:00 2001 From: seonghobae <8172694+seonghobae@users.noreply.github.com> Date: Sat, 11 Jul 2026 21:04:59 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EB=94=94=EB=A0=89=ED=86=A0=EB=A6=AC=20=EB=AA=A9?= =?UTF-8?q?=EB=A1=9D=20=EC=A1=B0=ED=9A=8C=20I/O=20=EC=A0=9C=EA=B1=B0?= =?UTF-8?q?=EB=A5=BC=20=ED=86=B5=ED=95=9C=20=EC=84=B1=EB=8A=A5=20=EC=B5=9C?= =?UTF-8?q?=EC=A0=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 3 ++ src/main/kotlin/html4tree/main.kt | 23 +++++++++------ src/test/kotlin/html4tree/MainTest.kt | 40 +++++++++++++++++---------- 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 82fe47b..5cf5ec7 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -13,3 +13,6 @@ **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. ## 2026-07-10 - Expensive OS stat calls before cheap in-memory checks\n**Learning:** In Kotlin/Java, checking file properties (like `isDirectory` or `isSymbolicLink`) via `java.nio.file.Files` requires allocating `Path` objects and performs expensive native OS stat calls. When processing file listings, always short-circuit filesystem checks by testing against exclusion lists (using cheap in-memory string operations) before calling methods that touch the filesystem.\n**Action:** Re-ordered conditionals to check `exclude` sets before invoking `Files.isDirectory` and `Files.isSymbolicLink`. +## 2024-07-11 - [Eliminating Redundant Directory Listings] +**Learning:** In nested file processing functions like `process_dir` and `process_ignore_file`, executing `curr_dir.listFiles()` repeatedly for the same directory is highly inefficient and creates expensive duplicate OS syscalls. +**Action:** Always hoist I/O intensive listing operations like `listFiles()` to the highest appropriate scope and pass the result as a parameter to child functions rather than re-evaluating it. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 936c2b7..3141b09 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -40,11 +40,16 @@ fun go(topDir: String, maxLevel: Int) { while(lle != null && Files.isDirectory(lle.file.toPath(), LinkOption.NOFOLLOW_LINKS)){ val currentLevel: Int = lle.level + // ⚡ Bolt Performance Optimization: Hoist listFiles() out of nested functions + // By fetching directory contents once, we eliminate redundant OS syscalls + // in process_dir and process_ignore_file (saves 2x I/O calls per directory). + val dir_files = lle.file.listFiles()?.toList() ?: emptyList() + if(maxLevel == -1 || currentLevel <= maxLevel) - process_dir(lle.file) + process_dir(lle.file, dir_files) if(maxLevel == -1 || currentLevel < maxLevel) { - lle.file.listFiles()?.forEach { + dir_files.forEach { if(Files.isDirectory(it.toPath(), LinkOption.NOFOLLOW_LINKS)){ ll.push( LinkedListEntry(it, currentLevel+1)) } @@ -109,7 +114,7 @@ fun String.urlEncodePath(): String { return encoded.toString() } -fun process_ignore_file(curr_dir: File): Set { +fun process_ignore_file(curr_dir: File, dir_files: List): Set { val ignore_filename = ".html4ignore" @@ -138,7 +143,7 @@ fun process_ignore_file(curr_dir: File): Set { } } - curr_dir.list()?.sorted()?.forEach { + dir_files.map { it.name }.sorted().forEach { val current = it ignored_regexes.forEach { regex -> if(regex.matches(current)){ @@ -169,9 +174,9 @@ fun write_index_file(curr_dir: File, content: String) { } } -fun process_dir(curr_dir: File){ +fun process_dir(curr_dir: File, dir_files: List){ - val exclude: Set = process_ignore_file(curr_dir) + val exclude: Set = process_ignore_file(curr_dir, dir_files) val css = """