diff --git a/.jules/bolt.md b/.jules/bolt.md index ee124e69..3e807d02 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -62,3 +62,7 @@ ## 2026-08-11 - Array의 toMutableList 할당 오버헤드 최적화 **학습:** 배열을 정렬하기 위해 `.toMutableList()`를 호출하면 새로운 `ArrayList` 객체와 내부 배열 객체가 할당되어 대규모 디렉토리를 순회할 때 가비지 컬렉션(GC) 부하를 유발합니다. 배열 복제가 필요한 경우 `.clone()`을 사용하면 하나의 배열 객체만 새로 할당되므로 더 효율적입니다. **조치:** 디렉토리 파일 배열을 정렬하기 전에 복사할 때 `.toMutableList()` 대신 `.clone()`을 사용하여 불필요한 중간 컬렉션 할당을 제거하고 성능을 향상시켰습니다. + +## 2024-05-18 - [디렉토리 목록 탐색 시 정적 리스트 Array 변환으로 성능 최적화] +**Learning:** `process_ignore_file` 내부에서 정적 리스트에 대한 `any` 순회 시 `List.any`는 매 순회마다 Iterator 객체를 할당합니다. 이러한 방식은 대규모 디렉토리를 탐색할 때 가비지 컬렉션(GC) 부하를 가중시킵니다. +**Action:** `Constants.defaultSensitiveExtensions`와 같은 정적인 `List`를 순회할 때는 `toTypedArray()`를 사용하여 미리 배열로 변환하고 `Array.any`를 사용하도록 최적화합니다. `Array.any`는 인라인 함수로 제공되어 컴파일 시 프리미티브 배열의 인덱스 기반 루프로 변환되므로 불필요한 Iterator 할당을 제거하고 성능을 크게 향상시킵니다. diff --git a/src/main/kotlin/html4tree/main.kt b/src/main/kotlin/html4tree/main.kt index 0972fa2c..df9ec814 100644 --- a/src/main/kotlin/html4tree/main.kt +++ b/src/main/kotlin/html4tree/main.kt @@ -356,7 +356,7 @@ fun process_ignore_file(curr_dir: File, dirFilesNames: Array? = null): S it.isHiddenFile() || normalizedName in Constants.defaultSensitiveFileNamesLowercase || normalizedName.endsWith("~") || - Constants.defaultSensitiveExtensions.any { extension -> + Constants.defaultSensitiveExtensionsArray.any { extension -> normalizedName.endsWith(extension) } ) { @@ -404,7 +404,7 @@ fun write_index_file( } fun process_dir(curr_dir: File, excludeSet: Set? = null, dirFiles: Array? = null){ - + val exclude: Set = excludeSet ?: process_ignore_file(curr_dir) val directoryName = curr_dir.name.ifEmpty { "Root" } @@ -526,4 +526,6 @@ private object Constants { ".swo", ".swpx" ) + + val defaultSensitiveExtensionsArray = defaultSensitiveExtensions.toTypedArray() } diff --git a/src/main/kotlin/html4tree/util.kt b/src/main/kotlin/html4tree/util.kt index fa25294c..7873cf58 100644 --- a/src/main/kotlin/html4tree/util.kt +++ b/src/main/kotlin/html4tree/util.kt @@ -1,47 +1,19 @@ package html4tree import java.io.File - -data class Entry (val data: File, val level: Int, var next: Entry?, val fileKey: Any? = null) +import java.util.ArrayDeque data class LinkedListEntry(val file: File, val level: Int, var fileKey: Any? = null) class LinkedList { - var first: Entry? = null - var last: Entry? = null + private val deque = ArrayDeque() fun push(lle: LinkedListEntry) { - if(last == null){ - last = Entry(lle.file, lle.level, null, lle.fileKey) - first = last - } else { - val nextEntry = Entry(lle.file, lle.level, null, lle.fileKey) - val currentFirst = first - if (currentFirst == null) { - var currentLast = last!! - while (currentLast.next != null) { - currentLast = currentLast.next!! - } - currentLast.next = nextEntry - } else { - currentFirst.next = nextEntry - } - first = nextEntry - } + // Performance optimization: Using ArrayDeque avoids allocating Entry wrapper nodes + deque.addLast(lle) } fun pull(): LinkedListEntry? { - val l: Entry? = last - if(l != null) { - last = l.next - } - - if(l == null){ - return null - } else { - l.next = null - return LinkedListEntry(l.data, l.level, l.fileKey) - } + return deque.pollFirst() } - } diff --git a/src/test/kotlin/html4tree/UtilTest.kt b/src/test/kotlin/html4tree/UtilTest.kt index 8230b3ed..ba39fc61 100644 --- a/src/test/kotlin/html4tree/UtilTest.kt +++ b/src/test/kotlin/html4tree/UtilTest.kt @@ -35,39 +35,6 @@ class UtilTest { assertNull(list.pull()) } - @Test - fun testEntryDataClass() { - val file1 = File("file1") - val entry1 = Entry(file1, 0, null) - val entry2 = Entry(file1, 0, null) - - assertEquals(entry1, entry2) - assertEquals("Entry(data=file1, level=0, next=null, fileKey=null)", entry1.toString()) - } - - @Test - fun testEntryDataClassGeneratedMembers() { - val file1 = File("file1") - val entry = Entry(file1, 0, null) - - assertEquals(entry, entry) - assertEquals(Entry(file1, 0, null).hashCode(), entry.hashCode()) - assertNotEquals(entry, "not an entry") - assertNotEquals(entry, Entry(File("file2"), 0, null)) - assertNotEquals(entry, Entry(file1, 1, null)) - assertNotEquals(entry, Entry(file1, 0, Entry(file1, 1, null))) - - val copied = entry.copy(level = 2) - assertEquals(file1, copied.data) - assertEquals(2, copied.level) - assertNull(copied.next) - - val (data, level, next) = entry - assertEquals(file1, data) - assertEquals(0, level) - assertNull(next) - } - @Test fun testLinkedListEntryDataClass() { val file1 = File("file1") @@ -110,25 +77,6 @@ class UtilTest { assertEquals(File("f2"), entry2?.file) } - @Test - fun testLinkedListAccessors() { - val list = LinkedList() - list.first = Entry(File("test"), 0, null) - list.last = Entry(File("test"), 0, null) - assertEquals(File("test"), list.first?.data) - assertEquals(File("test"), list.last?.data) - } - - @Test - fun testLinkedListPushNullFirst() { - val list = LinkedList() - list.last = Entry(File("fake"), 0, null) - list.push(LinkedListEntry(File("f3"), 0)) - assertEquals(File("fake"), list.pull()?.file) - assertEquals(File("f3"), list.pull()?.file) - assertEquals(File("f3"), list.first?.data) - } - @Test fun testLinkedListPreservesFileKey() { val key = Any() @@ -141,16 +89,4 @@ class UtilTest { assertEquals(1, pulled?.level) assertEquals(key, pulled?.fileKey) } - - @Test - fun testLinkedListPushNullFirstWithExistingChain() { - val list = LinkedList() - list.last = Entry(File("f1"), 0, Entry(File("f2"), 0, null)) - list.push(LinkedListEntry(File("f3"), 0)) - - assertEquals(File("f1"), list.pull()?.file) - assertEquals(File("f2"), list.pull()?.file) - assertEquals(File("f3"), list.pull()?.file) - assertNull(list.pull()) - } }