C# Assembly Duel – Properties vs. Getters and Setter Methods

Disclaimer: These are done in the name of fun and research, I’m not out to prove that one way of doing something is better than another, and I also want to point out that by using the disassembly window, we are looking at assembly generated for debug, for an x64 intel chipset, so your mileage will vary both in release code and for your specific chipset.

In the last post where I talked about the disassembly window in Visual Studio, a couple people reached out and told me they liked the idea of comparing methodologies at the assembly level to see the differences between the two, so I decided to start a series I’m dubbing assembly duels. In this first installment, we’ll be looking at the overhead of using a property in C# vs. the overhead of using a standard getter or setter method, so let’s jump straight into it.

The test class

For this comparison, we’ll need a class, let’s create a 2D vector class:

public class Vec2D
{
private int x;
private int y;
}

So we’ve got two simple member variables, an x and a y which represent x and y coordinates. Now, let’s write the properties to get and set these, and the methods to get and set them:

Pretty standard stuff here, we’ve got a property for x and y, which have a very basic get and set, and we have two methods, GetX and GetY, which return the member variables x and y. For the usage, I’m going to simply assign a few variables the result of each getter/setter:

Pretty simple again, I create an instance of the class, initialize the x and y values with the setter portion of the properties, and then assign 4 variables with the various methods of getting the members. Now let’s break open the disassembly window and see what we get:

Very interesting results here, you’ll notice that the assembly generated for each type of getter is literally identical, minus specific memory addresses, so are getter methods and properties the same thing under the hood? Well, at a surface level they appear to be, but notice the call command. The call command jumps to another portion of assembly, so to determine if they are identical, we also need to analyze what’s actually at each call. First, let’s break down the assembly above.

First we take the contents of the ecx register, and put them in the pointers memory. Next we see a command “cmp”, cmp means conditional jump, you can think of jump as a goto statement (which in assembly are not only not bad practice, they are literally required) the difference between cmp and a normal goto is cmp evaluates the data on the left and right side, and only jumps when it evaluates to true. Next, we get to a call method, which we’ll get to, and then three mov commands happen.

Getting into the meat of each method

Now let’s really drill down, the call method for the x member properties getter is as follows:

It’s not important to understand all this stuff, just notice two things, one: another call happens, and two, ret at the bottom returns us back to where we came, this occurs by simply popping the stack. As it turns out, we can’t actually view what occurs when the inner call happens, what we do know is it goes to the memory address 73B236D0, because the memory address is so massively far from our own jitted codes memory address range, it hints to us that it’s unmanaged code, but there’s not a really easy way to tell for sure, so let’s accept this and move on. Command count for the properties getter: 31 (I think).

Another disclaimer: Just because something may use more assembly instructions, doesn’t make it less efficient, to really, truly see how efficient something is in terms of the assembly executing is going to be tough in Visual Studio, as it only tells us that something took <= 1ms at best. At the assembly level you're dealing with clock cycles, which are incredibly fine granularity, much finer certainly that milliseconds. To determine runtime speed, you would really need to know ahead of time about how long a particular processor takes to execute a particular instruction, which will depend greatly from processor to processor, and it will even vary if you compare the same processor against the same processor, because some may have higher clocked speeds than others. (ex: two intel i7 7700k processors can be clocked at many different speeds, so all of them will run differently)

Here there’s certainly a lot less going on, notice though, that in this method as well we have a call command that calls to 73B236D0, which is the same memory address as the one the property called to. This tells us one thing for certain, whatever happens at that memory address doesn’t really matter, because both methods of getting a member are going to incur the same overhead of whatever goes on at that memory address. The nop (no-op) command is likely something the debugger put in, and even if it isn’t, no-ops incur virtually zero overhead on pretty much any processor or chipset, so we can basically call that free, so ignoring that command, we have 23 instructions executing for this method of getting a member.

What we learned

There are a few takeaways here. First off, properties for sure use more assembly instructions to execute, does that make them more inefficient? I don’t know, I haven’t gone in and studied how many clock cycles each instruction takes to execute, however my guess is it runs at roughly the same speed, give or take. The second takeaway is that we can say with certainty, that properties and getter and setter methods are absolutely not implemented the same way under the hood, they use very different sets of commands for doing their respective jobs, but we also know that at some point, both call into the exact same unmanaged code to do something, what that something is, we aren’t sure. Again, the idea behind these deep dives is exposure, it’s not to say whether something is good or bad, it’s just to see what happens under the hood. I hope this was at least mildly interesting, and if you enjoyed it make sure to like or leave a comment on the post and consider following the blog, I’ll definitely be doing more of these.