Earlier we started a new series about performance optimization, here Performance optimization – the hardcore series – part 1 – Quan Mai’s blog (vimvq1987.com) . There are ton of places where things can go wrong. A seasoned developer can, from experience, avoid some obvious performance errors. But as we will soon learn, a small thing can make a huge impact if it is called repeatedly, and a big thing might be OK to use as long as it is called once.
Let’s take this example – how would you think about this snippet – CategoryIds
is a list of string
converted from ContentReference
if (CategoryIds.Any(x => new ContentReference(x).ToReferenceWithoutVersion() == contentLink))
{
//do stuff
}
If this is in any “cool” path that run a few hundred times a day, you will be fine. It’s not “elegant”, but it works, and maybe you can get away with it. However, if it is in a hot path that is executed every time a visitor visits a product page in your website, it can create a huge problem.
And can you guess what it is?
new ContentReference(string
) is fairly lightweight, but if it is called a lot, this is what happen. This is allocations from the constructor alone, and only within 220 seconds of the trace
A lot of allocations which should have been avoided if CategoryIds
was just an IEnumerable<ContentReference>
instead of IEnumerable<string>
For comparison, this is how 10.000 and 1000.000 new ContentReference
would allocate
Thing is similar if you use .ToReferenceWithoutVersion()
to compare to another ContentReference
(although to a lesser extend as ToReferenceWithoutVersion
would return the same ContentReference
if the WorkId
is 0, and it use cloning instead of new
). The correct way to compare two instances of ContentReference
without caring about versions, is to use .Compare
with ContentReferenceComparer.IgnoreVersion
Moral of the story
- It is not only what you do, but also how you do it
- Small things can make big impacts, don’t guess, measure!