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.