【问题标题】:Java performance issue compared to Perl与 Perl 相比的 Java 性能问题
【发布时间】:2015-10-04 06:16:50
【问题描述】:

我编写了一个 Perl 代码来处理大量 CSV 文件并获得输出,这需要 0.8326 秒才能完成。

my $opname = $ARGV[0];
my @files = `find . -name "*${opname}*.csv";mtime -10 -type f`;
my %hash;
foreach my $file (@files) {
chomp $file;
my $time = $file;
$time =~ s/.*\~(.*?)\..*/$1/;

open(IN, $file) or print "Can't open $file\n";
while (<IN>) {
    my $line = $_;
    chomp $line;

    my $severity = (split(",", $line))[6];
    next if $severity =~ m/NORMAL/i;
    $hash{$time}{$severity}++;
}
close(IN);

}
foreach my $time (sort {$b <=> $a} keys %hash) {
    foreach my $severity ( keys %{$hash{$time}} ) {
        print $time . ',' . $severity . ',' . $hash{$time}{$severity} . "\n";
    }
}

现在我正在用 Java 编写相同的逻辑,但需要 2600 毫秒,即 2.6 秒才能完成。我的问题是为什么 Java 需要这么长时间?如何达到与 Perl 相同的速度? 注意:我忽略了 VM 初始化和类加载时间。

    import java.io.BufferedReader;
    import java.io.File;
    import java.io.FileFilter;
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.TreeMap;

    public class MonitoringFileReader {
        static Map<String, Map<String,Integer>> store= new TreeMap<String, Map<String,Integer>>(); 
        static String opname;
        public static void testRead(String filepath) throws IOException
        {
            File file = new File(filepath);

            FileFilter fileFilter= new FileFilter() {

                @Override
                public boolean accept(File pathname) {
                    // TODO Auto-generated method stub
                    int timediffinhr=(int) ((System.currentTimeMillis()-pathname.lastModified())/86400000);
                    if(timediffinhr<10 && pathname.getName().endsWith(".csv")&& pathname.getName().contains(opname)){
                        return true;
                        }
                    else
                        return false;
                }
            };

            File[] listoffiles= file.listFiles(fileFilter);
        long time= System.currentTimeMillis();  
            for(File mf:listoffiles){
                String timestamp=mf.getName().split("~")[5].replace(".csv", "");
                BufferedReader br= new BufferedReader(new FileReader(mf),1024*500);
                String line;
                Map<String,Integer> tmp=store.containsKey(timestamp)?store.get(timestamp):new HashMap<String, Integer>();
                while((line=br.readLine())!=null)
                {
                    String severity=line.split(",")[6];
                    if(!severity.equals("NORMAL"))
                    {
                        tmp.put(severity, tmp.containsKey(severity)?tmp.get(severity)+1:1);
                    }
                }
            store.put(timestamp, tmp);
            }
        time=System.currentTimeMillis()-time;
            System.out.println(time+"ms");  
            System.out.println(store);


        }

        public static void main(String[] args) throws IOException
        {
            opname = args[0];
            long time= System.currentTimeMillis();
            testRead("./SMF/data/analyser/archive");
            time=System.currentTimeMillis()-time;
            System.out.println(time+"ms");
        }

    }

文件输入格式(A~B~C~D~E~20150715080000.csv),约500个文件,每个~1MB,

A,B,C,D,E,F,CRITICAL,G
A,B,C,D,E,F,NORMAL,G
A,B,C,D,E,F,INFO,G
A,B,C,D,E,F,MEDIUM,G
A,B,C,D,E,F,CRITICAL,G

Java 版本:1.7

/////////////////////////////////////////////////////////////////////////////////////////////////

根据以下 cmets , 我用 regex 替换了 split ,性能提升了很多。 现在我在循环中执行此操作,经过 3-10 次迭代后,性能完全可以接受。

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

    public class MonitoringFileReader {
        static Map<String, Map<String,Integer>> store= new HashMap<String, Map<String,Integer>>(); 
        static String opname="Etis_Egypt";
        static Pattern pattern1=Pattern.compile("(\\d+\\.)");
        static Pattern pattern2=Pattern.compile("(?:\"([^\"]*)\"|([^,]*))(?:[,])");
        static long currentsystime=System.currentTimeMillis();
        public static void testRead(String filepath) throws IOException
        {
            File file = new File(filepath);

            FileFilter fileFilter= new FileFilter() {

                @Override
                public boolean accept(File pathname) {
                    // TODO Auto-generated method stub
                    int timediffinhr=(int) ((currentsystime-pathname.lastModified())/86400000);
                    if(timediffinhr<10 && pathname.getName().endsWith(".csv")&& pathname.getName().contains(opname)){
                        return true;
                        }
                    else
                        return false;
                }
            };

            File[] listoffiles= file.listFiles(fileFilter);
        long time= System.currentTimeMillis();  
            for(File mf:listoffiles){
                Matcher matcher=pattern1.matcher(mf.getName());
                matcher.find();
                //String timestamp=mf.getName().split("~")[5].replace(".csv", "");
                String timestamp=matcher.group();
                BufferedReader br= new BufferedReader(new FileReader(mf));
                String line;
                Map<String,Integer> tmp=store.containsKey(timestamp)?store.get(timestamp):new HashMap<String, Integer>();
                while((line=br.readLine())!=null)
                {
                    matcher=pattern2.matcher(line);
                    matcher.find();matcher.find();matcher.find();matcher.find();matcher.find();matcher.find();matcher.find();
                    //String severity=line.split(",")[6];
                    String severity=matcher.group();
                    if(!severity.equals("NORMAL"))
                    {
                        tmp.put(severity, tmp.containsKey(severity)?tmp.get(severity)+1:1);
                    }
                }
                br.close();
            store.put(timestamp, tmp);
            }
        time=System.currentTimeMillis()-time;
            //System.out.println(time+"ms");    
            //System.out.println(store);


        }

        public static void main(String[] args) throws IOException
        {
            //opname = args[0];
            for(int i=0;i<20;i++){
            long time= System.currentTimeMillis();
            testRead("./SMF/data/analyser/archive");
            time=System.currentTimeMillis()-time;


            System.out.println("Time taken for "+i+" is "+time+"ms");
            }
        }

    }

