调用组件函数与内嵌代码的开销比较 - ColdFusion

9
我一直在诊断生成包含约50,000行的CSV文件的性能问题,我已经将其缩小到每行使用一次的单个函数。
经过很多尝试,我发现使用该函数会产生额外开销,而不是直接将逻辑放在循环中-我的问题是:为什么?!
所涉及的函数非常简单,它接受一个字符串参数,并将其传递给包含大约15个选项的switch / case块,返回结果字符串。我在各个地方放了一堆计时器,并发现很多(不是全部)时间这个函数调用需要花费0到200毫秒才能运行...但是如果我将完全相同的代码内联,它在每次迭代上都为0。
所有这些都指向我对对象实例化的理解存在根本性问题,我希望得到一些澄清。
我一直以为,如果我在页面顶部实例化组件,或者确实在应用程序或会话等持久范围中实例化它,那么它将被放置在内存中,并且对该组件内的函数的后续调用将非常快速。然而,似乎调用这些函数存在开销,虽然我们只谈论几毫秒,但当您必须执行50,000次时,它很快就会累加。
此外,这样做似乎会消耗资源。我对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 - Application.cfc

http://pastebin.com/X8ZjL7D7 - TestCom.cfc(放在Web根目录外的'com'文件夹中)

http://pastebin.com/n8hBLrfd - index.cfm


2
哦,确保在cfcomponent和cffunction标签上设置output=false!这会增加开销,可能会减慢速度。 - Peter Boughton
1
为什么不在页面上创建一个结构体并以这种方式引用它?我认为函数不是处理这个问题的最佳方法。 - Matt Busche
1
此外,精确的CF和JVM版本可能也很重要,因此应提供该信息。 - Peter Boughton
1
CF通过值而不是引用传递简单对象。每次调用函数时,内存中都会为Arguments.VehicleID和Local.Category创建两个新值。您的减速可能是由于函数完成后新创建变量的垃圾回收。当您在同一行中运行相同的代码时,我假设您正在为现有变量分配新值,或者只是根据CSV文件中的值进行切换,这不会使用任何额外的内存。 - Twillen
2
没有读完所有最新的评论,但是有一个快速提示:如果在调试设置中启用了“报告执行时间”,请将其关闭 - 这可能会导致错误的缓慢。 - Peter Boughton
显示剩余16条评论
2个回答

2

在任何语言中,函数调用总是比内联代码慢。这就是为什么C++中有 inline 关键字,在JVM领域中有JIT优化器,如果它认为必要,就会为您内联函数。

现在ColdFusion是建立在JVM之上的另一层。因此,CF中的一个函数不是JVM中的一个函数,所以从JIT优化器的角度来看,事情并没有1:1的对应关系。CFML函数实际上被编译成Java类。此外,像 argumentslocal(Java哈希表)这样的作用域会在每次调用时被创建。这需要时间和内存,因此存在额外开销。

...如果我将其实例化到类似Application或Session这样的持久性作用域中,则它将被放入内存,并且对该组件中的函数的后续调用将非常快速

与新实例化相比,它肯定会更快,但当您在紧密循环中调用它时,它不会变得"非常快速"。

总之,内联函数,如果还不够快,请定位代码中最慢的部分并使用Java编写。


1
虽然这并没有解释为什么我的生产服务器在函数调用时看到了更慢的请求,但它确实回答了问题...让我感到非常沮丧! 出于封装、可重用性和清晰代码的原因,我一直朝着将复杂的内联代码分解成越来越小的函数的方向发展,所以这是一个必须面对的残酷现实! - Gary Stanton
你的生产服务器开启了类型检查吗?在旧版本中,这会使速度变慢很多。为了提高性能,也许你可以尝试启用可信缓存。 - Henry
它确实开启了类型检查,但是它是CF10,所以如果只会影响旧版本,那就不适用了。不过等我回到服务器时我会试一下的。试试也无妨。 - Gary Stanton
检查和禁用服务器监控。 - Henry
这是一个残酷的现实,方法会更慢,即使代码结构良好,也可能会变得非常缓慢,当处理大量数据时,这肯定会产生影响。 - J.T.

0

这里有一个小提示,由于Railo使用内部类而不是完全独立的类,因此如果您编写许多小函数的样式,则速度会更快。在我的实验中,两个引擎在基本的内联代码方面表现相似。如果您需要在负载下提高性能,则Adobe ColdFusion适合编写大型“god”函数。由于JVM无法在编译期间内联ColdFusion函数,因此您永远无法从编译器智能处理您的代码中获得好处。

如果您创建了一个使用大量显式getter/setter的应用程序,并且发现流量从小到大增加,那么所有这些小函数将使您屈服于较少的大型“god”函数。

我们进行了一个基本测试,运行了100,000次迭代,从最慢到最快的顺序如下:

Adobe ColdFusion(许多小函数)(比Java慢200倍) Railo(许多小函数)(比Java慢60倍) ColdFusion / Railo(所有代码都内联在一个巨大的函数中)(比Java慢10倍) Native Java Class(最快)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接