Advice on speeding up Visualforce pages?

I have a fairly complex Visualforce page that displays about 10 fields from about 10 different custom object types including a couple of tables, making about 20 objects overall in total (i.e. 200 fields). There is no view state in the page.

I’ve established that the SOQL queries complete in 250ms – the SOQL is not the bottleneck. But the page itself reports numbers like these:

page generation time: 1255ms

This delay is pretty noticeable, but becomes positively annoying when more objects are involved and the page generation time goes up to several seconds. (The SOQL query time remains pretty constant.)

Does anyone have any advice to help with Visualforce performance: should particular constructs or components be avoided? Or is it just a relatively slow page rendering technology?

(This also makes me wonder what activity the “Avg. Speed” reported here http://trust.salesforce.com/trust/status/ relates to.)

PS I’ve taken a look at the Visualforce “FINER” debug log output and have not found anything that stands out as particularly slow, just that the more data the page renders the longer it takes – hardly surprising. The fact that many Visualforce pages are quite small hides the limited performance. But short of just not using Visualforce (e.g. rendering from JSON client-side – a big and difficult to blend in change in the general case) this just looks like a not often mentioned limitation of the Force.com platform.

Answer

In this specific case, consider client-side rendering instead of Visualforce rendering. I wrote up an example of each, with performance considerations:

Pure Visualforce

First, I’m rendering a list of 10,000 items with pure Visualforce inside a data table.

Controller:

public with sharing class repeatvf {
    public repeatvf() {
        startDateTime = JSON.serialize(System.now());
    }

    public class wrapper {
        public string href { get; set; }
        public string value { get; set; }

        public wrapper(string h, string v) {
            href = h;
            value = v;
        }
    }

    static wrapper[] generatewrappers() {
        wrapper[] wrappers = new wrapper[0];
        for(integer i = 0; i < 10000; i++) {
            wrappers.add(new wrapper('http://www.google.com/search?q='+i,'Search Google for '+i));
        }
        return wrappers;
    }

    public wrapper[] getwrappers() {
        return generatewrappers();
    }

    public string startDateTime { get; set; }

    public string endDateTime { get { return JSON.serialize(System.now()); } }
}

Page Code:

<apex:page controller="repeatvf" readOnly="true">

<script>
    var startDateTime = JSON.parse('{!startDateTime}');
</script>
<div id="output">
    <apex:dataTable value="{!wrappers}" var="wrapper">
        <apex:column >
            <apex:outputLink value="{!wrapper.href}">{!wrapper.value}</apex:outputLink>
        </apex:column>
    </apex:dataTable>
</div>
<script>
    var endDateTime = JSON.parse('{!endDateTime}');
</script>
<script>
function onload() {
    var div = document.getElementById('totalTime'), output = document.getElementById("output");
    output.style.display = 'none';
    div.appendChild(document.createTextNode('Total Generation Time: '+(Date.parse(endDateTime) - Date.parse(startDateTime))));
}
window.addEventListener('DOMContentLoaded', onload, true);
</script>
<div id="totalTime">
</div>
</apex:page>

In my browser, this code consistently runs between values of 1,800 and 2,500 during the time of this trial.

Low       High
1834      2687

Remoting

Here is identical code, using remoting instead of pure Visualforce:

Controller:

public with sharing class renderjs {
    public renderjs() {
        startDateTime = JSON.serialize(System.now());
    }

    public class wrapper {
        public string href { get; set; }
        public string value { get; set; }

        public wrapper(string h, string v) {
            href = h;
            value = v;
        }
    }

    static wrapper[] generatewrappers() {
        wrapper[] wrappers = new wrapper[0];
        for(integer i = 0; i < 10000; i++) {
            wrappers.add(new wrapper('http://www.google.com/search?q='+i,'Search Google for '+i));
        }
        return wrappers;
    }

    public wrapper[] getwrappers() {
        return generatewrappers();
    }

    @RemoteAction
    public static wrapper[] wrappers() {
        return generatewrappers();
    }

    public string startDateTime { get; set; }

    public string endDateTime { get { return JSON.serialize(System.now()); } }
}

Page:

<apex:page controller="renderjs">
<script>
var jsStartTime = new Date(), vfRemoteStartTime;
function render(data, event) {
    var vfRemoteEndTime = new Date(), jsRenderTime, jsEndTime, div, table, tbody, tr, td, a, ctr, jsTime, vfTime, vfRemoteTime;
    div = document.getElementById('outputArea');
    table = document.createElement('table');
    tbody = document.createElement('tbody');
    for(ctr = 0; ctr < data.length; ctr += 1) {
        tr = document.createElement('tr');
        td = document.createElement('td');
        a = document.createElement('a');
        a.href = data[ctr].href;
        a.appendChild(document.createTextNode(data[ctr].value));
        td.appendChild(a);
        tr.appendChild(td);
        tbody.appendChild(tr);
    }
    table.appendChild(tbody);
    div.appendChild(table);
    jsEndTime = new Date();
    div.style.display = 'none';
    div = document.getElementById('resultArea');
    vfTime = Date.parse(vfEndDateTime) - Date.parse(vfStartDateTime);
    jsTime = jsEndTime - jsStartTime;
    vfRemoteTime = vfRemoteEndTime - vfRemoteStartTime;
    jsRenderTime = new Date() - vfRemoteEndTime;
    div.appendChild(document.createTextNode('VF Time: '+vfTime+', VF Remote Time: '+vfRemoteTime+', JS Time: '+jsTime+', JS Render Time: '+jsRenderTime));

}
function onload() {
    vfRemoteStartTime = new Date();
    renderjs.wrappers(render);   
}
window.addEventListener('DOMContentLoaded', onload, true);
</script>
<div id="outputArea">
</div>
<div id="resultArea">
</div>
<script>
var vfStartDateTime = JSON.parse('{!startDateTime}'), vfEndDateTime = JSON.parse('{!endDateTime}');
</script>
</apex:page>

In this example, I get to see the effects of rendering locally, including better time stamps. I have four values I can view: The initial loading time, the time spent remoting, the time elapsed in the page from start to end (the “JS” time), and the time spent rendering (“JS Render Time”).

Note that I could get VF rendering time using logs, but I’m just interested in a quick demonstration. Note that I end up with a total rendering time of 1,200 to 1,500, at least a third of a second faster, and in many cases up to a second faster.

This page gives me the following values:

 VF Time          VF Remote Time       JS Time          JS Render Time
 Low    High      Low      High        Low     High     Low     High
 19     25        1085     1277        1258    1451     91      97

Observations

Assuming that VF Time + VF Remote Time in the second set of code is the same approximate non-rendering time as the first page, I can clearly see that I have a rendering time of over 700 ms. Conversely, my browser is rendering the same data in less than 100 ms.

So, we can see from these examples, that the following cases are true:

  • The increased time until the page is available stems from two factors: bandwidth and Visualforce. First, we’re actually transferring far less data than we would be with pure HTML, because we only transfer the data, not the formatting. Secondly, “expressions” require time to evaluate that are much faster in JavaScript than in Visualforce. Had I used conditional rendering, it would have had a more profound effect.

  • The user has immediate (<0.03 seconds) that the page is indeed loading, instead of the 1.8 to 2.5 second wait without remoting. That said, we could also gain speed boosts by using rerender-on-load (e.g. the main page simply loads, then has a JS function that calls a reRender). It still wouldn’t be faster than pure remoting, however.

  • The overall time until the page is usable is reduced by up to a second, up to a 60% decrease of loading time. The user doesn’t have as long to wait.

That being said, this model won’t always work, and shouldn’t be advocated as the end-all solution. However, whenever you’re rendering a ton of data that won’t need to be edited, consider remoting whenever possible to reduce loading time.

Attribution
Source : Link , Question Author : Keith C , Answer Author : sfdcfox

Leave a Comment