What is it with the Coldfusion Compiler?

Posted by Neil on December 07, 2006

I’ve been taking a fairly in depth look at JSP recently and come across some startling conclusions with regard to CF. For some background for those of you who don’t quite get it - Coldfusion compiles down to a Java .class file and then executes under Jrun as native Java code. JSP is fairly similar in this respect - JSP compiles down to a .class and runs on Jrun (may have an itermediary .java step on servers like Tomcat). JSP can also run natively on CFMX (Jrun) with no changes. So there I was writing some code in JSP and noticed that things were a lot more snappy than the equivalent Coldfusion so I put together some code for a little test.

Consider the following CF:

<cfset start = getTickCount()>

<cfset result = 0>

<cfloop from="1" to="1000000" index="i">
<cfset result = result + i>
</cfloop>

<cfoutput>#result#
</cfoutput>
<cfoutput>Execution Time: #getTickCount() - start#ms</cfoutput>

This code simply loops to a million and tots up a result in the process. The runtime? On my box around 3.5 seconds - OK fair enough.

Then I re-wrote the same code as a JSP (you are seeing the complete files here):

<%
long start = java.lang.System.currentTimeMillis();

long result = 0;
for (long i = 0; i < = 1000000; i++)
{
result += i;
}
%>

<%=result %>
<BR>
Execution Time: <%= java.lang.System.currentTimeMillis() - start %>ms.

Running this from the same folder, same URL on the same machine I was getting an execution time averaging around 10ms. Now, I’m no computer scientist - but two languages running the same process, compiling down the same code and executing on the same server is giving me execution times that are several hundered percent apart? Somethings going on there that is killing Coldfusion.

Question is what, does anyone have any ideas?


You will need some sort of ColdFusion hosting if you decide that your web design will incorporate ColdFusion, so before you invest in a host make sure that ColdFusion hosting is not only an option but a strong focus; and if you run a business then Exchange 2007 hosting may be your email solution.
Trackbacks

Use this link to trackback from your own site.

Comments

