13

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.

6
  • 1
    7 Habits of Highly Efficient VF Pages, from Dreamforce '12: slideshare.net/developerforce/df121306-ready
    – Mike Chale
    Aug 21, 2013 at 16:47
  • Which part of the presentation do you think is relevant? I have no view state and the SOQL is efficient.
    – Keith C
    Aug 21, 2013 at 16:56
  • Given that I haven't seen your page, it's hard to say. Are you rendering anything asynchronously? That can often have a shorter perceived load time. Also, I think of that presentation as some simple Best Practices, just in case.
    – Mike Chale
    Aug 21, 2013 at 17:09
  • Not sure that async will help too much: added round-trip delays and the user (probably) still waiting until the page has settled to its final state.
    – Keith C
    Aug 21, 2013 at 17:14
  • I have found apex:repeat is a particularly slow way of iterating. "Avg. Speed" on trust.salesforce.com is average transaction duration.
    – Phil Rymek
    Aug 21, 2013 at 19:57

3 Answers 3

10

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.

4
  • Sounds like you are heading towards recommending a client-side MVC approach (e.g. AngularJS) to work-around the slowness? Having built the Visualforce already that would be a painful step for me to take and it would be difficult to keep the styling consistent with the norms of the platform. Still hoping for some insight on which parts of Visualforce are slow (or what the anti-patterns are) so I can refactor rather than rewrite...
    – Keith C
    Aug 21, 2013 at 23:17
  • The worst offenders are those that use metadata, $ObjectType, inputField, and outputField. Instead, use describes in Apex Code, inputText, and outputText, respectively. However, any VF element is bound to be slower than regular HTML. But you still need to remember to respect Field Level Security; this is the primary reason for using the former elements instead of the latter. Working out the FLS yourself in Apex Code is faster than VF, though. The next worse offenders are repeating elements. Most other elements are okay for "normal use."
    – sfdcfox
    Aug 21, 2013 at 23:27
  • I've been looking at the debug logs including with Visualforce's debug level set to "Finer". That gives a good level of timestamp detail because every formula evaluation is listed so you can work out which part of the page is being generated. Nothing in particular jumps out at least from an informal review: every millisecond has 5-10 outputs fairly consistently as the various parts of the page are rendered. So it appears to me that Visualforce is the bottleneck not the controller Apex - we just don't typically notice the problem because most pages are quite small.
    – Keith C
    Aug 22, 2013 at 10:54
  • Visualforce is ridiculously slow compared to rendering yourself. The presentation posted as a comment in the original post stated that you shouldn't try for "pixel perfect" interfaces. Even salesforce.com's own UI appears different between IE/FF/Chrome/etc. Just make it look "good enough", and opt for client-side rendering when possible, or "normal" HTML elements when possible, and VF elements only when necessary. Try converting your 10-20 objects with 10-20 fields to normal HTML markup with outputText instead of outputField. Big boost.
    – sfdcfox
    Aug 22, 2013 at 13:48
2

I'm going to answer my own question and say that if you want to express your rendering logic in Visualforce and you want to leverage things like SObject fields then the only way to achieve speed is to keep the number of fields shown small. Otherwise you can end up with the controller Apex executing in say 200ms but the Visualforce taking say 2 seconds.

Or get creative with asynchronous data loading, preferably of data that isn't showing.

Or don't use Visualforce.

2

Did you know you can see a performance timeline in the Developer Console using Debug -> Switch Perspective -> 'Analysis' and then clicking on the Timeline?

Four simple suggestions from my experience with rendering large lists in VF:

  1. A larger list means longer query time, longer Visualforce generation time, longer transmission time to the browser and longer rendering time. So keep those lists short.
  2. Showing data from objects and fields (e.g. {!acc.Name}) is up to 3x slower than storing the Account Name in a different variable and use {!accountName}.
  3. Using <apex:outputField> is up to 3x slower than <apex:outputText> because it will evaluate Field Level Security. Don't use it if you don't need it.
  4. You can prevent a whole lot of serialization and data transferring if you make controller variables transient. You should understand the implications though. Read the documentation for more details.

Using the above rules can make your VF page render significantly faster, I have real-world examples of a 10-fold speed increase!

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .