正则表达式在java和perl中的性能。慢8倍

正则表达式在java和perl中的性能。慢8倍,java,regex,perl,Java,Regex,Perl,我必须用java重写一些遗留的perl应用程序。 这个应用程序进行大量的文本处理。 但是java正则表达式的速度几乎是perl的6-8倍。 我如何优化此服务器的性能 Java代码需要26秒来替换字符串50k次 Perl代码需要4秒 为了复制场景,我将字符串托管在一个在线文件中。在我的实际用例中,这个字符串将来自输入队列 import java.io.IOException; import java.io.PrintStream; import java.net.URL; import java.

我必须用java重写一些遗留的perl应用程序。 这个应用程序进行大量的文本处理。 但是java正则表达式的速度几乎是perl的6-8倍。 我如何优化此服务器的性能

Java代码需要26秒来替换字符串50k次 Perl代码需要4秒

为了复制场景,我将字符串托管在一个在线文件中。在我的实际用例中,这个字符串将来自输入队列

import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;


public class RegexT {

    public static void main(String[] args) throws IOException {
        //RegexTest r = new RegexTest();
        Map<String, String> m = new HashMap<>();
        m.put("${ TO }", "rcpt");
        m.put("${ MESSAGE_ID }", "37");
        m.put("${ ID }", "40");
        m.put("${ UNIQID }", "cff47534-fe6b-c45a-7058-8301adf1b97");
        m.put("${ XOR }", "abcdef");

        System.out.println(m);

        String rx = "(\\$\\{[^}]+\\})";

        Pattern p = Pattern.compile(rx);
        String s = readStringFromURL("https://raw.githubusercontent.com/ramprasadp/hostedtexfiles/master/msg2.txt");
        //        System.out.println(s); System.exit(0);

        long start = System.currentTimeMillis();
        for (int i = 0; i < 50000; i++) {
            StringBuffer sb = new StringBuffer();
            Matcher mat = p.matcher(s);

            while (mat.find()) {
                String repString = m.get(mat.group(1));
                if (repString != null) {
                    mat.appendReplacement(sb, repString);
                }
            }
            mat.appendTail(sb);
        }

        long timeTaken = System.currentTimeMillis() - start;
        System.out.println("Time taken in ms = "+ timeTaken);

    }

    public static String readStringFromURL(String requestURL) throws IOException {
        try (Scanner scanner = new Scanner(new URL(requestURL).openStream(),
                StandardCharsets.UTF_8.toString())) {
            scanner.useDelimiter("\\A");
            return scanner.hasNext() ? scanner.next() : "No file";
        }
    }


}
import java.io.IOException;
导入java.io.PrintStream;
导入java.net.URL;
导入java.nio.charset.StandardCharset;
导入java.util.HashMap;
导入java.util.Map;
导入java.util.Scanner;
导入java.util.regex.Matcher;
导入java.util.regex.Pattern;
导入java.nio.file.Files;
导入java.nio.file.path;
导入java.io.BufferedReader;
导入java.io.FileReader;
导入java.io.IOException;
公共类正则表达式{
公共静态void main(字符串[]args)引发IOException{
//RegexTest r=新的RegexTest();
Map m=新的HashMap();
m、 put(“${TO}”,“rcpt”);
m、 放置(${MESSAGE_ID},“$37”);
m、 放置(“${ID}”,“40”);
m、 投入(${UNIQID}),“cff47534-fe6b-c45a-7058-8301adf1b97”);
m、 put(“${XOR}”,“abcdef”);
系统输出打印项次(m);
字符串rx=“(\\$\{[^}]+\})”;
模式p=模式编译(rx);
字符串s=readStringFromURL(“https://raw.githubusercontent.com/ramprasadp/hostedtexfiles/master/msg2.txt");
//系统输出打印项次;系统退出(0);
长启动=System.currentTimeMillis();
对于(int i=0;i<50000;i++){
StringBuffer sb=新的StringBuffer();
Matcher mat=p.Matcher(s);
while(mat.find()){
String repString=m.get(mat.group(1));
if(repString!=null){
材料附件更换(sb、repString);
}
}
mat.appendTail(某人);
}
long-timetake=System.currentTimeMillis()-开始;
System.out.println(“以毫秒为单位的时间=+所用时间”);
}
公共静态字符串readStringFromURL(字符串请求URL)引发IOException{
try(Scanner Scanner=new Scanner(new URL(requestURL).openStream(),
StandardCharsets.UTF_8.toString()){
scanner.useDelimiter(\\A”);
返回scanner.hasNext()?scanner.next():“无文件”;
}
}
}
在perl中也有相同的逻辑

#!/usr/bin/perl
use Time::HiRes qw( gettimeofday tv_interval );
use strict;
my %data;

$data{'TO'} = "rcpt";
$data{'MESSAGE_ID'} = "37";
$data{'ID'} = "7";
$data{'UNIQID'} = "cff47534-fe6b-c45a-7058-8301adf1b97";
$data{'XOR'} = "abcdef";


#Get the content
my $msg_string = `wget -q -O - http://raw.githubusercontent.com/ramprasadp/hostedtexfiles/master/msg2.txt`;

