自家製のプログラムを題材に、JavaとCの速度比較をしてみました。アクセスログからトップへのアクセスだけを切り出すという処理です。処理対象のファイルは某サイトの一ヶ月分のログで8.9Gあります。測定は、OSがLinuxバージョン2.6で、CPUがPentium Dの2.80GHz、メモリを512MB搭載しつつ、7,200RPMでキャッシュが8MBのSATA HDDというマシンで行なっています。
まずは、単純にgrepをかけてみましょう。
% time grep 'GET /[ ?]' access_log > /dev/null grep 'GET /[ ?]' access_log > /dev/null 1219.31s user 7.65s system 99% cpu 20:33.59 total
さすがに8.9Gのファイルを相手にすると時間がかかります。
この処理をJavaで書いてみましょう。標準入力から1行読み込んで、トップページへのアクセスかどうかを正規表現を利用して調べています。もしトップページへのアクセスならば、標準出力にそのログの行を出力します。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.regex.Pattern;
class MyGrep {
private static final Pattern REGEXP = Pattern.compile("GET /[ ?]");
public static void main(String[] args) throws Exception {
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
while ((line = reader.readLine()) != null) {
if(REGEXP.matcher(line).find())
System.out.println(line);
}
}
}
実行結果はgrepよりだいぶ早くなりました。
% time java MyGrep < access_log > /dev/null java MyGrep < access_log > /dev/null 180.81s user 33.76s system 84% cpu 4:13.97 total
さらに最適化を試みてみましょう。Server VMを利用するために、-serverオプションを付加します。
% time java -server MyGrep < access_log > /dev/null java -server MyGrep < access_log > /dev/null 176.78s user 34.23s system 84% cpu 4:09.49 total
このプログラムでは、あまり効果はありませんでしたが、それでもオプションをひとつつけるだけで、4秒ほど早くなりました。 同様のプログラムをCで書いてみましょう。
#include <stdio.h>
#include <regex.h>
#define BUF_SIZE 256
#define PATTERN "GET /[ ?]"
int main() {
char buf[BUF_SIZE];
FILE *fp;
regex_t preg;
regmatch_t pmatch[0];
fp = fopen("/dev/stdin", "r");
regcomp(&preg, PATTERN, REG_EXTENDED | REG_NOSUB);
while(fgets(buf, BUF_SIZE, fp) != NULL) {
if(regexec(&preg, buf, 0, pmatch, 0) == 0) {
printf("%s", buf);
}
}
regfree(&preg);
}
% time ./mygrep < access_log > /dev/null ./mygrep < access_log > /dev/null 48.60s user 6.29s system 27% cpu 3:20.47 total
やっぱりCは速いですね。Rubyで書いてもみました。
#!/usr/bin/ruby REGEXP = /GET \/[ ?]/ STDIN.each do |l| print l if REGEXP === l end
% time ruby mygrep.rb < access_log > /dev/null ruby mygrep.rb < access_log > /dev/null 95.02s user 7.98s system 49% cpu 3:26.06 total
うわ、このケースだとRubyってJavaよりも速い……。

Cでregex.hじゃなくてstfing.hだけ(strcmp)使った場合はどうなんだろう
この例だとJavaでnio使ってもあまり変わらないかな?
8GBにMappedByteBufferは富豪すぎるかもしれませんが...
----MyGrep2.java----
import java.io.FileInputStream;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MyGrep {
private static final Pattern LINE_REGXP = Pattern.compile(".*$", Pattern.MULTILINE);
private static final Pattern REGEXP = Pattern.compile("GET /[ ?]");
public static void main(String[] args) throws Exception {
FileInputStream in = new FileInputStream("access_log");
FileChannel channel = in.getChannel();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
CharBuffer charBuffer = decoder.decode(buffer);
Matcher lineMatcher = LINE_REGXP.matcher(charBuffer);
while (lineMatcher.find()) {
String line = lineMatcher.group();
Matcher matcher = REGEXP.matcher(line);
if (matcher.find()) {
System.out.println(matcher.group());
}
}
in.close();
}
}