代码改变世界

尝试使用IKVM运行Lucene 2.9.0版

2009-10-09 15:26  Jeffrey Zhao  阅读(...)  评论(...编辑  收藏

上月末Lucene发布了2.9.0版,这个版本的改进比较明显,主要是各方面性能的增强,以及对数字字段范围查询的直接支持。这个版本还有个重要的意义,就是它标记了Lucene 3在API上的改变,及早跟进的话对Lucene 3的未来接受程度会比较好。Lucene的更新很慢,而移植到.NET平台上的Lucene.NET二进制发布则更是一直停留在07年三月的2.0版本。虽然我们可以通过svn获取到Lucene.Net 2.3.2的源代码自行编译,但这次我还是想直接使用最新的2.9.0版本。

至于如何在.NET平台上调用Java代码,我想过各种方式,例如我最初设想使用Java => JNI => C++/CLI => C#的“曲线救国”方式,也考虑过最保险的借助HTTP做Proxy的方式。不过最后在RednaxelaFX的建议下,我决定趁这个机会尝试一下IKVM.NET(下文称为IKVM)。IKVM的目的是在.NET平台(包括mono)上实现一个Java执行环境(也就是JVM),它可以将jar包直接运行在.NET平台上,将Java语言的源文件编译为.NET的程序集,而现在我们使用它的另一个功能,直接把Lucene 2.9.0的二进制jar包转化为.NET下的dll。

IKVM在我博客的侧边栏上停留了很久,不过我一直认为它还不太成熟于是没有怎么去接触。现在从Case Studies上来看,IKVM的可用性似乎还是有一定保障的。IKVM目前实现了JDK 1.6,也可谓紧跟Java脚步。对于这种转化,首先要看中的应该是“正确性”,对于纯粹内存中的对象来说,我猜想应该不会有什么正确性方面的问题。而IO操作就相对值得斟酌了,例如对于文件锁的处理,对于数据流的读写,即使差1个字节可能也会造成大问题。不过,就我初步使用下来的感受,效果比想象中要好很多。在我测试的场景下,使用IKVM Lucene和Java Lucene的索引可以互相读写,没有发生任何问题。当然,这还需要更进一步的测试,这也是我写这篇文章的目的。

在保证了正确性之后,要关注的便是性能了。根据我的推测,由于IKVM需要在Java生成的.NET程序集和BCL之间加上一层Runtime和JDK,因此其性能几乎一定会比Java原有的程序要差。不过,对于Lucene这种项目来说,算法才是性能的关键。例如,有人测试Lucene 2.9.0在某些情况下会比2.4有15倍左右的性能提升。不过由于没有很好的测试数据和场景,目前我只进行了最最简单的,不涉及磁盘IO的性能比较。

性能比较的场景很简单,我准备了《神雕侠侣》全文作为索引数据,2兆多的文本文件,反复使用StandardAnalyzer向RAMDirectory中添加索引,并在以下四种环境下统计索引100遍所消耗的时间(统计时都会对JIT进行“预热”)。

  • 在.NET平台下使用Lucene.Net 2.3.2(需获取源代码自行编译)。
  • 在.NET平台下使用IKVM运行Lucene 2.9.0。
  • 在Java平台下使用默认的Client VM运行Lucene 2.9.0。
  • 在Java平台下使用Server VM(加-server参数)运行Lucene 2.9.0。

我将所有的测试数据、类库、源文件都打包了,感兴趣的朋友可以下载以后查看。压缩包内的DotNet和Java目录中有着相似的内容:

  • lib目录:保存所需的类库(dll或jar文件)
  • src目录:保存测试用源代码(cs或java文件)
  • build.bat:调用csc或javac编译src下的源文件,将结果输出至bin目录
  • clean.bat:清除编译结果(即删除build文件)
  • run.bat:运行测试程序

在执行build.bat文件的时候,需要保证PATH中包含了正确的目录,可以访问到csc或者javac。在使用run.bat运行测试程序时,也可以添加参数进入不同的分支。例如对.NET的测试中:

...\DotNet> build

...
...

...\DotNet> :: 测试Lucene.Net 2.3.2
...\DotNet> run

...
...

...\DotNet> :: 测试IKVM Lucene 2.9.0
...\DotNet> run -ikvm

...

而在Java的测试中:

...\Java> build

...
...

...\Java> :: Client VM
...\Java> run

...
...

...\Java> :: Server VM
...\Java> run -server

...

在我的笔记本上(Windows 7,双核2.0 Ghz CPU,2G RAM),执行结果如下:

以上图表展示的是每种环境下各执行3次的平均耗时。可见,性能从高到低依次如下:

  1. Java Server VM + Lucene 2.9.0
  2. .NET + Lucene.Net 2.3.2
  3. Java Client VM + Lucene 2.9.0
  4. .NET + IKVM + Lucene 2.9.0

从结果上看,Java Server VM的表现最好,而且领先幅度较大,相对于IKVM有35%左右的性能优势。不过这个结果让我非常满意,因为在我看来,这完全处在一个可以接受的范围之内,绝大部分系统根本不会在乎这点性能差距。Java平台的优势是类库数量,如果IKVM可以仅靠这点性能损失换来.NET对Java类库的使用能力,天下没有更合算的事情了。

不过让我更加感兴趣的是Java Server VM + Lucene 2.9.0和.NET + Lucene .Net 2.3.2的比较结果,我承认这个差距让我始料未及,因为就我过去的经验,相同的.NET和Java应用程序,.NET的性能会领先Java。在简单思考过后,我猜想有以下的几点可能:

  • Lucene 2.9.0的算法本身比Lucene 2.3.2效率高。
  • JVM的Memory Consistency Model比CLR来的宽松,因而高效。
  • JVM的JIT优化比CLR做的好。

HotSpot的白皮书称,Server VM与Client VM的除了Heap大小,GC等参数方面不同以外,最大的区别在于Server的JIT会尝试更多的优化手段,这会导致较多的启动时间,以此换来更好的执行效率:

The Client VM compiler does not try to execute many of the more complex optimizations performed by the compiler in the Server VM, but in exchange, it requires less time to analyze and compile a piece of code. This means the Client VM can start up faster and requires a smaller memory footprint.

The Server VM contains an advanced adaptive compiler that supports many of the same types of optimizations performed by optimizing C++ compilers, as well as some optimizations that cannot be done by traditional compilers, such as aggressive inlining across virtual method invocations. This is a competitive and performance advantage over static compilers. Adaptive optimization technology is very flexible in its approach, and typically outperforms even advanced static analysis and compilation techniques.

而编译器及虚拟机大牛RednaxelaFX在推特上也告诉我:

话说GC方面CLR或许非常先进,但JIT方面就还是HotSpot强(和复杂)一些了。CLR没办法inline虚方法或者接口上的方法的调用,HotSpot却可以……总之这种对比的结果要看你测试的代码的性质

至于具体的情况,RednaxelaFX建议查看HotSpot和CLR在JIT之后的Native Code,这并非我所擅长的东西,于是就希望R大可以给出更详细的结果了。:)

最后,便是本文的目的了:目前的测试缺少实际价值,那么您有兴趣和我一起做一个更接近生产环境的实验吗?或者说,您在生产环境中是如何使用Lucene的呢?