37

I was about to reconfigure some of my code to use the newer Schema.describeSObjects(types) method that I had heard was faster than using a globalDescribe.

I made some code to quickly check this and I was dismayed to find that the newer technique is not only not faster, it's about twice as slow as using a global describe. Here's the code I used :

New DescribeSObjects() method

//each block was run separately for timing purposes
String obj = 'Account';
String[] types = new String[]{obj};
System.debug('START DESCRIBE SOBJECTS');

for (Integer i = 0; i < 50; i++) {
  List<Schema.DescribeSobjectResult> results = Schema.describeSObjects(types); 
}
System.debug('FINISH DESCRIBE SOBJECTS');

Old getGlobalDescribe() method

String obj1 = 'Account';
System.debug('START DESCRIBE GLOBAL');

for (Integer i = 0; i < 50; i++) {

  Map<String,Schema.SObjectType> gd = Schema.getGlobalDescribe(); 
  Schema.SObjectType sobjType = gd.get(obj1); 
  Schema.DescribeSObjectResult describeResult = sobjType.getDescribe(); 
}
System.debug('FINISH DESCRIBE GLOBAL');

Over 5 runs of each, the getGlobalDescribe() method took an average of 337ms for 50 describes and the describeSObjects() method took and average of 658ms. This is basically twice as long.

Does anyone have any insight as to why this would be? Any improvements that will significantly improve the accuracy of the resulting data?

As this data stands, I'm sticking with the old method.

One final note: the old technique uses 3 times a much heap (67kb vs 27kb)

1

4 Answers 4

60

I suspect, internally, that this code is written in Apex Code and thus suffers the same general performance problems as doing it yourself. If you want a blazing-fast interface, consider using Type instead:

String obj1 = 'Account';
Long time1 = DateTime.now().getTime();

for (Integer i = 0; i < 50; i++) {
    SObjectType r = ((SObject)Type.forName('Schema',obj1).newInstance()).getSObjectType();
    DescribeSObjectResult d = r.getDescribe();
}
Long time2 = DateTime.now().getTime();
System.debug(time2-time1);

This runs in about 10-15ms for 50 describes, while Schema.describeSObjects runs in about 1700-1900ms in my org, and Schema.getGlobalDescribe runs in 300-500ms in my org.

Skipping the global describe shaves off an amazing amount of time. Caching is also acceptable; if you must use Schema.getGlobalDescribe, use it only once to avoid performance issues:

String obj1 = 'Account';
Long time1 = DateTime.now().getTime();

Map<String,Schema.SObjectType> gd = Schema.getGlobalDescribe(); 

for (Integer i = 0; i < 50; i++) {
    Schema.SObjectType sobjType = gd.get(obj1); 
    Schema.DescribeSObjectResult describeResult = sobjType.getDescribe(); 
}
Long time2 = DateTime.now().getTime();
System.debug(time2-time1);

This results in a more respectable time of about 80-140ms instead.

I personally have a static variable that all classes use so I never global describe more than once per transaction:

public static Map<String, SObjectType> globalDescribe {
    get { if(globalDescribe == null) globalDescribe = Schema.getGlobalDescribe();
        return globalDescribe;
    }
}

If it's never used, there's no performance penalty, but when I do need it, I will only need to global describe once.

5
  • 1
    @IllusiveBrian I know it's cached, because it's much faster than it was before, but see my version versus the original version; it's about 200% faster than relying on the internal caching.
    – sfdcfox
    May 23, 2018 at 1:11
  • 2
    Damn, great answer. And using Type.forName('Schema.obj').newInstance() is great. I'll be using this from now on. May 23, 2018 at 1:11
  • I ran into some issues today (Jan 2020) with the performance of getGlobalDescribe() where in logs for a client org it'd apparently run instantly sometimes, and take up to two seconds at others in the same execution context. It wasn't the first call that was slow either. I ran some benchmark's of sfdcfox's code above in a scratch org, averaging 50 runs of my code, with each run running each type of describe 50 times against the User object. Results were an average of 15ms for the newInstance() technique vs. 362ms for getGlobalDescribe(). So yep, it's still blazing fast in comparison.
    – Matt Lacey
    Jan 21, 2021 at 4:40
  • I should note also that this was a scratch org which means a relatively simple schema in comparison to some of our clients where I expect this would run even slower.
    – Matt Lacey
    Jan 21, 2021 at 4:40
  • 1
    Wow ok. I tried to run the code in a much larger org and immediately hit a CPU timeout. I dropped it down to 1 run of 50 each, and the results are astonishing to say the least. Logging probably has some impact, but the numbers came out at 22ms and 37,683ms respectively.
    – Matt Lacey
    Jan 21, 2021 at 4:52
2

In my Winter 22 Scratch orgs I came to completely different results than what was discussed in the comments the past years, so I hope some of you can use the linked gist to verify the findings in your specific environments, thanks for your help in advance!

