18

Anyone have any data to support a claim for which of the following two expressions is more efficient (meaning faster)? The use case is determining whether a List is empty to inform an auxiliary method such as hasErrors().

Option 1: Use List.isEmpty()

public static Boolean hasErrors() {

    // Assume getErrors() returns a List object
    return !getErrors().isEmpty();
}

While semantically this approach makes a lot of sense, I wonder whether the ! operator has any material impact on the method's performance.

Option 2: Use List.size()

public static Boolean hasErrors() {

    // Assume getErrors() returns a List object
    return getErrors().size() > 0;
}

The questions raised by this alternative approach are: Is List.size() inherently faster than List.isEmpty()? And how does the performance of the greater than (>) operator compare to that of the negation (!) operator?

13

UPDATE, I now cover all four cases and tested in a different org so the raw numbers are different.

I agree that isEmpty is superior from a readability perspective. However, out of curiosity I ran each scenario 50,000 times. The size method took .02334 seconds on average , whereas the isEmpty method took .02139 seconds on average. That is a difference of around 9%. The difference appears negligible.

Here is my methodology, perhaps less elegant than Mr. Ballinger's.

List<String> strings = new List<String>();
for (Integer i = 0; i < 10000; i++) { strings.add(String.valueOf(i)); }
Datetime start = Datetime.now();
for (Integer i = 0; i < 50000; i++)
{
    // use one of the following:
    // Boolean isNotEmpty = !strings.isEmpty();
    // yields 1092
    // Boolean isNotEmpty = strings.size() > 0;
    // yields 1085
    // Boolean isEmpty = strings.isEmpty();
    // yields 1047
    // Boolean isEmpty = strings.size() == 0
    // yields 1249
}
Datetime stop = Datetime.now();
system.debug(stop.getTime() - start.getTime());

// (1092 + 1047) / 100,000 = .02139
// (1085 + 1249) / 100,000 = .02334
  • Boolean isEmpty = strings.size() > 1; ? – Pål GD Mar 10 '15 at 3:31
  • Good catch. 0, of course. – Adrian Larson Mar 10 '15 at 7:07
  • Hey, @AdrianLarson I think your approach is neat! However, to compare apples to apples, can you re-run your test and update your answer after you add the negation (!) operator in front of strings.isEmpty() inside the for loop? – Marty C. Mar 10 '15 at 12:12
  • I can see the appeal of averaging out multiple calls to try and get rid of the noise. There is also something to be said for avoiding the logging as that will also affect the performance. Did you turn all the logging levels off for the run? I'm wondering if there is significant difference between the first call to isEmpty() and size() and the subsequent 49,999 calls. The impact of this would depend on how the conditions were being used in the actual code. – Daniel Ballinger Mar 11 '15 at 1:46
  • I will respond to that with more profiling. You got me thinking some more! – Adrian Larson Mar 11 '15 at 1:59
15

Personally I'd go with the code that was the easiest to read (!getErrors().isEmpty() IMHO) and then worry about micro-optimising it if there was a proven performance problem.

isEmpty() makes your intentions clearer.


That said, why not measure the difference using the Apex Log set to the finest level of logging? (See Caveats below for several reasons why this might be flawed)

E.g.

List<Account> listOfAccounts = new List<Account>();
boolean isEmptyResult = !listOfAccounts.isEmpty();
boolean sizeGreaterResult = listOfAccounts.size() > 0;

Gives:

12:26:40.359 (359852996)|STATEMENT_EXECUTE|2
12:26:40.359 (359890588)|SYSTEM_METHOD_ENTRY|2|List.isEmpty()
12:26:40.359 (359905522)|ENTERING_MANAGED_PKG|
12:26:40.359 (359942358)|SYSTEM_METHOD_EXIT|2|List.isEmpty()
12:26:40.360 (360075896)|HEAP_ALLOCATE|2|Bytes:5
12:26:40.360 (360107441)|VARIABLE_ASSIGNMENT|2|isEmptyResult|false
12:26:40.360 (360121408)|STATEMENT_EXECUTE|3
12:26:40.360 (360139577)|SYSTEM_METHOD_ENTRY|3|List.size()
12:26:40.360 (360151946)|ENTERING_MANAGED_PKG|
12:26:40.360 (360180804)|SYSTEM_METHOD_EXIT|3|List.size()
12:26:40.360 (360201183)|HEAP_ALLOCATE|3|Bytes:5
12:26:40.360 (360226173)|VARIABLE_ASSIGNMENT|3|sizeGreaterResult|false

Which gives:

  • List.isEmpty() taking from 359890588 to 359942358. So 51770 elapsed time milliseconds* nanoseconds.
  • List.size() taking from 360139577 to 360180804. So 41227 elapsed time milliseconds* nanoseconds.

