(ꡬν) KGrep
π μμ± λ°°κ²½
μ΅κ·Όμ μ΄λ°μ λ° μ΄μ λ‘ λΌμ΄λΈ μ½λ©ν
μ€νΈμ κ΄μ¬μ κ°κ² λμλλ°, μΏ ν€λ°μΌλ‘ μ λͺ
ν λ°λΈμμ€ν°μ¦ λΌλ νμ¬μ λΌμ΄λΈ μ½ν
κ° μ λͺ
νλ€κ³
μ΄λμ λ€μ΄μ μ΄λ€ μμΌλ‘ μ§νλλμ§ κΆκΈνμ¬ μ°Ύμ보λ μ€ λ°λΈμμ€ν°μ¦μμ 22λ
λμ μμ±λ κ²μκΈμ νλ λ°κ²¬νκ² λμκ³ ,
ν΄λΉ κ²μκΈμ μλ λ¬Έμ μ€ grep
μ μ§μ ꡬνν΄λ³΄λ λ¬Έμ κ° μμ΄ μ£Όλ§μ μ κΉ νμ΄λ΄€λλ° μ¬λ―Έμκ² νμλ κΈ°μ΅μ΄ μμ΄
ν΄λΉ λ΄μ©μ λν΄ μμ±ν΄λ³΄λ €κ³ νλ€.
λ¬Έμ μ λν μμΈν λ΄μ© λ° κΆκΈν λ΄μ©μ μλ λ°λΈμμ€ν°μ¦ κ²μκΈμ μ°Έκ³ νλ©΄ μ’μκ² κ°λ€.
π μ 3μ€ μμ½
- KotlinμΌλ‘
grep
κ³Ό μ μ¬ν κΈ°λ₯μ νλ λ¬Έμμ΄ κ²μ λꡬλ₯Ό λ¨Όμ μ±κΈμ€λ λλ‘ κ΅¬νν ν λ©ν°μ€λ λλ‘ κ°μ νμλ€. - νμΌ νμκ³Ό λ¬Έμμ΄ κ²μμ λΆλ¦¬νκ³ νμΌ λ¨μλ‘ μ€λ λλ₯Ό ν λΉνλ λ°©μμΌλ‘ λ³λ ¬ μ²λ¦¬λ₯Ό ꡬννμλ€.
- λ©ν°μ€λ λ λ²μ (KGrepV2)μ μλ³Έ grepλ³΄λ€ μ½ 88% λΉ λ₯Έ μλ(1.16μ΄ vs 9.69μ΄)λ₯Ό 보μμΌλ, CPU μμμ μ½ 8.5λ°°(832% vs 98%) λ λ§μ΄ μ¬μ©νλ€.
ποΈ λ¬Έμ μꡬμ¬ν
μΏ ν€λ° νμ¬ λ΅κ² λ¬Έμ μλ μΏ ν€κ° λ±μ₯ νλκ±Έ λ³Ό μ μλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
μ©κ°ν μΏ ν€λ νμ μ¬μ©νλ νμΌ λ΄ λ¬Έμμ΄ κ²μ ν΄ grepλ³΄λ€ λ λΉ λ₯Έ μλλ₯Ό μλνλ ackλ agκ°μ λ€μν ν΄λ€μ΄ μλ€λ κ²μ λ€μλ€.
μμ λ§μ dgrepμ λ§λ€μ΄ λ리 μΈμμ μ΄λ‘κ² νκ³ μΆλ€κ³ μκ°νκ² λ μ©κ°ν μΏ ν€λ κ·Έ 첫 λ¨κ³λ‘ λ©ν°μ€λ λλ‘ λμνλ grepμ μ§μ λ§λ€μ΄λ³΄κΈ°λ‘ κ²°μ¬νμλ€.
μνλ μ€νμ λ§λ νμΌ λ΄ λ¬Έμμ΄ κ²μ ν΄μ λ§λ€μ΄λ³΄μ.
μ
λ ₯ νμ
- dgrep {keyword} {relative path}
μΆλ ₯ νμ
- νμΌμ κ° lineμ keywordκ° μλ κ²½μ°, ν΄λΉ νμΌκ³Ό μ€ λ²νΈλ₯Ό μΆλ ₯νλ€.
쑰건
1. relative pathκ° λλ ν λ¦¬μΈ κ²½μ° λλ ν 리 λ΄ λͺ¨λ νμΌμ λν΄ κ²μ¬λ₯Ό μ§ννλ€.
2. relative path λ΄μ λ λ€λ₯Έ λλ ν λ¦¬κ° μ‘΄μ¬νλ κ²½μ°, κ° λλ ν 리 λ΄ λͺ¨λ νμΌμ λν κ²μ¬ λν μ§ννλ€.
3. λ©ν° μ€λ λλ₯Ό μ΄μ©νμ¬ μ΅λν λΉ λ₯΄κ² μμ
μ μλ£νλλ‘ μμ±νλ€.
4. λμΌν νμΌμ λν κ²μ¬ κ²°κ³Όλ ν λ²μ μΆλ ₯λμ΄μΌ νλ€.
5. Directory λ΄ symlinkλ μλ€κ³ κ°μ νλ€.
6. νμΌλ€μ λͺ¨λ UTF8 μΈμ½λ©μΌλ‘ μμ±λ TextνμΌμ΄λΌκ³ κ°μ νλ€.
λ§μ΄ μ¬μ©λλ grep
μ΄λΌλ ν΄ λ³΄λ€ μ‘°κΈ λ λΉ λ₯΄κ² λ¬Έμμ΄μ κ²μν μ μλ κΈ°λ₯μ κ°μ dgrep
μ λ§λ€λΌλ μꡬμ¬νμ΄λ€. grep
μ λ§μ κΈ°λ₯λ€ μ€ μ¬νν keyword
μ‘°ν κΈ°λ₯μ λν΄ κ΅¬ν νλ©΄ λλ λ¬Έμ λΌκ³ μκ°νλ€.
β‘οΈ μ κ·Ό λ°©ν₯
grep
μ νμμλ λ§μ΄ μ¬μ©νκ³ μλ ν΄μ΄λΌ μꡬμ¬νμ μ΄ν΄νλλ° μ΄λ €μμ΄ μμ§ μμλ€.
κ°μ₯ λ¨Όμ μκ°ν λΆλΆμ μ΄λ»κ² νλ©΄ λ΄κ° μ£Όλ ₯μΌλ‘ μ¬μ©νλ μΈμ΄μμ grep
κ³Ό κ°μ λμμ νλ νλ‘κ·Έλ¨μ λ§λ€μ μμμ§μ λν λΆλΆμ΄μλ€. λΉμ°ν grep
λ³΄λ€ λΉ¨λΌμΌ νλλ°, μ²μλΆν° λΉ λ¦μ μ§μ€μ νλ©΄ κ±°κΈ°μ λ무 λ§€λͺ°λ κ² κ°μ. μ°μ grep
μ²λΌ λμνλ νλ‘κ·Έλ¨μ λ§λ€κ³ μλλ₯Ό κ°μ μν€λ λ°©ν₯μΌλ‘ μ§ννλκ² μ’μκ² κ°λ€κ³ μκ°νλ€.
⨠ꡬν
μ±κΈ μ€λ λ
kotlin
μ μ¬μ©ν΄μ ꡬννλ€. μ΄λ¦λ μ΄μ§ λ³κ²½ν΄λ΄€λ€.(dgrep -> kgrep
1
2
3
4
5
6
7
8
9
10
class KGrep(
private val keyword: String,
private val path: String
) { }
fun main(args: Array<String>) {
val keyword: String = args[0]
val path: String = args[1]
val kGrep = KGrep(keyword, path)
}
μ°μ KGrep
μ΄λΌλ ν΄λμ€λ₯Ό μ μΈνλ€. ν΄λΉ ν΄λμ€λ keyword
μ path
λκ°μ properties
λ₯Ό κ°κ³ μλ€. κ·Έ μ΄ν main
ν¨μλ₯Ό λ§λ€μ΄μ€¬λ€. KGrep
μ ν리μΌμ΄μ
μ μ€ν νμλ λμμ μ£Όμ²΄κ° λλ ν¨μλ€. Java
λ κ·Έλ κ³ Kotlin
λ κ·Έλ κ³ main
ν¨μμμ args
μ¦ arguments
리μ€νΈλ₯Ό νλΌλ―Έν°λ‘ λ°μ μ μλλ° μ΄ λΆλΆμ μ κ·Ή νμ©νλ€. μ΄λ κ² μ μΈμ νκ²λλ©΄ jar
νμΌμ μ€ν νμλ λ€μ ν리미ν°λ₯Ό λμ΄μ°κΈ°λ‘ ꡬλΆνμ¬ λκ²¨μ€ μ μκ² λλ€.
1
java -jar KGrep.jar "keyword" "path"
μμ μμμ²λΌ KGrep.jar
νμΌμ μ€ν μν¬λ λ€μ keyword
μ path
λ₯Ό λ£μ΄μ€ μ μκ³ , μ΄κ±Έ main
ν¨μμμ μ¬μ©ν μ μκ² λλ€.
λ€μμΌλ‘λ main
ν¨μμμ νΈμΆ ν KGrep
λ΄λΆμ search
λ©μλλ₯Ό μ μΈνλ€.
1
2
3
4
5
6
7
8
9
10
class KGrep(
private val keyword: String,
private val path: String
) {
fun search() {
val startPath = File(path)
}
}
// ...
μ¬κΈ°μ λΆν° κ³ λ―Όλλ λΆλΆμ΄ μμλλ°, μꡬμ¬νμ 보면 λλ ν 리 λ΄λΆμ λ λ€λ₯Έ λλ ν λ¦¬κ° μμ μ μμΌλ©°, λ΄λΆμ μΌλ‘ λ€μ΄μλ λλ ν 리μ λν΄μλ λ¬Έμμ΄ κ²μμ ν΄μΌνλ€λ λ΄μ©μ΄ μλ€.
μ¬κΈ°μ λ€μλ κ³ λ―Όμ΄ μμλ€.
path
μμ μλ λͺ¨λ λλ ν 리μ λν΄file
νμμ λ¨Όμ μ§ννκ³ , ν΄λΉfile
λ€μ λν΄ μμ°¨μ μΌλ‘ λ¬Έμμ΄ κ²μμ ν μ§file
νμκ³Ό λ¬Έμμ΄ κ²μμ ν¨κ» μ§νν μ§
λλ 1λ² λ°©λ²μ μ ννκΈ°λ‘ νλ€. κ°μ₯ ν° μ΄μ λ‘λ κΈμ μ’λ 보λ€λ³΄λ©΄ λμ¬ν
λ° λ©ν° μ€λ λμ λν κ³ λ €λ₯Ό μν μ μμκΈ° λλ¬Έμ΄λ€.
λ¨μ CLI
λ‘€ ν΅ν΄ λμνλ νλ‘κ·Έλ¨μ λ§λλ κ±°λΌλ©΄ 2λ² λ°©λ²μ΄ λ μ ν© νλ€ μκ°νκ³ κ·Έλ κ² κ΅¬νμ νμκ² κ°μλ°
λ©ν° μ€λ λ νκ²½μμ ꡬνμ λΉ λ₯΄κ³ κ°λ¨νκ² νκΈ° μν΄ 1λ² λ°©λ²μ μ ννκ² λμλ€.
λ¨μ CLI
λ₯Ό ν΅ν΄ λμνλ νλ‘κ·Έλ¨μ 2λ²μ μ ννμκ±°λΌ νλλ° κ·Έ μ΄μ λ 1λ² λ°©λ²μ μ¬μ©νλ©΄ μλμ κ°μ λΆλΆμ΄ λ§μμ κ±Έλ ΈκΈ° λλ¬Έμ΄λ€.
- λλ ν 리 λ΄λΆμ λͺ¨λ
file
μ λ©λͺ¨λ¦¬μ μ μ¬ μμΌμΌ νλ€. file
νμμ΄ λλ λκΉμ§ λ¬Έμμ΄ νμμ μ§ννμ§ μκΈ° λλ¬Έμ κ²°κ³Όλ¬Ό μΆλ ₯μ΄ λ리λ€.
λ€μ λμκ°μ search
λ©μλμ λ΄λΆλ₯Ό νμΈν΄ 보μ.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
private val filesToProcess: Queue<File> = LinkedList()
fun search() {
val startPath = File(path)
collectFiles(startPath)
}
private fun collectFiles(startPath: File) {
when {
startPath.isFile -> filesToProcess.add(startPath)
startPath.isDirectory -> startPath.listFiles()?.forEach { collectFiles(it) }
else -> println("Cannot process this path: ${startPath.absolutePath}")
}
}
Queue
μ λλ ν 리λ₯Ό νμνμ¬ file
μ μ μ₯νλ collectFiles
λ©μλλ₯Ό μΆκ°νλ€.
startPath
λ₯Ό μ‘°μ¬ν΄μ νμΌμ΄λΌλ©΄ Queue
μ μ μ¬λ₯Ό, λλ ν 리면 listFiles
λ©μλλ₯Ό ν΅ν΄ ν΄λΉ λλ ν 리μ μλ λͺ¨λ file
λ° λλ ν 리λ₯Ό κ°κ³ μ¨ λ€ forEach
μ μ¬κ·λ₯Ό ν΅ν΄ file
리μ€νΈλ₯Ό λ€μ νλ² νμνκ²λλ€.
λ€μμΌλ‘λ μ μ₯ν file
μ λν΄ λ¬Έμμ΄μ κ²μνλ searchInFile
λ©μλλ₯Ό μΆκ°νλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private val results = HashMap<String, List<Pair<Int, String>>>()
private fun searchInFile(file: File) {
if (!file.isFile || !file.canRead()) {
return
}
val matchingLines: MutableList<Pair<Int, String>> = mutableListOf()
file.useLines { lines ->
lines.forEachIndexed { index, line ->
if (line.contains(keyword)) {
matchingLines.add((index + 1) to line.trim())
}
}
}
if (matchingLines.isNotEmpty()) {
results[file.path] = matchingLines
}
}
νλΌλ―Έν°λ‘ λ겨 λ°μ file
μ λν΄ κ°λ¨νκ² κ²μ¦μ μ§ν ν λ€ νΉμ ν¬λ©§μ λ§κ² κ²°κ³Όλ¬Όμ μΆλ ₯νκΈ° μν΄ matchingLines
리μ€νΈμ κ°μ μ μ₯νκ³ μλ€.
kotlin
μμλ κΈ°λ³Έμ μΌλ‘ μ λ§ λ€μν ν¨μλ€μ΄ μ 곡λκ³ μλλ° ν΄λΉ λ©μλμμ μ¬λ¬ ν¨μλ€μ μ¬μ©νμ¬ μμ½κ² ꡬνμ΄ κ°λ₯νλ€.
useLines
:file
μbufferedReader
λ‘ λΌμΈλ¨μλ‘ μ²λ¦¬νμ¬ κ° λΌμΈ λ§λ€Sequence<String>
ννλ‘ μ κ·Όν μ μλλ‘ ν΄μ£Όλ ν¨μμ΄λ€. λ΄λΆμμuse
λ₯Ό μ¬μ©νκ³ μκΈ° λλ¬Έμ λ³λλ‘ λ¦¬μμ€ μ 리λ₯Ό ν νμκ° μμ΄ λ§€μ° νΈλ¦¬νλ€.forEachIndexed
: μνλ₯Ό νλ©΄μ κ°κ³Όindex
λ₯Ό ν¨κ» μ¬μ©ν μ μκ² ν΄μ£Όλ ν¨μμ΄λ€.contains
: νΉμ κ°μ ν¬ν¨νκ³ μλμ§ νμΈν μ μλλ‘ ν΄μ£Όλ ν¨μμ΄λ€. μ 3κ°μ ν¨μλ₯Ό μ¬μ©νκ³ , νμμ ν΅ν΄ κ²μνκ³ μ νλ λ¬Έμμ΄μ΄ ν¬ν¨λμ΄ μμΌλ©΄ κ²°κ³Όλ¬Όμ λ΄λresults
μ μ μ₯νλλ‘ λμνκ³ μλ€.
λ§μ§λ§μΌλ‘ μ΄ν΄λ³Ό λ©μλλ κ²°κ³Όλ¬Όμ μΆλ ₯ν΄μ£Όλ λ©μλλ€.
1
2
3
4
5
6
7
8
private fun printResults() {
results.keys.sorted().forEach { filePath ->
println("File: $filePath")
results[filePath]?.forEach { (lineNumber: Int, line: String) ->
println("Line $lineNumber: $line")
}
}
}
searchInFile
λ©μλμμ results
μ μ μ₯ν κ²°κ³Όλ¬Όμ λν΄ λͺ¨λ νμμ΄ λλκ³ νλ²μ μΆλ ₯μ ν΄μ£Όλ λ©μλμ΄λ€. κ²°κ³Όλ¬Όμ λν΄ μμ°¨μ μΌλ‘ 보μ¬μ§ μ μλλ‘ sorted
λ₯Ό νμ©νκ³ , results
μ μλ Paire<Int, String>
μ λν΄ lineNumber
μ line
μ μΆλ ₯νλλ‘ λ§λ€μλ€.
μ¬κΈ°κΉμ§κ° μ±κΈ μ€λ λλ‘ λμνλ KGrep
μ ꡬν λ΄μ©μ΄λ€. μ΄κ±Έ μμΌλ‘ KGrepV1
μΌλ‘ λͺ
μΉνμ.
κ·ΈλΌ μ΄μ μ±λ₯ ν
μ€νΈλ₯Ό μ§νν΄λ³΄μ. λ¨Όμ μ±λ₯ ν
μ€νΈλ₯Ό μν΄ λͺκ°μ§ μμ
μ΄ νμνλ€.
- ν μ€νΈ λ°μ΄ν° μμ±
- ꡬνν
KGrepV1
μ μ»΄νμΌνκ³.jar
νμΌλ‘ λ§λ€μ. - λ§λ
.jar
νμΌμ μ€νμν€λλ°time
λͺ λ Ήμ΄λ₯Ό ν΅ν΄ μ΄λμ λ μκ°μ΄ μμ λμλμ§ νμΈν΄λ³΄μ. grep
κ³ΌKGrepV1
μ κ°κ° μ€ν μν€κ³ μ±λ₯μ λΉκ΅ν΄λ³΄μ.
ν μ€νΈ λ°μ΄ν°λ μλμ κ°λ€.(μ¬λ¬ νμΌ μ€ μ΄ 6κ°μ νμΌλ§μ΄ βkeywordβ λΌλ λ¨μ΄λ₯Ό κ°κ³ μλ€.)
1
2
3
4
5
6
7
8
9
10
11
test-data/
βββ test.json
βββ dir_0/
β ββ file_0.txt # 100λ§μ€μ ν
μ€νΈ λ°μ΄ν°
β ...
β ββ file_9.txt
...
βββ dir_9/
ββ file_0.txt
...
ββ file_9.txt
μ±λ₯ λΉκ΅ κ²°κ³Όλ μλμ κ°λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
kotlinc KGrepV1.kt -include-runtime -d KGrepV1.jar # .jar νμΌ μμ±
time java -jar KGrepV1.jar "keyword" "test-data" # time μ ν΅ν μ±λ₯ μΈ‘μ
# result (m3 pro 12 core 12 thread, RAM 18GB κΈ°μ€)
## KGrepV1
File: test-data/dir_1/file_0.txt
Line 501: Line 500 in file_0.txt of dir_1. Contains keyword.
File: test-data/dir_3/file_7.txt
Line 501: Line 500 in file_7.txt of dir_3. Contains keyword.
File: test-data/dir_4/file_2.txt
Line 501: Line 500 in file_2.txt of dir_4. Contains keyword.
File: test-data/dir_5/file_5.txt
Line 501: Line 500 in file_5.txt of dir_5. Contains keyword.
File: test-data/dir_8/file_0.txt
Line 501: Line 500 in file_0.txt of dir_8. Contains keyword.
File: test-data/test.json
Line 2: "name": "keyword"
java -jar KGrepV1.jar "keyword" "test-data" 3.37s user 0.81s system 95% cpu 4.365 total
## grep
test-data/dir_5/file_5.txt:Line 500 in file_5.txt of dir_5. Contains keyword.
test-data/dir_4/file_2.txt:Line 500 in file_2.txt of dir_4. Contains keyword.
test-data/dir_3/file_7.txt:Line 500 in file_7.txt of dir_3. Contains keyword.
test-data/test.json: "name": "keyword"
test-data/dir_1/file_0.txt:Line 500 in file_0.txt of dir_1. Contains keyword.
test-data/dir_8/file_0.txt:Line 500 in file_0.txt of dir_8. Contains keyword.
grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn,.idea,.tox,.venv,venv 8.81s user 0.36s system 99% cpu 9.245 total
KGrepV1
κ³Ό grep
λΉκ΅ κ²°κ³Ό
νλͺ© | KGrepV1.jar | grep |
---|---|---|
μ¬μ©μ μκ° (user) | 3.37s | 8.81s |
μμ€ν μκ° (system) | 0.81s | 0.36s |
CPU μ¬μ©λ₯ | 95% | 99% |
μ΄ μμ μκ° | 4.365s | 9.245s |
KGrepV1
μ΄ λ¦¬μμ€λ μ κ² μ¬μ©νμ§λ§, λΉ λ₯΄κ² μ²λ¦¬νλ λͺ¨μ΅μ λ³Ό μ μμλ€. KGrepV1
κ³Ό grep
λͺ¨λ CPU
λ₯Ό 100%
λ΄μΈλ‘ μ¬μ©νκ³ μλ λͺ¨μ΅μ΄λ€.(μ€μ!)
μ΄μ μ¬κΈ°μ λ©ν° μ€λ λλ₯Ό λμ νμ¬ μ±λ₯μ κ°μ μμΌ λ³΄μ.
KGrepV1
μ μ 체 μ½λλ μλμ κ°λ€. κ°λ¨ν μμΈ μ²λ¦¬λ±μ μΆκ°νλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.crispinlab.kgrep
import java.io.File
import java.util.LinkedList
import java.util.Queue
class KGrepV1(
private val keyword: String,
private val path: String
) {
private val filesToProcess: Queue<File> = LinkedList()
private val results = HashMap<String, List<Pair<Int, String>>>()
fun search() {
val startPath = File(path)
collectFiles(startPath)
val fileCount: Int = filesToProcess.size
for (count: Int in 0 until fileCount) {
val file: File = filesToProcess.poll() ?: break
searchInFile(file)
}
printResults()
}
private fun collectFiles(startPath: File) {
when {
startPath.isFile -> filesToProcess.add(startPath)
startPath.isDirectory -> startPath.listFiles()?.forEach { collectFiles(it) }
else -> println("Cannot process this path: ${startPath.absolutePath}")
}
}
private fun searchInFile(file: File) {
if (!file.isFile || !file.canRead()) return
val matchingLines: MutableList<Pair<Int, String>> = mutableListOf()
file.useLines { lines ->
lines.forEachIndexed { index, line ->
if (line.contains(keyword)) {
matchingLines.add((index + 1) to line.trim())
}
}
}
if (matchingLines.isNotEmpty()) {
results[file.path] = matchingLines
}
}
private fun printResults() {
results.keys.sorted().forEach { filePath ->
println("File: $filePath")
results[filePath]?.forEach { (lineNumber: Int, line: String) ->
println("Line $lineNumber: $line")
}
}
}
}
fun main(args: Array<String>) {
if (args.size < 2) {
println("Usage: kgrep <keyword> <relative path>")
return
}
val keyword: String = args[0]
val path: String = args[1]
val kGrep = KGrepV1(keyword, path)
kGrep.search()
}
λ©ν° μ€λ λ
μ±κΈ μ€λ λλ‘ κ΅¬νν KGrepV1
μμ λ³κ²½λ λΆλΆλ§ λΉ λ₯΄κ² μ΄ν΄λ³΄μ.
μ°μ μ¬μ©νλ μλ£κ΅¬μ‘°λ€μ λ©ν° μ€λ λ νκ²½μμ μ¬μ©κ°λ₯νλλ‘ μ§μν΄μ£Όλ μλ£κ΅¬μ‘°λ‘ λ³κ²½νλ€.
1
2
private val results = ConcurrentHashMap<String, List<Pair<Int, String>>>()
private val filesToProcess = ConcurrentLinkedQueue<File>()
μ½νλ¦°μμ λ©ν° μ€λ λλ₯Ό μ¬μ©νλ λ°©λ²μλ μ¬λ¬κ°μ§ λ°©λ²μ΄ μλλ° λλ java
μμ μ§μνλ©΄μ kotlin
μμ λ°λ‘ μ¬μ© κ°λ₯ν ExcutorService api
λ₯Ό μ¬μ©νλ€. λ©ν° μ€λ λλ₯Ό μ¬μ©ν¨μ μμ΄ μ€λ λ κ΄λ¦¬κ° μ€μνλ° λ΄λΆμ μΌλ‘ κ·Έκ±Έ ν΄μ£Όλ©΄μλ thread pool
μ μ¬μ©ν μ μμ΄ μ ννκ² λμλ€.
thread pool
μ κ°μλ νμ¬ runtime
μμμ μ¬μ©κ°λ₯ν CPU
μ½μ΄ μ λ§νΌ μ§μ ν΄μ€¬λ€.
1
2
3
private val threadPool: ExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
)
κ·Έλ¦¬κ³ μμ μ μΈν thread pool
μ μ¬μ©νκΈ° μν΄ search
λ©μλλ₯Ό λ³κ²½ν΄μ€¬λ€.
μ¬κΈ°μλ κ³ λ―Όλλ λΆλΆμ΄ μμλλ°, thread
λ₯Ό μ΄λ€ λ¨μλ‘ ν λΉν κ²μΈκ° μ λν κ³ λ―Όμ΄μλ€.
μ¬κΈ°μ λλ κ΅³μ΄ νλμ νμΌμ μ¬λ¬ thread
μμ μ‘°νν νμλ μμκ±°λΌ μκ°νλ€. λ¬Όλ‘ ν° νμΌμ΄ λ§μ κ²½μ°λΌλ©΄ νλμ νμΌμ λκ° μ΄μμ thread
λ₯Ό μ¬μ©νμ¬ νμνλ λ°©λ²λ μμκ² κ°μλ° μΌλ¨ ꡬν μμ²΄κ° λ§€μ° λ³΅μ‘ν μ μκ³ (λμμ± μ²λ¦¬ λ±), μ²μμλ κ°λ¨νκ³ λΉ λ₯΄κ² ꡬννλκ² λ¨Όμ λΌκ³ μκ°νκΈ° λλ¬Έμ΄λ€.
κ·Έλμ λλ file
λ¨μλ‘ thread
λ₯Ό ν λΉνλ λ°©μμ μ ννλ€.
μ΄λ―Έ file
λͺ©λ‘μ Queue
μ λ΄κ³ μμκΈ° λλ¬Έμ ν΄λΉ Queue
μμ file
μ νλμ© κΊΌλ΄λ©΄μ ν΄λΉ file
μ νμν λ thread
λ₯Ό κ°κ° ν λΉ νλ λ°©μμΌλ‘ μμ
νλ©΄ κ°λ¨νκ² λ©ν° μ€λ λλ₯Ό μ¬μ©ν μ μμκ±°λΌ μκ°νκΈ° λλ¬Έμ΄λ€.
μλλ μ νν λ°©μλλ‘ λμνκΈ° μν΄ λ³κ²½ν search
λ©μλλ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
fun search() {
val startPath = File(path)
if (!startPath.exists()) {
println("Path does not exist: $path")
return
}
collectFiles(startPath)
val fileCount: Int = filesToProcess.size
for (count: Int in 0 until fileCount) {
val file: File = filesToProcess.poll() ?: break
threadPool.submit { searchInFile(file) }
}
threadPool.shutdown()
threadPool.awaitTermination(1, TimeUnit.HOURS)
printResults()
}
λμ λ°©μμ μλμ κ°λ€.
threadPool.submit
μ ν΅ν΄searchInFile
λ©μλλ₯Ό κ°κ°μthread
μμ λμνλλ‘ ν λΉνλ€.threadPool.shutdown
μ ν΅ν΄ λͺ¨λthread pool
μ μλthread
κ° μμ μ λ§μΉ λ κΉμ§ κΈ°λ€λ¦°λ€.- λ§μ½μ λλΉνμ¬
threadPool.awaitTermination
μ ν΅ν΄timeout
μ μ€μ threadPool
μ μλ λͺ¨λthread
κ° μμ μ λ§μΉλ©΄ κ²°κ³Όλ¬Όμ μΆλ ₯νλ€.
κ·ΈλΌ μ΄μ KGrepV2
μ λν μ±λ₯ ν
μ€νΈλ₯Ό μ§νν΄ λ³΄μ. ν
μ€νΈ λ°μ΄ν°μ μΈ‘μ λ°©μμ KGrepV1
μ μΈ‘μ νμλμ κ°λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
kotlinc KGrepV2.kt -include-runtime -d KGrepV2.jar # .jar νμΌ μμ±
time java -jar KGrepV2.jar "keyword" "test-data" # time μ ν΅ν μ±λ₯ μΈ‘μ
# result (m3 pro 12 core 12 thread, RAM 18GB κΈ°μ€)
## KGrepV1
File: test-data/dir_1/file_0.txt
Line 501: Line 500 in file_0.txt of dir_1. Contains keyword.
File: test-data/dir_3/file_7.txt
Line 501: Line 500 in file_7.txt of dir_3. Contains keyword.
File: test-data/dir_4/file_2.txt
Line 501: Line 500 in file_2.txt of dir_4. Contains keyword.
File: test-data/dir_5/file_5.txt
Line 501: Line 500 in file_5.txt of dir_5. Contains keyword.
File: test-data/dir_8/file_0.txt
Line 501: Line 500 in file_0.txt of dir_8. Contains keyword.
File: test-data/test.json
Line 2: "name": "keyword"
java -jar KGrepV1.jar "keyword" "test-data" 3.33s user 0.81s system 94% cpu 4.376 total
## KGrepV2
File: test-data/dir_1/file_0.txt
Line 501: Line 500 in file_0.txt of dir_1. Contains keyword.
File: test-data/dir_3/file_7.txt
Line 501: Line 500 in file_7.txt of dir_3. Contains keyword.
File: test-data/dir_4/file_2.txt
Line 501: Line 500 in file_2.txt of dir_4. Contains keyword.
File: test-data/dir_5/file_5.txt
Line 501: Line 500 in file_5.txt of dir_5. Contains keyword.
File: test-data/dir_8/file_0.txt
Line 501: Line 500 in file_0.txt of dir_8. Contains keyword.
File: test-data/test.json
Line 2: "name": "keyword"
Search completed in 1091 ms
java -jar KGrepV2.jar "keyword" "test-data" 8.26s user 1.40s system 832% cpu 1.162 total
## grep
test-data/dir_5/file_5.txt:Line 500 in file_5.txt of dir_5. Contains keyword.
test-data/dir_4/file_2.txt:Line 500 in file_2.txt of dir_4. Contains keyword.
test-data/dir_3/file_7.txt:Line 500 in file_7.txt of dir_3. Contains keyword.
test-data/test.json: "name": "keyword"
test-data/dir_1/file_0.txt:Line 500 in file_0.txt of dir_1. Contains keyword.
test-data/dir_8/file_0.txt:Line 500 in file_0.txt of dir_8. Contains keyword.
grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn,.idea,.tox,.venv,venv 9.12s user 0.41s system 98% cpu 9.685 total
KGrepV1
,KGrepV2
, grep
λΉκ΅ κ²°κ³Ό
νλͺ© | KGrepV1.jar | KGrepV2.jar | grep |
---|---|---|---|
μ¬μ©μ μκ° (user) | 3.33s | 8.26s | 9.12s |
μμ€ν μκ° (system) | 0.81s | 1.40s | 0.41s |
CPU μ¬μ©λ₯ | 94% | 832% | 98% |
μ΄ μμ μκ° | 4.376s | 1.162s | 9.685s |
KGrepV2
κ° μλ μΈ‘λ©΄μμλ λ§€μ° λΉ λ₯Έκ±Έ λ³Ό μ μλ€.
λ©ν° μ€λ λλ₯Ό μ¬μ©νκΈ° λλ¬Έμ CPU
λ₯Ό 100% μ΄μ μ¬μ©νλκ±Έ λ³Ό μ μλ€.
νμ€ν λ©ν° μ€λ λλ₯Ό μ¬μ©νλ μλμ μΈ λΆλΆμμλ λ§€μ° κ°μ λκ±Έ λ³Ό μ μλ€. λ€λ§, 리μμ€λ κ·Έ λ§νΌ μ¬μ©νμ§λ§..
KGrepV2
μ μ 체 μ½λλ μλμ κ°λ€.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
package com.crispinlab.kgrep
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
import kotlin.system.measureTimeMillis
class KGrepV2(
private val keyword: String,
private val path: String
) {
private val results = ConcurrentHashMap<String, List<Pair<Int, String>>>()
private val filesToProcess = ConcurrentLinkedQueue<File>()
private val threadPool: ExecutorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors()
)
fun search() {
val startPath = File(path)
if (!startPath.exists()) {
println("Path does not exist: $path")
return
}
collectFiles(startPath)
val time: Long = measureTimeMillis {
val fileCount: Int = filesToProcess.size
for (count: Int in 0 until fileCount) {
val file: File = filesToProcess.poll() ?: break
threadPool.submit { searchInFile(file) }
}
threadPool.shutdown()
threadPool.awaitTermination(1, TimeUnit.HOURS)
}
printResults()
println("Search completed in $time ms")
}
private fun collectFiles(startPath: File) {
when {
startPath.isFile -> filesToProcess.add(startPath)
startPath.isDirectory -> startPath.listFiles()?.forEach { collectFiles(it) }
else -> println("Cannot process this path: ${startPath.absolutePath}")
}
}
private fun searchInFile(file: File) {
try {
if (!file.isFile || !file.canRead()) return
val matchingLines: MutableList<Pair<Int, String>> = mutableListOf()
file.useLines { lines ->
lines.forEachIndexed { index, line ->
if (line.contains(keyword)) {
matchingLines.add((index + 1) to line.trim())
}
}
}
if (matchingLines.isNotEmpty()) {
results[file.path] = matchingLines
}
} catch (e: Exception) {
System.err.println("Error processing file ${file.path}: ${e.message}")
}
}
private fun printResults() {
results.keys.sorted().forEach { filePath ->
println("File: $filePath")
results[filePath]?.forEach { (lineNumber: Int, line: String) ->
println("Line $lineNumber: $line")
}
}
}
}
fun main(args: Array<String>) {
if (args.size < 2) {
println("Usage: kgrep <keyword> <relative path>")
return
}
val keyword: String = args[0]
val path: String = args[1]
val kGrep = KGrepV2(keyword, path)
kGrep.search()
}
π‘ λ§λ¬΄λ¦¬
μ€λμ λΌμ΄λΈ μ½λ© ν
μ€νΈ μμ λ¬Έμ λ₯Ό νμ΄λ΄€λ€.
μΉ κ°λ°μ νλ€λ³΄λ©΄ μꡬμ¬νμ λ§λ API
λ₯Ό λ§μ΄ ꡬννμ§, μ΄λ κ² μꡬμ¬νμ λ§λ μ ν리μΌμ΄μ
μ ꡬνν μΌμ΄ μκ°λ³΄λ€ λ§μ§ μμλ° μ€λλ§μ μ΄λ°μ 건 κ³ λ―Όμ νλ©΄μ μꡬμ¬νμ λ§λ μ ν리μΌμ΄μ
μ ꡬννλ μ¬λ―Έμμλ€.
μ΄λ² κΈ°νμ νμΌ μ²λ¦¬ λ° λ©ν° μ€λ λμ λν΄ λ€λ€λ³Ό μ μμ΄ κ²½νμ μΌλ‘ λ§μ΄ λμμ΄ λμλ€.
μλκ° λΉ λ₯΄λ€κ³ ν΄μ 무쑰건 μ’μ μ ν리μΌμ΄μ
μ μλλ€. 리μμ€λ₯Ό νμ μ μΌλ‘ μ¬μ©ν΄μΌ νλ νκ²½μμ KGrepV2
λ 그리 μ’μ μ ν리μΌμ΄μ
μ΄λΌ ν μ μμκ±°λ€. λ€λ§ κΈ°μ‘΄μ λ§μ΄ μ¬μ©νλ grep
μ μλμ μΈ νκ³λ₯Ό μ΄λ»κ² 극볡ν μ μμμ§μ λν΄ λ¬Έμ λ₯Ό ν΅ν΄ μ μλλ§ μκ°ν μ μμ΄μ λ무 μ’μλ€.
κΈ°νκ° λλ€λ©΄ ν΄λΉ λ¬Έμ λ₯Ό λ€λ₯Έ κ°λ°μλΆλ€μ μ΄λ»κ² νμλμ§ λ΄κ° ꡬνν λ΄μ©κ³Όλ μ΄λ€ λΆλΆμ΄ λ€λ₯Όμ§ λΉκ΅ν΄λ³΄κ³ μΆλ€.