Like Reflection BUT... You Won't Believe How FAST This Is!
September 13, 2024
• 657 views
Unsafe Accessors are fast. Like... Stupid fast.
When we look at unsafe accessors, we generally compare them to reflection in DotNet. This is because they allow us to do some pretty crazy things like accessing private members on classes.
Heck, we can even mutate private fields!
But just how fast are unsafe accessors? Wait until you check out these numbers!
View Transcript
reflection often gets a bad reputation because it's slower than normal code now what if I told you there was something else that was much like reflection but was much much faster hi my name is Nick centino and I'm a principal software engineering manager at Microsoft in this video we're going to look at benchmarks for unsafe accessors now if you haven't seen my previous video I'll link it right up here so you can go check that out and come back but that'll teach you all about the very basics of unsafe accessors and how we can call them in this video that we are going to look at performance character istics while walk us through some benchmark.com for my courses on dome train with that said we'll jump over to visual studio look at the benchmark.us this is something that's going to allow us to access private
members now because I want to be able to compare these things to normal calls I want to be able to use public here so this is a slightly different setup but the point of this is that I can't call a method or a field if it's private from the outside without using reflection or unsafe accessors so just to make sure I have a direct comparison I've made these public so that I can call them from the outside and that way we can do a fair comparison so I'm just going to be dealing with field a method and a property I'm leaving the static part out of this just because it's going to double up all of the results that we need to look through and I don't think that you're going to see anything that's dramatically different or surprising just by doing static so very
simple type to work with I'm going to start walking through the different benchmarks that we have here so the first Benchmark is going to be for instantiation so I have the Constructor call here just marked as the Baseline this is just newing up our type and then the other ones that we're going to be looking at here are just using activator create instance you could also try to do comparisons with Constructor info you could do type invoke member I do have other videos on these benchmarks if you want to check out the rest of my channel but we're just going to limit it to activator create instance here and then we're going to call the unsafe Constructor which I have defined right here from line 43 to 44 that's going to be it for instantiation if we go a little bit further we'll do benchmarks
on getting field values so you'll notice that I'm just going to have a cached version of instance field info so that we're not paying the penalty of looking this up every time again in other videos I have I do show you the overhead of actually calling this code on line 58 but I do want to call out that for this video Let's assume that we've already cashed that and that way that's an optimization you can do to basically pay the penalty once and then not paid every time but we are going to look at just the sort of traditional way of getting a field value that's why I had to make these things public we're going to use reflection on that field info and then again using unsafe accessors so we are using the ref keyword here and you can see the ref keyword again
on line 81 we're just going to get that instance field and then return it back on all of these the set field benchmarks are essentially the exact same so I'll skim through them pretty quick you'll notice that I'm just setting the value to 1 2 3 4 5 6 across all of these and then of course for the reflection scenario I'm calling set value instead of get value next up are properties these ones again I will move through pretty quickly because it's going to be the same setup that we just saw except for doing property info and then we're going to obviously use the property name so the other important thing to note though is that properties when it comes to unsafe accessors they are marked as methods and you'll notice that the property name is technically just the method name that's because unsafe accessors
don't have a property accessor kind they need to be method if you're familiar with typing and properties and reflection in CP and.net then you'll know that the property syntax that we have is just like syntactic sugar everything behind the scenes is still a set and get method just with the property name so before moving on this is just a quick reminder that I do have courses available on dome train if you're interested in learning more about reflection I've put together an entire reflection course it's just over 5 hours long that walks you through all of the basics about reflection as well as some more complicated scenarios if we have a quick look at the curriculum you can see that we start diving into what types are in programming languages how we can find types how we can work with different member information on those types
and that includes some fancy things like being able to set private readon fields which is pretty wild I Do cover more advanced topics like generics performance analysis and some things that are Beyond reflection remember to check it out on dome train thanks and back to the video These benchmarks are set up in a very similar fashion and the set property ones again almost identical except we need to be able to get the property and then call set value when we're using reflection so just wanted to make sure that was obvious between those two the final set of benchmarks is just going to be calling the method that we have so uh kind of like the other setups we're caching the method info from reflection and that way a little bit down here we can see that we're just going to be calling that cached method
info the unsafe accessor call right here we need to pass in the instance and then of course just traditional code when we're calling a method this one hopefully doesn't look too complicated online 25 I've already run these benchmarks and have the results so I can save you a little bit of time and we can go check them out right now starting off if we have a quick look at the ratio column just a heads up that this ratio column colum is not going to be very well populated on all these results because some of these run so fast that it can't give you a very good accurate ratio but if we look at Constructor classic just the normal way that we construct things that is the Baseline that has a 1.0 ratio we can see that activator create instance is about twice as slow with
the 2.04 ratio but have a look at this last one unsafe using the unsafe accessor was even faster than in a normal Constructor which is pretty incredible this is probably not what I would recommend unless you had like a pretty extreme situation where you're just trying to get all the performance you can out of it but uh I thought that was pretty impressive that we see something that's technically even faster when it comes to memory allocated everything's kind of flat across the board so nothing interesting for memory but truly the performance you can see that even if you wanted to assume that it was on par with classic it's much faster than reflection heading over to the get field benchmarks this is where if we have a quick look at the ratio column things are going to start to look pretty ridiculous so kind of
want to draw your eyes back over to the mean column on the far left just to kind of show you that when we're dealing with fields and we're getting a field value we're talking about you know sub nanc so it's quite quite fast right so 0.003 almost 04 Nan seconds just to be able to access a field and get the value off of a instance right if we use reflection and using field info it's 42 nond so it's multiple orders of magnitude slower to use reflection just to be able to get a field now 42 NCS is obviously it's pretty fast but when you're talking about doing these things in a hot path maybe many many times this might be something that you just don't want to do but if we have a quick look at the final row here we can see that instance
field uh for unsafe using an unsafe accessor didn't even register time so uh pretty incred again if you assume this is even a rounding error it would be at least on par with the classic way to just access a field so again I thought that was pretty uh pretty amazing just a heads up this code does run because if you're like well hey is it just throwing an exception and that's why it's not registering benchmark.us video which I linked you'll see that when we're calling these unsafe accessors for Fe Fields This truly does work so pretty remarkable that we get basically almost no time registered when calling these things a quick note too that on the reflection scenario we do get some memory allocated so 24 bytes and obviously for the classic uh call we don't get any memory allocated and unsafe is essentially the
same so reflection in this case is overall just worse because it's allocating memory and it's significantly slower relative to the other ways jumping down to being able to set Fields again again the ratio column didn't even compute this time that's why we have some question marks you'll see that Nan so fast but not relatively speaking the unsafe call uh was certainly slower by again orders of magnitude than the classic way but I just wanted to call out that like compared to reflection this is pretty incredible right cuz it still orders of magnitude faster than the reflection scenario in this case yes reflection did allocate 24 bytes unsafe and the classic way of just setting a field are not allocating any memory moving things over of properties instead of fields so these are technically method calls right so when we get a property value the classic
way we do see that we're at 006 nond so still it's extremely fast to be able to get a property reflection is orders of magnitude slower this is kind of the pattern that we're seeing right 12 NCS still fast but slower than the classic way of just doing things but the unsafe accessor again ended up being faster in this example than just calling a property the classic way so again I'm pretty surprised looking at all these benchmarks right that we have things using unsafe accessors that are even faster sometimes when we're using those versus the classic way of doing things so uh let's move on cuz this is a similar pattern we've already seen a couple of bytes allocated but let's move on to the set and see if there's anything different there overall for the set property benchmarks uh nothing too interesting especially compared
to what we just saw right we see the classic way uh very very fast reflection couple of orders of magnitude slower still you know that 10 20 range for Nan seconds uh and then the unsafe property a little slower in this case than the classic way so overall extremely fast for unsafe so uh again very impressed with these finally looking at method calls just keep in mind that the properties that we saw properties are technically just method calls right so these ones ended up printing in a different order so I apologize but uh that's why it might seem a a little confusing but the classic one at the very top basically a classic method call registered no time at all that method itself there's nothing to do inside of the body but we still have to pay whatever overhead to call the method and we
can see that between the classic and UNS Safeway literally no time registered again if we put something inside of that method it might do something but that method is just returning a number so even though yes there's no code executing inside of that method it does have a return value it's not a no op so it is still worth calling and then the reflection line here that we have is 12 nond so again fast but compared to the other two ways just doesn't make any sense to be calling that unless you absolutely needed to do something with reflection overall we can see that unsafe accessors are dramatically faster than using reflection and in some cases even faster than the typical way that we might go write some code again I'm not going to go recommend that you use unsafe accessors if you don't need to
be doing and unsafe accessors are supposed to be giving us that power of being able to work with private members when otherwise we couldn't so it should be a pretty Niche use case but like we can see there are some interesting performance characteristics I will remind you again that reflection and unsafe accessors don't quite solve the same problem there's a bit of overlap right like reflection can access things uh that are private same with unsafe accessors but reflection can be used for things more dynamic at runtime so we can dynamically load up types we can call Things by their name with unsafe accessors we do need that information at compile time because we need to be able to write those signatures that give us unsafe accessors I hope you found this video interesting and I'd be very curious to hear from you how you might
be using unsafe accessors in your code thanks for watching and I'll see you next time
Frequently Asked Questions
What are unsafe accessors and how do they compare to reflection?
Unsafe accessors are a way to access private members in a much faster manner compared to reflection. While reflection allows for dynamic access to types and members at runtime, unsafe accessors require compile-time information and can be significantly faster in terms of performance.
Why should I consider using unsafe accessors if they are faster than traditional methods?
While unsafe accessors can be faster, I wouldn't recommend using them unless you have a very specific need for performance optimization. They are meant for niche use cases where you need to access private members, and in most scenarios, traditional methods are sufficient and safer.
What are the performance characteristics of unsafe accessors compared to reflection?
The performance characteristics of unsafe accessors are dramatically better than reflection. In my benchmarks, unsafe accessors often registered almost no time for operations, while reflection was orders of magnitude slower, making unsafe accessors a compelling choice for performance-critical code.
These FAQs were generated by AI from the video transcript.