3

As we know, a Set is a collection of unique elements (cannot contain duplicates)

While writing some Apex just now, I was inclined to use !SetName.contains(Id) to skip the addition of records if they were already in the Set. As I was writing it, I realized that I was being redundant, since a Set can't contain more than one instance of the same Id anyway.

However, I was curious about the performance implications of one option versus the other in a bulk sense, thinking that one approach may save on CPU time over the other.

My assumption was based on thinking that Set.add() uses the Set.contains() method itself to check if the Record is already in the Set to avoid duplicates, so by using Set.contains() myself inside my loop, I would take up less CPU time by avoiding passing duplicates into Set.add() just so that Set.add() could run Set.contains()

However, using the following 2 Execute Anonymous Tests, the CPU time was nearly identical in all tests (within 1-2 milliseconds).

So the question is: is there a performance difference between checking for an item in a Set manually before adding it, versus letting the Set figure it out on its own? Is my test just too rudimentary or small to see an obvious difference in performance, or should I not be concerned with which style I choose?

Example 1:

Integer limitStart = System.Limits.getCpuTime();
List<Id> stringList = new List<String>();
Set<String> testStrings = new Set<String>();
for(Integer i = 0;i<200;i++){
    stringList.add('TestString');
}
for(String s : stringList){
    if(!testStrings.contains(s))
        testStrings.add(s);
}
Integer limitEnd = System.Limits.getCpuTime();
System.debug('Strings: ' + testStrings);
System.debug('Start: ' + limitStart);
System.debug('End: ' + limitEnd);

Example 2:

Integer limitStart = System.Limits.getCpuTime();
List<Id> stringList = new List<String>();
Set<String> testStrings = new Set<String>();
for(Integer i = 0;i<200;i++){
    stringList.add('TestString');
}
for(String s : stringList){
    testStrings.add(s);
}
Integer limitEnd = System.Limits.getCpuTime();
System.debug('Strings: ' + testStrings);
System.debug('Start: ' + limitStart);
System.debug('End: ' + limitEnd);

2 Answers 2

2

The add method performs the contains check by definition so it's superfluous to call contains() before add. Not only that but add tells you whether the item being added to the set was not present in the set (and therefore was added) or was present and not added. From the docs:

add(setElement)

Return Value

Type: Boolean

Usage: This method returns true if the original set changed as a result of the call. For example:

Set<String> myString = new Set<String>{'a', 'b', 'c'}; 
Boolean result = myString.add('d'); 
System.assertEquals(true, result); 

or adding a duplicate

Set<String> myString = new Set<String>{'a', 'b', 'c'};
Boolean result = myString.add('a');
System.assertEquals(false, result);
2

Your test is far too small to detect any major differences. Try using much larger values, such as 100,000, and you'll notice a huge difference in performance. This is because the hashCode/equals algorithm has to be executed every time you use add or contains. This means calling contains and add takes twice as much time as just calling add by itself (100% less efficient).

If you really want to know if there was already a value in the set, you can actually do:

Boolean modified = someSet.add(newValue);
if(modified) { // this value was not already in the set

In your case, it's not necessary, as you were just worried about "avoiding duplicates", but it's actually more efficient to just add the value than check for the existing value first.

There's some rare algorithms where you wouldn't want to add before checking contains, but these are exceptions to the rule. Do not check contains before using add. Similarly, you do not need to check for a key before putting a value in a Map, as you'll gain the same efficiency.

You must log in to answer this question.

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