I feel like with the lazy loading and bug fixes around describes the past years they put some effort into describe results to improve the all-over performance.

I tried all the mentioned approaches and added some different versions of them. I call each of them 50.000 times, because this is probably the biggest number of records you might want to handle during one execution context. (adjust that number in your org if you run into CPU time limits, make sure the logging levels are set to NONE and ERROR only for Apex).

In my environment, I had metadata of two of managed packages with about 170 custom objects in total.

The main finding is, that in winter 22 the Type.forName() approach seems to be the slowest by a factor of 10 compared to the global Describe approach. The numbers vary just slightly for other custom and standard SObject Types.

These are the results I constantly get, ordered by the milliseconds they needed for the 50k iterations.

ms   - Approach (* = personal favorites)
---------------------------
374* - sObjecType.getDescribe()                                    // type available
473  - Global Describe (stored in anonymous Apex)
512  - Global Describe (stored in a class)        
568* - record.getSObjectType().getDescribe()                       // record available
618* - Cached DescribeResult (stored in class after first request) // name available
700  - Account.SObjectType.getDescribe()
704  - SObjectType.Account
900  - Global Describe (stored in a class, describe not cached)
920  - stringified Account.SObjectType with anonymous Global Describe
975  - Cached SObjectType retrieved by accessor method, then call .getDescribe()
1374 - fflib_SObjectDescribe.getDescribe(objectName).getDescribe()
1629 - Schema.describeSObjects(objectName)
4676 - Type.forName('Schema', objectName)

My takeaway:

In the past, I was using the fflib whenever I had a String or SObjectType available, to benefit from the caching there. But since it is 2-3 times faster, I will do the following for the future:

Having a String like 'Account':

I will use a small custom Global Describe class that provides the Types and nothing else to keep the performance high with almost no effort. For OOP reasons and readability I prefer a get(objectName) method that costs an extra 100ms per 50k over accessing the describes publicly. You could also make use of the getDescribe(SObjectDescribeOptions.FULL) while caching it, so definitely worth a thought:

for (Integer i = 0; i < 50000; i++) {
    DescribeSObjectResult describeResult = GlobalDescribe.get(objectName); // 618ms
}

Having a record or SObjectType:

I will always directly call the getSObjectType() and getDescribe() on it:

DescribeSObjectResult describeResult = sObjecType.getDescribe();             //374
DescribeSObjectResult describeResult = record.getSObjectType().getDescribe();//568

For a hands-on, feel free to use this gist: Apex SObject Describes performance testing and please share the results with me.

I will do another investigation on field Describes as well and try to find an appropriate post to add the results and link the answer here as well.

4
  • 1
    I'll check this out and see if I can replicate - thanks! Oct 25, 2021 at 17:44
  • Thanks! I feel like most of the milliseconds in the past comments and posts were due to the time the logging took. When all my logging levels are set to NONE, most of the requests take 0-1 millisecond for 50 iterations, and the Global Describe is the looser for sure, since it takes about 30-50ms to be initialized without even being used yet. So everything below 1000 requests is not even worth investigating because all the approaches take less than 100ms.
    – Basti
    Oct 26, 2021 at 13:50
  • I missed this. I need to re-visit performance myself since we found some other aspects (e.g. around qualified API names for entity definitions in custom metadata types) that were seriously slow in earlier releases. Take a look here.
    – Phil W
    Feb 1 at 22:42
  • Sure, please share your findings!
    – Basti
    Feb 2 at 16:32
1

We have a class configured to determine approval in a Apttus callback. After release we had one case where users reported took 8 hours to do a quote. More typical though it was 2 or 3 minutes. These things are not easy to track down, but we finally found it was the Schema.getGlobalDescribe(). Everytime it was called in a callback it added 10 seconds in delay. That in turn cause the method polling the callback to wait much more than 10 seconds. It would get called multiple times each time we did pricing.

After switching to just do a switch statement base on the class name, the delay was completely gone. Of course that means our metadata configuration we had designed to use and sobject type we specified could only use the few we hard coded in the switch statement. But considering we were only using two different values, it wasn't that much of a tradeoff.

1

I re-ran the this scenario and looks like Schema.describeSObjects seems to have better performance now when compared to SObjectType.getDescribe for individual objects, I'm assuming salesforce must have optimised this flow.

Below is the profiler trace for 'Account' and 'Product2' objects.

enter image description here

3
  • I'll check this out - thanks! Sep 11, 2019 at 6:58
  • Hmm, I reran my code and they are both slower on the current org I'm on: Old technique about 1.3 seconds vs new technique about .8 seconds... but I guess your results may vary. Sep 11, 2019 at 7:27
  • I guess schema.getGlobalDescribe is slower in my org. But using Type.forName seems to be amazingly performant for me. Sep 13, 2019 at 5:36

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.