【问题标题】:Slower than expected Java regex performance比预期的 Java 正则表达式性能慢
【发布时间】:2011-07-11 20:55:14
【问题描述】:

我的任务是读取大型 CSV 文件(超过 30 万条记录)并将正则表达式模式应用于每条记录。我一直是一名 PHP 开发人员,从未真正尝试过任何其他语言,但我决定尝试使用 Java 来完成这项工作,我认为这会更快。

事实上,在 Java 中逐行读取 CSV 文件的速度要快 3 倍。然而,当我应用正则表达式要求时,Java 实现证明比 PHP 脚本花费 10-20% 的时间。

很可能我在 Java 中做错了什么,因为我今天刚刚学到了这一点。以下是两个脚本,任何建议将不胜感激。我真的不想因为这个特定的项目而放弃 Java。

PHP 代码

<?php
$bgtime=time();
$patterns =array(
    "/SOME REGEXP/",
    "/SOME REGEXP/",                    
    "/SOME REGEXP/",    
    "/SOME REGEXP/" 
);   

$fh = fopen('largeCSV.txt','r');
while($currentLineString = fgetcsv($fh, 10000, ","))
{
    foreach($patterns AS $pattern)
    {
        preg_match_all($pattern, $currentLineString[6], $matches);
    }
}
fclose($fh);
print "Execution Time: ".(time()-$bgtime);

?>

JAVA 代码

import au.com.bytecode.opencsv.CSVReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.ArrayList;

public class testParser
{
    public static void main(String[] args)
    {
        long start = System.currentTimeMillis();


        String[] rawPatterns = {
                    "SOME REGEXP",
                    "SOME REGEXP",                    
                    "SOME REGEXP",    
                    "SOME REGEXP"    
        };

        ArrayList<Pattern> compiledPatternList = new ArrayList<Pattern>();        
        for(String patternString : rawPatterns)
        {
            Pattern compiledPattern = Pattern.compile(patternString);
            compiledPatternList.add(compiledPattern);
        }


        try{
            String fileName="largeCSV.txt";
            CSVReader reader = new CSVReader(new FileReader(fileName));

            String[] header = reader.readNext();
            String[] nextLine;
            String description;

            while( (nextLine = reader.readNext()) != null) 
            {
                description = nextLine[6];
                for(Pattern compiledPattern : compiledPatternList)
                {
                    Matcher m = compiledPattern.matcher(description);
                    while(m.find()) 
                    {
                        //System.out.println(m.group(0));
                    }                
                }
            }
        }

        catch(IOException ioe)
        {
            System.out.println("Blah!");
        }

        long end = System.currentTimeMillis();

        System.out.println("Execution time was "+((end-start)/1000)+" seconds.");
    }
}

【问题讨论】:

  • 与您的正则表达式问题不完全相关,但您可能想查看download.oracle.com/javase/6/docs/api/java/util/Scanner.html。您可能会发现不需要您的 CSVReader 类。不创建所有这些临时字符串(nextLine 似乎至少有 7 个字符串,但您只需要一个)可能会提高性能。

标签: java php regex


【解决方案1】:

使用缓冲阅读器可能有助于提高性能:

CSVReader reader = new CSVReader(new BufferedReader(new FileReader(fileName)));

【讨论】:

  • 很好,如果 FileReader 不能自行缓冲,那肯定是性能瓶颈。
  • CSVReader 是否缓冲更重要。
  • 这略有增加,但不是魔术酱。不过感谢您的建议!
【解决方案2】:

我看不出您的代码有什么明显错误。尝试使用分析器隔离性能瓶颈。我发现netbeans 分析器非常用户友好。

编辑:为什么要推测?分析应用程序并获取有关时间花费的详细报告。然后努力解决低效领域。请参阅http://profiler.netbeans.org/ 了解更多信息。

EDIT2:好的,我对此感到无聊并对此进行了分析。我的代码与您的代码相同,并解析了一个包含 1,000 行相同行的 CSV 文件,如下所示:

