我最近收集了五个关于 Groovy 性能的答案的反对票 (!);但是,我认为确实应该需要客观事实。就个人而言,我认为与 Groovy 和 Grails 一起工作是富有成效和乐趣的。然而,有一个需要解决的性能问题。
网络上有许多基准比较,包括this one。你永远不能相信单一的基准(引用的基准甚至不接近科学),但你会明白的。
Groovy 强烈依赖于运行时元编程。例如,Groovy 中的每个对象(除了 Groovy 脚本)都从 GroovyObject 扩展而来,带有 invokeMethod(..) 方法。每次在 Groovy 类中调用方法时,都不会像在 Java 中那样直接调用该方法,而是通过调用前面提到的 invokeMethod(..)(它执行大量反射和查找)。
此外,每个GroovyObject 都有一个关联的MetaClass。方法调用等概念类似。
与 Java 相比,还有其他一些因素会降低 Groovy 的性能,包括原始数据类型的装箱和(可选的)弱类型,但上述运行时元编程的概念至关重要。你甚至无法想到一个带有 Groovy 的 JIT 编译器,它将 Java 字节码编译为本机代码以加快执行速度。
为了解决这些问题,有Groovy++ 项目。您只需用@Typed 注释您的Groovy 类,它们就会静态地 编译成(真正的)Java 字节码。然而不幸的是,我发现 Groovy++ 还不够成熟,并且与 Groovy 主线和 IDE 没有很好的集成。 Groovy++ 也与基本的 Groovy 编程范式相矛盾。此外,Groovy++ 的@Typed 注解不会递归地工作,也就是说,不会影响像 GORM 或 Grails 控制器基础设施这样的底层库。
我猜你也在评估使用 Grails 项目。
查看 Grails 的 GORM 时,该框架大量使用运行时元编程,直接使用 Hibernate,性能应该会更好。
在控制器或(尤其是)服务级别,可以将大量计算外化到 Java 类。但是,GORMs 在典型的 CRUD 应用中所占的比例更高。
Grails 中的潜在性能通常通过在database 级别缓存层或通过避免调用服务或控制器方法来解决(请参阅SpringCache plugin 或Cache Filter plugin)。这些通常在Ehcache infrastructure 之上实现。
与经常变化的(数据库)数据或相当多变的 Web 输出相比,缓存显然更适合静态数据。
最后,您可以“向它扔硬件”。 :-)
总之,支持或反对在大型网站中使用 Groovy/Grails 的最决定性因素应该是缓存是否适合特定网站的性质的问题。
编辑:
至于Java的JIT编译器是否有机会介入的问题...
一个简单的 Groovy 类
class Hello {
def getGreeting(name) {
"Hello " + name
}
}
编译成
public class Hello
implements GroovyObject
{
public Hello()
{
Hello this;
CallSite[] arrayOfCallSite = $getCallSiteArray();
}
public Object getGreeting(Object name) {
CallSite[] arrayOfCallSite = $getCallSiteArray();
return arrayOfCallSite[0].call("Hello ", name);
}
static
{
Long tmp6_3 = Long.valueOf(0L);
__timeStamp__239_neverHappen1288962446391 = (Long)tmp6_3;
tmp6_3;
Long tmp20_17 = Long.valueOf(1288962446391L);
__timeStamp = (Long)tmp20_17;
tmp20_17;
return;
}
}
这只是冰山一角。活跃的 Groovy 开发人员 Jochen Theodoru 是这样说的:
Groovy 中的方法调用包括
通常有几种正常的方法
调用,存储参数的地方
在一个数组中,
必须检索参数,键是
由它们生成,哈希图是
用于查找方法,如果是
失败,那么我们必须测试
兼容的可用方法
方法,选择其中一种方法
根据运行时类型,创建一个
hasmap 的键,然后在
结束,做一个反射,比如调用
方法。
我真的不认为 JIT 内联了如此动态、高度复杂的调用。
至于您的问题的“解决方案”,没有“那样做就可以了”。相反,任务是确定比其他因素更重要的因素以及可能的替代方案和缓解策略,评估它们对您当前用例的影响(“我可以忍受吗?”),最后,确定组合最能(不完全)满足要求的技术。