Java的性能问题与Perl相比

3

我已经编写了一段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

////////////////////更新///////////////////

根据下面的评论, 我用正则表达式替换了分割操作,性能得到了很大提升。 现在我正在循环中执行此操作,并且在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

在最开始的几个实例中,所花费的时间更长,然后时间逐渐缩短,为什么?

谢谢。


3
同样适用于Perl。https://metacpan.org/pod/Text::CSV比你自己实现的更安全。 - simbabque
2
Perl 基本上是一种文本处理语言。它是为了处理文本而开发的。 - Raghavendra
2
有很多方法可以让Perl代码运行更快! - Borodin
1
Text::CSV 可能更安全,但它可能比您现有的实现要慢。 - mob
1
好的,这样更有意义。看看我的回答。如果你想让代码更好,请在code review上发布它。请先稍微整理一下。 - maaartinus
显示剩余6条评论
2个回答

4

由于JIT编译,Java需要一些时间才能达到其最高速度。Java针对运行数小时(或数年)的服务器进行了优化,而不是仅运行几秒钟的小型工具。

关于类加载,我猜你可能不知道例如PatternMatcher,它们在split中间接使用,并在需要时进行加载。


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可能确实更快。

  1. 对我来说,拆分内部正则表达式部分是新的。
  2. 最初我使用了读取器的默认缓冲区大小,但后来我想也许由于更多的磁盘IO,性能较慢,因此我增加了它。 但在时间方面没有任何改变。
- RBanerjee
即使在测试读取方法中,我在文件过滤器之后添加了计时功能,显示主要时间由while循环占用。@maaartinus,你也说Java适合长期运行,是的,这将是一个永远运行的任务,我的问题是,如果它永远运行,那么从第二次或第三次运行开始,性能会有所提高吗? - RBanerjee
1
@user3080158 我敢打赌,当迭代多次时,它会变得更好。典型的基准测试在开始测量时间之前会进行5-20次的抛弃迭代。问题是它有多好。如果不关闭读取器,您很快就会用完文件描述符。+++ 如果您提供数据,其他人可以更努力地进行优化。 - maaartinus
1
@RBanerjee 这就是JIT编译。首先,代码被解释并收集统计信息。同时,一个简单的编译器(C1)运行并生成一些中等质量的代码,当准备好时使用。然后,更好的编译器(C2)运行以生成高度优化的代码。这适用于代码的每个相关部分(通常只执行几次的部分不需要编译)。实际上,这还有点复杂(可以在Google上搜索OSR或去优化)。 - maaartinus
@maaartinus,我得到了答案。对我来说是很多的学习。谢谢!! - RBanerjee
显示剩余4条评论

0
一个显而易见的事情:使用split()会减慢你的速度。根据我在网上找到的JDK源代码,Java不会缓存已编译的正则表达式(如果我错了,请纠正我)。
确保在你的Java代码中使用预编译的正则表达式。

谢谢!我会尝试并告诉你结果。 - RBanerjee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接