* Somehow I doubt those elapsed times are actually in milliseconds as the documentation reports. It doesn't line up with the first part of the timestamp in HH:mm:ss.SSS format. E.g. 51770 milliseconds = 51.77 seconds. I suspect the unit for the elapsed time is actually nanoseconds. E.g. 359890588 = 0.359890588 seconds elapsed.

Caveats:

  • The act of collecting the additional logging maywill be affecting the performance. There is cost in capturing the SYSTEM_METHOD_ENTRY/EXIT events which will add noise to the results. See Observer effect.
  • ENTERING_MANAGED_PKG is being logged in-between the System Method calls. I'm not sure why. It's not like the managed package can do anything meaningful in the middle of the system method call.
  • This is only a test with an empty list. You should also check the performance with larger lists.
  • The generic type of the list may be affecting the performance. Timing for a list of Integers may be different that a list of sObjects.
  • Ideally the test should be performed multiple times and the results averaged out. Should this occur within a local loop or multiple transactions? Maybe. I'd say it depends on how you will use it in practice. If you really were using it heavily in a for loop, why not perform the test once and assign it to a boolean member and be done with it.
  • Does anonymous Apex behave the same as a fixed Apex class (maybe a fixed cost before the code executes).
  • I suspect, but haven't proven, that any performance difference between the two would be lost in the variation you will get from transaction to transaction. E.g. one may be X microseconds faster on average than the other over millions of test runs, but the variation/noise on any one test is 1.5 * X. So within the variation you are unlikely to see any meaningful difference unless you are calling it lots of times.

See also:

  • Personally, I used to use !list.isempty() but after having difficulty debugging my code by missing that pesky little exclamation mark quite a few times, I've reverted to list.isEmpty() == false, although I'm still torn on whether to use list.isEmpty() or try to be consistent and use the redundant list.isEmpty() == true – Nick Cook Mar 10 '15 at 9:41
  • You say "ideally the test should be performed multiple times and the results averaged out", but it's more than "ideal": you must do this. The results in this answer are completely meaningless. Just noise. You're not actually measuring what you're trying to measure! – Lightness Races with Monica Mar 10 '15 at 16:30
  • @LightnessRacesinOrbit Definitely, the variation could be significant between runs. Mostly I was trying to demonstrate measuring execution time from the debug log. All sorts of things could cause a significant variation in the timing (type of record in the list, number of members, server load, the logging level). Question is, how do you test the call multiple times? – Daniel Ballinger Mar 11 '15 at 0:52
  • @LightnessRacesinOrbit I.e. Is there an overhead with the first call? Would a for loop with a static list be slower with the first call and then subsequently faster for the remaining 49,999 calls? Does that really matter if when actually used in code the performance difference between the two is insignificant compared to the variation you get from call to call. This seems like a slippery slope to be going down. – Daniel Ballinger Mar 11 '15 at 0:55
  • @Daniel: I don't know but considering checking lists for emptiness takes nanoseconds, not microseconds, it's clear that your benchmark is just fundamentally flawed. You're not measuring the actual operation, at all! It'll be a combination of I/O and whatever routine retrieves and formats that timestamp, plus context switches and other funky stuff. I don't know in Salesforce how you can test the call multiple times ... all I know is that to get a benchmark you will have to! – Lightness Races with Monica Mar 11 '15 at 0:57
5

The efficiency of one method vs. the other would depend on the implementation of the List<> class. As Salesforce provide a dedicated isEmpty() method I would probably suggest using that, because it's possible that size() has to iterate the whole list to return whereas isEmpty() only has to evaluate the first item in the list.

The example test code in some of these answers is also flawed. By calling both isEmpty() and size() on the same list variable you will probably get skewed results due to internal caching of the object.

As Apex uses Java underneath it may be worth reading into some of the discussions on Stackoverflow about this topic: https://stackoverflow.com/questions/1508975/why-is-list-size0-slower-than-list-isempty-in-java

  • If you read my answer you will notice that I do not call both methods on the same list in the same execution. No risk of caching. – Adrian Larson Mar 10 '15 at 15:59
0

Just for information, who cares about precessing time, I found that

Boolean isEmpty = mylist[0] != null;

works much better then !mylist.isEmpty() or mylist.size() > 0 . Execution of compair null value at 0 position takes only around 15% of time. When I run code from 'Adrian Larson' I get only average 50 miliseconds, around 340milisecond for size() and 350 miliseconds for isEmpty. I used diferent lists, so data were not cached. Also size() is little faster for sets and maps too.

  • 2
    Your code should almost certainly generate an exception when the list is, in fact, empty. System.ListException: List index out of bounds: 0 – Adrian Larson Apr 21 '15 at 23:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.