my $start = [gettimeofday];
for (my $j=0;$j<50000; $j++) {
    my $tmp_string = $msg_string;
    $tmp_string =~ s/\$\{ ([\w_]+) \}/$data{$1}/g;
}
print "Time taken in ms is " . 1000 * tv_interval ( $start )."\n";
#/usr/bin/perl
使用时间::租用qw(gettimeofday tv_interval);
严格使用;
我的%数据;
$data{'TO'}=“rcpt”;
$data{'MESSAGE_ID'}=“37”;
$data{'ID'}=“7”;
$data{'UNIQID'}=“cff47534-fe6b-c45a-7058-8301adf1b97”;
$data{'XOR'}=“abcdef”;
#获取内容
my$msg_string=`wget-q-O-http://raw.githubusercontent.com/ramprasadp/hostedtexfiles/master/msg2.txt`;
我的$start=[gettimeofday];

对于(my$j=0;$j简单的子字符串处理将其降低到12-13s:

    long start = System.currentTimeMillis();
    for (int i = 0; i < 50000; i++) {
        String tmpS = s;
        for (Entry<String, String> ms : m.entrySet()) {
            int index = -1;
            while ((index = tmpS.indexOf(ms.getKey())) >= 0) {
                tmpS = tmpS.substring(0, index) + ms.getValue() + tmpS.substring(index + ms.getKey().length());
            }
        }
    }
long start=System.currentTimeMillis();
对于(int i=0;i<50000;i++){
字符串tmpS=s;
对于(条目ms:m.entrySet()){
int指数=-1;
而((index=tmpS.indexOf(ms.getKey())>=0){
tmpS=tmpS.substring(0,index)+ms.getValue()+tmpS.substring(index+ms.getKey().length());
}
}
}
因为您知道每个标记都有一个匹配项,所以可以稍微优化它,将执行时间降低到7.5秒:

        for (Entry<String, String> ms : m.entrySet()) {
            int index = tmpS.indexOf(ms.getKey());
            tmpS = tmpS.substring(0, index) + ms.getValue() + tmpS.substring(index + ms.getKey().length());
        }
for(条目ms:m.entrySet()){
int index=tmpS.indexOf(ms.getKey());
tmpS=tmpS.substring(0,index)+ms.getValue()+tmpS.substring(index+ms.getKey().length());
}
Perl速度不是很快,但仍然比JavaRegexp方法快4倍

如果你真的想节省几秒钟,你可以利用你的标签出现在邮件开头这一事实。我不知道是否总是这样,但这是一个公平的猜测。这只需要2.5秒:

    for (int i = 0; i < 50000; i++) {
        int i1 = s.lastIndexOf("}") + 1;
        String tmpS = s.substring(0, i1);
        for (Entry<String, String> ms : m.entrySet()) {
            int i2 = tmpS.indexOf(ms.getKey());
            tmpS = tmpS.substring(0, i2) + ms.getValue() + tmpS.substring(i2 + ms.getKey().length());
        }
        String result = tmpS + s.substring(i1);
    }
for(int i=0;i<50000;i++){
int i1=s.lastIndexOf(“}”)+1;
字符串tmpS=s.substring(0,i1);
对于(条目ms:m.entrySet()){
inti2=tmpS.indexOf(ms.getKey());
tmpS=tmpS.substring(0,i2)+ms.getValue()+tmpS.substring(i2+ms.getKey().length());
}
字符串结果=tmpS+s.substring(i1);
}
现在,Java实现比最初的Perl实现更快


我还组合了我的
s.lastIndexOf(“}”)
使用原始的
regexp
方法进行技巧,然后只花了2.3秒。因此这将是我的建议,因为您可以假设您的标记总是在字符串的开头。

除非对该代码进行更深入的基准测试或分析,否则很难单独估算这种低性能对于正则表达式实现(尽管我倾向于——推测性地——同意您的意见……)

因此,我尝试了这个精确的代码,在我的机器上运行花费了14秒。我尝试了并行运行,这将从14秒减少到了3秒:

IntStream.iterate(0, i -> i+1).limit(50000).parallel().forEach((i) -> {

    Matcher mat = p.matcher(s);
    StringBuffer sb = new StringBuffer();

    while (mat.find()) {
        String repString = m.get(mat.group(1));
        if (repString != null) {
            mat.appendReplacement(sb, repString);
        }
    }
    mat.appendTail(sb);
});
现在,我假设您的确切问题与代码是否按顺序运行这一事实没有特别的联系(您的实际应用程序不太可能在单个字符串上执行这个50000查找/替换练习),但这至少会导致您的问题中没有提到的其他方面(甚至根本没有考虑到)。否则,它还告诉您,如果这是一个如此频繁使用的东西,您最好并行运行。换句话说,这回答了“如何优化此性能”的问题

您可能需要将单个执行的结果与尽可能少的其他类的参与进行比较,以最小化其他因素的影响,但是比较不同语言的性能肯定仍然具有挑战性


另一种选择Map<String, String> binding = new HashMap<>(); binding.put("TO", "rcpt"); binding.put("MESSAGE_ID", "37"); binding.put("ID", "40"); binding.put("UNIQID", "cff47534-fe6b-c45a-7058-8301adf1b97"); binding.put("XO", "abcdef"); binding.put("XOR", "abcdef"); String text = s; groovy.text.SimpleTemplateEngine engine = new groovy.text.SimpleTemplateEngine(); Template template = engine.createTemplate(text); for (int i = 0; i < 50000; i++) { template.make(binding).toString(); } long timeTaken = System.currentTimeMillis() - start; System.out.println("Time taken in ms = "+ timeTaken); }