Regex 正则表达式的Kotlin性能问题
我正在制作一个使用正则表达式的日志解析应用程序,我看到一些奇怪的行为,我希望有人能帮助解释,也许能给出一些克服这些行为的技巧。首先,代码如下:Regex 正则表达式的Kotlin性能问题,regex,performance,kotlin,jvm,performance-testing,Regex,Performance,Kotlin,Jvm,Performance Testing,我正在制作一个使用正则表达式的日志解析应用程序,我看到一些奇怪的行为,我希望有人能帮助解释,也许能给出一些克服这些行为的技巧。首先,代码如下: import java.io.File var regex1Count = 0 var regex2Count = 0 var noMatchCount = 0 val regex1 = Regex(".*error.*", RegexOption.IGNORE_CASE) val regex2 = Regex("exce
import java.io.File
var regex1Count = 0
var regex2Count = 0
var noMatchCount = 0
val regex1 = Regex(".*error.*", RegexOption.IGNORE_CASE)
val regex2 = Regex("exception|crashed|death|fatal|killed| f | e ", RegexOption.IGNORE_CASE)
fun main(args: Array<String>) {
val file = File("C:\\Users\\pnogas\\Desktop\\mobicontrol.log")
val time = System.currentTimeMillis()
val result = file.useLines { sequence ->
sequence.mapNotNull { line ->
parseLine(line)
}.toList()
}
println("took ${(System.currentTimeMillis() - time) / 1000.0} seconds")
println("regex1Count = $regex1Count, regex2Count = $regex2Count, noMatchCount = $noMatchCount")
}
private fun parseLine(line: String) {
for (filter in listOf(regex2, regex1)) {
if (filter.containsMatchIn(line)) {
if (regex1 == filter) {
regex1Count++
} else if (regex2 == filter) {
regex2Count++
}
return
}
}
noMatchCount++
}
但是如果我将一行更改为listOf(regex1,regex2),而不是listOf(regex2,regex1):
我知道通配符regex的运行成本会更高,但数字表明,更改顺序只会使其运行两倍以上,与处理的总行数相比,这似乎可以忽略不计。如果我使列表只包含regex1,则会获得相同的性能
最重要的是,当我使用记事本++对同一个文件执行相同的正则表达式搜索时,我得到了18个结果,但结果几乎是立即出现的。我知道JVM的性能不如本机编译代码,但它真的会运行得那么慢吗。还是我完全走错了路
由于到目前为止的答复,一些澄清
我唯一的随机猜测是,有某种JIT JVM预热首先运行简单的正则表达式,这允许第二个更复杂的正则表达式运行得更快,我应该插入一个虚拟正则表达式,它在执行我关心的正则表达式以提高性能之前总是很快的…???这是一个复杂的问题,因此提前为长时间的问题道歉(遗憾的是不完整)答案 您的测试代码存在误解。列表中的第一个正则表达式将在所有行上求值,因此在您的示例中求值11676次。您的regex1Count变量仅返回一个正匹配项被返回的次数(代价高昂)搜索操作。因此,更改正则表达式的求值顺序会对性能产生巨大影响,因为第一个正则表达式将用作主过滤器 此外,正如@PiRocks所说,正则表达式可以简化。更重要的是,由于它的简单性(搜索单个单词),这里甚至不需要使用正则表达式。您可以执行文字搜索,而且速度会快得多 此外,作为多年的JVM用户,我必须纠正一个关于性能的常见误解:JVM应用程序并不总是比本地应用程序慢。每种技术都在其自己的领域中大放异彩,要获得最大性能,通常需要为正确的任务选择正确的工具。例如,JVM使用JIT对频率进行积极的优化垃圾收集器大大降低了可变分配的成本 无论如何,在当前情况下,我们无法将手工代码的性能与附带的应用程序进行比较,无论双方使用何种技术。为什么?因为我们无法确定是否比较等效的工作流。在这里,记事本可能有:
- 在内存中缓冲整个文件
- 事先创建了索引
- 分析了输入搜索正则表达式,并在执行前消除了不必要的复杂性
- 多线程搜索
- 等等
regex1
regex2
*错误相比,文字搜索速度非常快。*
regex。regex是一个非常强大的工具,但它们的复杂性可能很难管理
现在,还有一个问题:JVM正则表达式在性能方面是否实现得很差?要回答这个问题并不简单。天真地说,我们可以尝试一下我做的游戏(见下面的代码),用另一种语言重写它,并比较两者的输出。但由于JVM JIT/预热时间的原因,比较会有偏差
我们必须对这两个实现进行广泛的循环,收集统计数据,最后比较结果,以获得良好的洞察力
以下是游乐场及其输出,仅供参考:
导入kotlin.random.random
导入java.lang.StringBuilder
导入java.lang.System
导入java.util.regex.Pattern
主要内容(){
println(“日志示例:”)
generateLogs(nbLines=2,wordPerLine=10).forEach{println(it)}
println(“\n测量12000行上的正则表达式\n”)
val regex1=Regex(“.*error.*”,RegexOption.IGNORE_CASE)
for(列表(10,20)中的单词){
粗略测量(“每行$nbWords的正则表达式1”){
val matched=generateLogs(wordPerLine=nbWords)
.count{regex1.containsMatchIn(it)}
}
}
val javaPattern=Pattern.compile(“.*error.*”,Pattern.CASE\u不区分大小写)
for(列表(10,20)中的单词){
粗略测量(“每行$nbWords的Java模式1”){
val=gen
took 4.198 seconds
regex1Count = 16, regex2Count = 101, noMatchCount = 11559
took 35.049 seconds
regex1Count = 18, regex2Count = 99, noMatchCount = 11559
Log example :
ex quam Suspendisse vel sed rhoncus aliquet. elit.
nibh amet, sed nibh eleifend diam amet ex eleifend.
Measure Regex on 12000 lines
Regex 1 for 10 words per line took 0.439 seconds
Regex 1 for 20 words per line took 0.843 seconds
Java pattern 1 for 10 words per line took 0.407 seconds
Java pattern 1 for 20 words per line took 1.347 seconds
Regex 2 for 50 words per line took 0.463 seconds
Literal search for 1000 words per line took 0.836 seconds
import kotlin.random.Random
import java.lang.StringBuilder
import java.lang.System
import java.util.regex.Pattern
fun main() {
println("Log example :")
generateLogs(nbLines = 2, wordPerLine = 10).forEach { println(it) }
println("\nMeasure Regex on 12000 lines\n")
val regex1 = Regex(".*error.*", RegexOption.IGNORE_CASE)
for (nbWords in listOf(10, 20)) {
roughMeasurement("Regex 1 for $nbWords words per line") {
val matched = generateLogs(wordPerLine = nbWords)
.count { regex1.containsMatchIn(it) }
}
}
val javaPattern = Pattern.compile(".*error.*", Pattern.CASE_INSENSITIVE)
for (nbWords in listOf(10, 20)) {
roughMeasurement("Java pattern 1 for $nbWords words per line") {
val matched = generateLogs(wordPerLine = nbWords)
.count { javaPattern.matcher(it).find() }
}
}
val regex2 = Regex("(exception)|(crashed)|(death)|(fatal)|(killed)| f | e ", RegexOption.IGNORE_CASE)
roughMeasurement("Regex 2 for 50 words per line") {
val matched = generateLogs()
.count { regex2.containsMatchIn(it) }
}
roughMeasurement("Literal search for 1000 words per line") {
val matched = generateLogs(wordPerLine = 1000)
.count { it.indexOf("error") >= 0 }
}
}
fun roughMeasurement(title: String, action: () -> Unit) {
val start = System.nanoTime()
action()
val end = System.nanoTime()
val timeSeconds = (end - start).toDouble() * 1e-9
println("$title took ${"%.3f".format(timeSeconds)} seconds")
}
/*
* LOG GENERATION UTILITIES
*/
fun generateLogs(nbLines : Int = 12000, wordPerLine : Int = 50) : Sequence<String> {
return (1..nbLines).asSequence()
.map { generateSentence(wordPerLine) }
}
fun generateSentence(nbWords : Int) : String {
require(nbWords > 2) { "Need more than two words per sentence" }
val builder = StringBuilder(nbWords * 3)
for (i in 0..nbWords-2) {
builder.append(wordPool.pick()).append(' ')
}
builder.append(wordPool.pick())
return builder.toString()
}
fun List<String>.pick() = this[Random.nextInt(0, size)]
/**
* Authorized words in log generation.
* To test for worst-case scenario, we've omitted searched keywords:
* error exception crashed death fatal killed
*/
val wordPool = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Suspendisse eu ex eu ligula egestas posuere ac et velit.
Fusce sed nisl diam. Proin eleifend nibh vel felis fermentum,
a luctus diam eleifend. Pellentesque feugiat magna sit amet
arcu eleifend, vel lacinia justo aliquet. In quam magna,
rhoncus a lacinia vel.
""".split(Regex("\\s+"))