我一直在诊断生成一个包含约50000行的CSV的性能问题,并将其缩小到每行使用一次的单个函数。
经过一番折腾,我发现使用函数而不是将逻辑直接放在循环中会有开销——我的问题是:为什么?!
有问题的函数非常简单,它接受一个字符串参数,并将其传递给一个包含大约15个选项的switch/case块,返回结果字符串。我到处都放了一堆计时器,发现这个函数调用的很多(不是全部)时间都在0到200毫秒之间。。。然而,如果我把完全相同的代码放在内联中,它在每次迭代中都位于0。
所有这些都指向了我对对象实例化的理解中的一个基本问题,我希望能澄清一下。
我一直认为,如果我在页面顶部实例化一个组件,或者如果我在像Application或Session这样的持久作用域中实例化它,那么它就会被放入内存中,随后对该组件中函数的调用会非常快。然而,调用这些函数似乎有开销,虽然我们只讨论了几毫秒,但当你必须这样做50000次时,它很快就会累积起来。
此外,这样做似乎会消耗资源。我对JVM使用内存的方式不是特别熟悉,我读过它,也玩过设置之类的东西,但这是一个压倒性的话题,尤其是对于我们这些没有Java开发经验的人来说。看起来,当通过内联代码调用该方法时,有时ColdFusion服务会崩溃,请求永远不会结束。其他时候它确实完成了,尽管太慢了。这表明只有当服务器有资源处理请求时,请求才能完成,因此方法调用本身正在消耗内存。。。(?)
如果一个方法的调用确实有附加开销,那么我就有一个大问题了。将所有这些代码都内联是不可行的(虽然有问题的函数很简单,但我还需要使用很多其他函数),这样做违背了我作为开发人员的所有信念!!
因此,如有任何帮助,我们将不胜感激。
为了清楚起见,因为我相信有人会要求它,下面是有问题的代码:
编辑:根据建议,我已经将代码更改为使用结构查找,而不是CFSwitch-下面是修改后的代码以供参考,但底部的pastebin链接中也有一个测试应用程序。
init方法内部:
<cfset Variables.VehicleCategories = {
'T1' : 'Beetle'
, 'T1C' : 'Beetle Cabrio'
, 'T2' : 'Type 2 Split'
, 'T2B' : 'Type 2 Bay'
, 'T25' : 'Type 25'
, 'Ghia' : 'Karmann Ghia'
, 'T3' : 'Type 3'
, 'G1' : 'MK1 Golf'
, 'G1C' : 'MK1 Golf Cabriolet'
, 'CADDY' : 'MK1 Caddy'
, 'G2' : 'MK2 Golf'
, 'SC1' : 'MK1/2 Scirocco'
, 'T4' : 'T4'
, 'CO' : 'Corrado'
, 'MISC' : 'MISC'
} />
正在调用的函数:
<cffunction name="getCategory" returntype="string" output="false">
<cfargument name="vehicleID" required="true" type="string" hint="Vehicle type" />
<cfscript>
if (structKeyExists(Variables.VehicleCategories, Arguments.VehicleID)) {
return Variables.VehicleCategories[Arguments.VehicleID];
}
else {
return 'Base SKUs';
}
</cfscript>
</cffunction>
根据请求,我创建了一个测试应用程序来复制这个问题:
http://pastebin.com/KE2kUwEf-应用.cfc
http://pastebin.com/X8ZjL7D7-TestCom.cfc(放在webroot外部的"com"文件夹中)
http://pastebin.com/n8hBLrfd-index.cfm
在任何语言中,函数调用总是比内联代码慢。这就是为什么C++中有inline
关键字,JVM中有JIT优化器,如果它认为有必要,它会为您内联函数。
现在ColdFusion是JVM之上的又一层。因此,CF中的函数不是JVM中的函数,所以从JIT优化器的角度来看,事情不会1:1转换。CFML函数实际上被编译为Java类。此外,每次调用都会创建arguments
、local
(Java哈希表)等作用域。这需要时间和内存,因此也需要开销。
如果我在像Application或会话,然后将其放入内存,并随后调用该组件内的功能将是闪电般快速的
它肯定比实例化一个新实例快,但不会"闪电般快",尤其是当你在一个紧密的循环中调用它时。
总之,内联函数,如果它仍然不够快,请找到代码中最慢的部分并用Java编写。
这里只需要补充一点,因为Railo使用内部类而不是完整的独立类,所以如果您以这样一种风格编写,即具有许多小函数,则会更快。在我的实验中,两个引擎在使用基本的内联代码时表现相似。如果您需要在负载下提高性能,Adobe ColdFusion可以提供大型神函数。由于JVM在编译过程中无法内联ColdFusion函数,您将永远无法从编译器对代码的智能化中获益。
如果您创建了一个使用大量显式getter/setter的应用程序,并且发现流量从小容量增加到大容量,那么这一点尤为重要。所有这些小功能都会让你屈服,而不是拥有更少的大型"上帝"功能。
我们进行了100000次迭代的一个基本测试,从最慢到最快:
Adobe ColdFusion(许多小功能)(比Java慢200X)Railo(许多小功能)(速度慢60X)ColdFusion/Railo(所有代码都在一个巨大的函数中内联)(慢10倍)本机Java类(最快)