Leave a response

  1. Nick Kwiatkowski Thu, 07 Dec 2006 16:45:26 EST

    Well, they don’t compile down to the -exact- code. Remember, Coldfusion being a typeless language will require Java to check the type of variable that it is trying to add everytime you are adding. This costs CPU time.

    What would be interesting is if you pulled the number zero from a DB and tried to do the same routine. If you pull a number from a DB, it will be typefull, and should be a bit quicker.

  2. Rob Gonda Thu, 07 Dec 2006 17:36:50 EST

    No need to pull the number from the DB, you can mimic that with queryNew()… you can also use cfparam and specify a type.

    Nevertheless, that check should not increase time by 35,000% damn!

  3. Rod Hilton Thu, 07 Dec 2006 18:17:26 EST

    Though CF pages compile to servlets, they don’t compile in the same way as JSP pages compile to servlets.

    Remember that CF isn’t strongly typed. This means that, the way CF has to run, all variables you declare are not actually local java variables at the bottom, but are instead references retrieved from a CF variable table. Types are dynamically coerced on every access as well, and the loops don’t compile down the way you might expect either.

    Turn on caching and decompile the class file that your code above generates. You’ll be surprised.

  4. Rod Hilton Thu, 07 Dec 2006 18:20:52 EST

    I also need to point out, you’re using JSP scriptlets, which are directly code in Java. The code you wrote in the scriptlet tag is exactly the code in the .java file that is generated. The CF code is working through tags. If you rewrote your jsp example using JSTL and avoiding scriptlets, you’d find the behavior more similar.

    This doesn’t change what I said above, however. In the end, CF code is slower because of all the dynamic type checking and variable interpretation it has to do.

    You can sort of imagine it like if I wrote a web language and a Java API to interpret it completely. Then I could say that I “compile your page to a servlet” by simply dumping the code you wrote into a string and then using the java api interpreter for the language to process it. Though CF code is significantly more “compiled” than this example, it’s no where near as compiled as JSP code.

  5. Rod Hilton Thu, 07 Dec 2006 18:24:34 EST

    I have no idea if this code will actually make it to your blog, but I figured you’d have an interest in seeing it.

    This is the .java file that’s generated by your exact code in the first example.

    public final class cfblogtest2ecfm1762415691 extends CFPage
    {

    public cfblogtest2ecfm1762415691()
    {
    }

    protected final void bindPageVariables(VariableScope varscope, LocalScope locscope)
    {
    super.bindPageVariables(varscope, locscope);
    START = bindPageVariable(”START”, varscope, locscope);
    RESULT = bindPageVariable(”RESULT”, varscope, locscope);
    I = bindPageVariable(”I”, varscope, locscope);
    }

    protected final Object runPage()
    {
    javax.servlet.jsp.JspWriter out = super.pageContext.getOut();
    javax.servlet.jsp.tagext.Tag parent = super.parent;
    START.set(GetTickCount());
    _whitespace(out, “\r\n\r\n”);
    RESULT.set(”0″);
    _whitespace(out, “\r\n\r\n”);
    double t4 = 1.0D;
    double t6 = Cast._double(”1000000″);
    double t8 = Cast._double(”1″);
    Object value = Cast._Object(t8);
    SetVariable(”i”, value);
    for(; CfJspPage._checkCondition(t4, t8, t6); CfJspPage.checkRequestTimeout(”CFLOOP”))
    {
    _whitespace(out, “\r\n”);
    RESULT.set(Cast._Object(Cast._double(_autoscalarize(RESULT)) + Cast._double(_autoscalarize(I))));
    _whitespace(out, “\r\n”);
    value = Cast._Object(t8 += t4);
    SetVariable(”i”, value);
    }

    _whitespace(out, “\r\n\r\n”);
    OutputTag output0 = (OutputTag)_initTag(class$coldfusion$tagext$io$OutputTag, 0, parent);
    try
    {
    int mode0;
    if((mode0 = output0.doStartTag()) != 0)
    do
    {
    out.write(Cast._String(_autoscalarize(RESULT)));
    _whitespace(out, “\r\n”);
    } while(output0.doAfterBody() != 0);
    if(output0.doEndTag() == 5)
    {
    Object t12 = null;
    return t12;
    }
    }
    catch(Throwable t13)
    {
    output0.doCatch(t13);
    }
    finally
    {
    output0.doFinally();
    }
    _whitespace(out, “\r\n”);
    OutputTag output1 = (OutputTag)_initTag(class$coldfusion$tagext$io$OutputTag, 1, parent);
    try
    {
    int mode1;
    if((mode1 = output1.doStartTag()) != 0)
    do
    {
    out.write(”Execution Time: “);
    out.write(Cast._String(Cast._double(GetTickCount()) - Cast._double(_autoscalarize(START))));
    out.write(”ms”);
    } while(output1.doAfterBody() != 0);
    if(output1.doEndTag() == 5)
    {
    Object t18 = null;
    return t18;
    }
    }
    catch(Throwable t19)
    {
    output1.doCatch(t19);
    }
    finally
    {
    output1.doFinally();
    }
    return null;
    }

    public final Object getMetadata()
    {
    return metaData;
    }

    private Variable START;
    private Variable RESULT;
    private Variable I;
    static final Class class$coldfusion$tagext$io$OutputTag = Class.forName(”coldfusion.tagext.io.OutputTag”);
    static final Object metaData = new AttributeCollection(new Object[0]);

  6. [...] Neil asks on his blog, “What is wrong with the Coldfusion compiler?” [...]

  7. TomK Thu, 07 Dec 2006 19:14:35 EST

    In your CF tests, did you account for all the other issues. Like was your application.cfm template empty and was the first run ignored, because of the first complile and debuging overhead.

  8. [...] We’ve talked about how CF translates into Java many times over the past couple of years and some of the inherent performance problems with weak typing (at some point it still has to be converted back to static typing– this is a basic theoretical problem that any dynamically typed language has), and I showed my co-worker (and primarily Java programmer) Rod the “what is wrong with the ColdFusion compiler” post, and he has a very well written response to how ColdFusion translates its own dynamically typed tag based syntax down to statically typed Java code. Of course the high level Computer Science answer would just be “a parser-generator, duh”, but instead he goes into the details of how everything must be eventually cast/typed. He doesn’t really ever do any ColdFusion, so be easy on him in the comments if you have a bone to pick on the CF side. [...]

  9. vinceb Thu, 07 Dec 2006 19:48:12 EST

    I wrote a blog entry a while ago that talks about this:

    http://tinyurl.com/yen594

  10. Todd Thu, 07 Dec 2006 20:12:30 EST

    Did you have debugging turned on? Go turn it off and look at the difference.

  11. Edward Smith Thu, 07 Dec 2006 20:18:52 EST

    Doing it in CFScript drops it by a factor of 5 for me.

    I went from 1200ms for the tag version to 175ms for this script version.

    start = getTickCount();
    result = 0;
    for (i=1; i lte 1000000; i=i+1)
    {
    result=result+i;
    }
    writeoutput(”#result#Execution Time: #getTickCount() - start#ms”);

  12. Seth Petry-Johnson Thu, 07 Dec 2006 21:10:52 EST

    I’m no expert, but you might be seeing the result of a compiler optimization. In the Java loop, if the compiler realizes that all you’re doing is looping x number of times and incrementing a counter each time, with no side effects to worry about, it might translate that into a single set statement and avoid the loop altogether.

    Since CF is weakly typed there will be a bunch of extra code that is generated “under the hood”. That extra code might prevent the compiler from making the same optimizations and resulting in a loop with a million iterations.

    You might want to add something non-trivial to the body of the loop, such as a method call or dynamic mathematical expression, and then re-run your test.

  13. Sean Corfield Fri, 08 Dec 2006 04:30:59 EST

    I ran your code and on first execution got around 4.5 seconds. On subsequent executions it was around 1.5 seconds. Still a big difference from what you see with JSP (which on my system executes in about 8ms on first hit and 4ms on subsequent hits).

    You’d be closer comparing it to code like this:

    Execution Time: ms.

    The execution time of this is around 130ms. That’s around 30x slower than the example with pure builtin types.

    Now, yes, there’s still one order of magnitude between my JSP above and CF’s time but there’s a lot of additional machinery in the CF code.

    You need to bear in mind that all of these little performance comparisons we see are testing completely unrealistic code fragments.

    After all, how many real world program actually perform a numeric addition 1,000,000 times in a single request? You need to look at the real world difference under load of typical requests - stuff that talks to databases and does simple string processing…

  14. Sean Corfield Fri, 08 Dec 2006 04:31:35 EST

    Aww… my source code got eaten!

    I’ll repost with angle brackets escaped…

  15. Sean Corfield Fri, 08 Dec 2006 04:41:48 EST

    Let’s try this:

    <%
    long start = java.lang.System.currentTimeMillis();

    Object result = new Long(0);
    Object limit = new Long(1000000);for (Object i = new Long(0); ((Long)i).longValue() <= ((Long)limit).longValue(); i = new Long(((Long)i).longValue() + 1))
    {
    result = new Long(((Long)result).longValue() + ((Long)i).longValue());
    }
    %>

    <%=result %>
    <BR>
    Execution Time: <%= java.lang.System.currentTimeMillis() - start %>ms.

  16. Aidan Kane Fri, 08 Dec 2006 09:15:50 EST

    I understand (roughly) the differences in implementation that cause the effects we’re seeing, but I don’t think trying to slow JSP down to make the test a bit fairer is the right way to go.

    Instead we should be asking ourselves - ‘what will CF give me that JSP will not do?’ and futher to that - ‘what solutions/frameworks exist to give us that functionality?’

  17. Neil Fri, 08 Dec 2006 09:31:14 EST

    @Rod Hilton - I guess my point here is “why?” - why would I want to have so much overhead for something that acheives the same end effect?

    @TomK - I ran the file on it’s own with no other CF - and I took an average runtime from a lot of requests.

    @Todd - Yup, it was off

    @Edward - This is something else that stumps me. You would have thought that CFScript and CFTags go through the same compiler and end up with the same net result - but as you can see, that is clearly not the case (and why is one so much faster?)

    @Sean - I see what you are saying, but again why? Why would I want to write some CF (and therefore adopt a hefty license cost at the same time) when the equivalent JSP ( in a lot of cases and leveraging JSTL) is not any harder to do but has better performance (and I would guess better reliability as there is less links in the chain)?

Comments