Why are foreach loops slower in Apex than standard for loop?

Considering the following test class:

@IsTest
private class ProfilingTest {

    private static String TEST_STR = '';

    public static testMethod void testForEachLoop(){
        List<Account> accs = [SELECT Id, Name FROM Account LIMIT 5000];

        for(Account acc : accs){
            acc.Name += TEST_STR;
        }
    }


    public static testMethod void testForLoop(){
        List<Account> accs = [SELECT Id, Name FROM Account LIMIT 5000];

        for(Integer i = 0; i < accs.size(); ++i){
            accs[i].Name += TEST_STR;
        }
    }


    public static testMethod void testImprovedForLoop(){
        List<Account> accs = [SELECT Id, Name FROM Account LIMIT 5000];
        Integer size = accs.size();

        for(Integer i = 0; i < size; ++i){
            accs[i].Name += TEST_STR;
        }
    }


    public static testMethod void testWhileLoop(){
        List<Account> accs = [SELECT Id, Name FROM Account LIMIT 5000];
        Integer size = accs.size();
        Integer i = 0;

        while(i < size){
            accs[i].Name += TEST_STR;
            ++i;
        }
    }
}

And the test result : enter image description here

Is there a particular reason why standard for loop is faster in Apex than for-each loop?
What I think is it may be because the ‘i’ knows which element to pick and for every iteration the size of the list gets smaller and smaller.

But I need some more explanation as why are we seeing huge performance advantage when using standard for loop ?

Answer

It’s because for each loops end up using an Iterator, which is inherently slower than a normal for loop. Here’s what it might compile to internally:

Iterator<Object> iter = source.iterator();
while(iter.hasNext()) {
  Object next = iter.next();
  forLoopBody.yield(next);
}

As you can see, it has to initialize an extra object, and has to do a bit more work than a straight for loop (calling not just one, but two functions for every value in the loop). This effect is multiplied when you have a for each loop nested inside another, as it has to create a new Iterator for every value in the outer loop, even if the Iterator is empty.

I don’t have the exact timing of this, as it is a small, but noticeable difference. There are several losses, including having to allocate an entire object instead of a simple variable, and the overhead of calling methods.

For now, while for each loops are easier to read, they are definitely the worst in performance, especially compared to a pre-initialized for loop:

for(Integer index = 0, size = someList.size(); index < size; index++) {

As other answers noted, you may be also be seeing database latency. Here’s an updated version that doesn’t use the database:

@isTest class q227272 {
    private static String TEST_STR = '';

    static Account[] createAccounts() {
        Account[] records = new Account[0];
        for(Integer i = 0; i < 5000; i++) {
            records.add(new Account(Name=''+i));
        }
        return records;
    }
    public static testMethod void testForEachLoop(){
        List<Account> accs = createAccounts();
        Long start = DateTime.now().getTime();

        for(Account acc : accs){
            acc.Name += TEST_STR;
        }
        System.assert(false, DateTime.now().getTime()-start);
    }


    public static testMethod void testForLoop(){
        List<Account> accs = createAccounts();
        Long start = DateTime.now().getTime();

        for(Integer i = 0; i < accs.size(); ++i){
            accs[i].Name += TEST_STR;
        }
        System.assert(false, DateTime.now().getTime()-start);
    }


    public static testMethod void testImprovedForLoop(){
        List<Account> accs = createAccounts();
        Long start = DateTime.now().getTime();
        Integer size = accs.size();

        for(Integer i = 0; i < size; ++i){
            accs[i].Name += TEST_STR;
        }
        System.assert(false, DateTime.now().getTime()-start);
    }


    public static testMethod void testWhileLoop(){
        List<Account> accs = createAccounts();
        Long start = DateTime.now().getTime();
        Integer size = accs.size();
        Integer i = 0;

        while(i < size){
            accs[i].Name += TEST_STR;
            ++i;
        }
        System.assert(false, DateTime.now().getTime()-start);
    }
}

After ignoring the effects of the database, we get output like this:

testForEachLoop      93ms
testForLoop          104ms
testImprovedForLoop  73ms
testWhileLoop        80ms

Which still shows that the improved for loop is the best option, but the testForLoop takes the worst place; calling size() each iteration results in undesirable performance, while caching the size() call is the best option.

Attribution
Source : Link , Question Author : Nagendra Singh , Answer Author : sfdcfox

Leave a Comment