SOME REGEXP,SOME REGEXP,SOME REGEXP,SOME REGEXP,SOME REGEXP,SOME REGEXP,SOME REGEXP,SOME REGEXP,SOME REGEXP,SOME REGEXP

这是结果(显然你的结果会有所不同,因为我的正则表达式很简单)。但是,很明显,正则表达式处理并不是您主要关心的领域。

有趣的是,如果我应用 BufferedReader,性能会提高 18%(见下文)。

【讨论】:

  • 刚刚在 Notepad++ 中打过这个,但我会让 netbeans 去看看它表示什么。
  • 显然我不知道如何有效地利用分析器。我已经针对我的 JAVA 应用程序运行了探查器,但它似乎向我展示的只是内存(堆)、内存(GC)和线程/加载的类......以及 main() 的执行时间。有没有关于如何使用它的好教程?
  • @IOInterrupt - 在 Netbeans 中,配置文件 > 配置文件主项目 > CPU > 整个应用程序 > 运行
  • 谢谢!我发现我必须将过滤器设置为“Profile all classes”才能显示实时结果。我肯定会更深入地研究这个,所以我确切地了解问题所在。再次感谢您。
【解决方案3】:

这里需要注意几点。

  1. 您甚至在模式编译之前就开始测量时间。 Pattern.compile 是一个相对昂贵的操作,如果模式复杂,可能会花费更多时间。为什么不在编译步骤之后开始测量呢?

  2. 我不确定CSVReader 类的效率如何。

  3. 与其直接在主线程本身打印匹配的结果(因为System.out.println 是阻塞且昂贵的),您或许可以将打印委托给不同的线程。

【讨论】:

  • 我更喜欢从一开始就对脚本进行计时,因为这两个脚本基本上都在做同样的事情,而且我认为总脚本执行时间是一个有效的指标。我认为正则表达式的复杂性是问题所在,所以我将它们全部更改为一个通用词。 PHP 执行时间为 93 秒,而 Java 为 246 秒。我相信 CSVReader 类是高效的,因为它能够比 PHP fgetcsv() 函数更快地读取 CSV 文件(快 3 倍)。另外,我把println()函数注释掉了。
  • @IOInterrupt:对。有许多因素可以在这里发挥作用。输入到 VM 的内存量也起着重要作用。您可以尝试按照 hoipolloi 的建议分析应用程序。
  • 我对虚拟机一无所知。我很高兴能让这件事发挥作用。
【解决方案4】:

几件事:

  1. 正则表达式只需要编译一次,并且应该在服务器启动时编译,因此对于运行时的性能并不重要。

  2. 最重要的是,您正在为一个长时间运行的 java 程序编写一个完全无效的基准测试。您肯定会在进行基准测试时加载几个类,并且总体上只测试解释器的性能,而不是 JIT,这显然会导致性能更差。请参阅 this 出色的帖子,了解如何在 Java 中编写有效的基准测试。毫无疑问,这将解决本案例中所有所谓的性能问题。

【讨论】:

  • OP 没有说有服务器,也没有说它是一个长时间运行的程序。可能是这样,你会是对的,但可能不是。
  • 假设它是一个服务器,因为他使用的是 PHP,但是是的,你是对的。但是,如果程序运行时间不长且性能不重要,为什么还要关心优化它呢?
  • PHP 脚本通过 PHP CLI 手动运行,就像手动运行 JAVA 应用程序一样。我不确定这是否会影响您的建议。处理 500mb 的 CSV 文件时,这两个脚本都会运行大约 10 分钟。
【解决方案5】:

我会推荐:

  • 正如其他人所建议的那样,剖析以查看实际瓶颈在哪里;
  • 告诉我们实际的正则表达式是什么:可能是您使用了一些在 Java 实现中效率不高的特定子模式。

对于特定的表达式类型,PHP 的正则表达式引擎的某些部分很可能比 Java 更优化,和/或有一种方法可以优化您正在使用的实际表达式。

【讨论】:

    猜你喜欢
    • 2015-09-18
    • 2012-03-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-21
    • 1970-01-01
    相关资源
    最近更新 更多