但我现在还有一个问题,

在小型数据集上运行时查看结果。

**Time taken for 0 is 218ms
Time taken for 1 is 134ms
Time taken for 2 is 127ms**
Time taken for 3 is 98ms
Time taken for 4 is 90ms
Time taken for 5 is 77ms
Time taken for 6 is 71ms
Time taken for 7 is 72ms
Time taken for 8 is 62ms
Time taken for 9 is 57ms
Time taken for 10 is 53ms
Time taken for 11 is 58ms
Time taken for 12 is 59ms
Time taken for 13 is 46ms
Time taken for 14 is 44ms
Time taken for 15 is 45ms
Time taken for 16 is 53ms
Time taken for 17 is 45ms
Time taken for 18 is 61ms
Time taken for 19 is 42ms

对于最初的几个例子,花费的时间更多,然后减少,.. 为什么???

谢谢,

【问题讨论】:

标签: java performance perl


【解决方案1】:

由于 JIT 编译,几秒钟不足以让 Java 达到全速。 Java 针对运行数小时(或数年)的服务器进行了优化,而不是针对仅需几秒钟的小型实用程序。

关于类加载,我猜你不知道例如PatternMatchersplit 中间接使用,并根据需要加载。


static Map<String, Map<String,Integer>> store= new TreeMap<String, Map<String,Integer>>(); 

Perl 哈希与 Java HashMap 最相似,但您使用的是速度较慢的 TreeMap。我想这无关紧要,请注意,差异远比您想象的要多。


 int timediffinhr=(int) ((System.currentTimeMillis()-pathname.lastModified())/86400000);

您一次又一次地读取每个文件的时间。即使对于那些名字不以“.csv”结尾的人,你也这样做。 find 肯定不是这么做的。


String timestamp=mf.getName().split("~")[5].replace(".csv", "");

与 Perl 不同,Java 不缓存正则表达式。据我所知,单个字符的拆分会单独优化,否则使用类似

的东西会更好
private static final Pattern FILENAME_PATTERN =
    Pattern.compile("(?:[^~]*~){5}~([^~]*)\\.csv");

Matcher m = FILENAME_PATTERN.matcher(mf.getName());
if (!m.matches) ... do what you want
String timestamp = m.group(1);

 BufferedReader br = new BufferedReader(new FileReader(mf), 1024*500);

这可能是罪魁祸首。默认情况下,它使用平台编码,可能是 UTF-8。这通常比 ASCII 或 LATIN-1 慢。据我所知,Perl 直接使用字节,除非另有说明。

半兆字节的缓冲区大小对于只需要几秒钟的任何事情来说都非常大,尤其是当您多次分配它时。请注意,您的 Perl 代码中没有这样的内容。


话虽如此,Perl 和 find 对于如此短的任务可能确实更快。

【讨论】:

  • 你提出了一些好观点。我要补充一点,OP 已经完全实现了 Perl 代码,包括使store(Perl 中的%hash)成为散列的散列。 HashMaps 的对应 Java TreeMap 远不那么明显,并且可能比 Perl 原始版本慢得多。 Perl 默认在 8KB 缓冲区中缓冲输入流,但其split 不使用正则表达式引擎。我不知道FileFilter 是如何工作的,但我相信Perl 可以比find 更快地模拟过滤器。最后,我认为由 OP 来展示他所拥有的。我还没有看到重写的好案例
  • 感谢您的回答,很有道理。我想补充几点, 1. 我得到了类加载部分,当它需要时,类正在加载。 2. Treemap 部分,实际上在 Perl 中我也在对 map 进行排序,因此 java 中的 treemap 也没有改进。
  • 3.拆分内部正则表达式部分对我来说是新的。4.最初我为阅读器使用默认缓冲区大小,但后来我可能由于更多磁盘 IO 性能很慢,因此我增加了它.但在时间方面没有任何改变
  • @user3080158 我敢打赌,多次迭代它会变得更好。一个典型的基准在开始测量时间之前会进行 5-20 次一次性迭代。问题是它有多好。如果不关闭阅读器,您很快就会用完文件描述符。 +++ 如果你能提供数据,有人会更加努力地优化。
  • @RBanerjee 那是 JIT 编译。首先,代码被解释并收集统计信息。同时,一个简单的编译器 (C1) 运行并生成一些中等质量的代码,这些代码在准备好后就可以使用。然后,运行更好的编译器 (C2) 以生成高度优化的代码。这一切都适用于代码的每个相关部分(只执行几次的部分通常不需要编译)。它实际上有点复杂(谷歌出 OSR 或去优化)。
【解决方案2】:

一件显而易见的事情:使用split() 会减慢您的速度。根据我在网上可以找到的JDK源码,Java不会缓存编译好的正则表达式(如有错误请指正)。

确保在 Java 代码中使用预编译的正则表达式。

【讨论】:

  • 谢谢!!我会试试的,然后告诉你。
猜你喜欢
  • 1970-01-01
  • 2017-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-12-10
  • 2011-01-29
  • 1970-01-01
相关资源
最近更新 更多