Blog

Field access versus local variable access performance

Recently, I was at a customer site and they were comparing the performance of a function that was written in unmanaged C with its C# equivalent. As they expected, the C code was performing much faster than the C# code. However, I didn’t expect this. Once managed code is JIT compiled, the code is very similar to that of unmanaged code. The code was being called in a loop in both versios numerous times so the additional time spent by the JIT compiler should be noise and I expect the performance of the C# code to be near identical and even better than the unmanaged C version because the JIT compiler could emit optimizations for the speciifc CPU architecture of the host machine.
I was very interested in this problem and I closely examined both functions. Not just the source code, but I also very closely examined the machine code generated by the unmanaged C compiler as well as the machine code generated by the .NET Framework’s JIT compiler. Not surprising (to me), the machine code was pratically identical. I did see a place where the C compiler emitted an INCrement instruction where th JIT compiler emitted an ADD instruction to add 1. I assume that this is because the JIT compiler knew the host CPU and determined that an ADD instruction would be faster than an INCrement instruction. This would actually make the managed code version faster.
I also noticed a place where the unmanaged version was updating a register directly where the managed version was updating a indirect memory location. This was because the unmanaged source code was updating a local variable while the C# version was turned into an object-oriented equivalent and as updating a field in the class object. This difference alse seemed minor to me and could not possibly accounting for the performance discrepency we were seeing. However, we continued to examine the code and in an effort to get the emitted machine code to be identical between the 2 as possible, we got rid of the field in the object and turned it into a local variable instead. WOW! What a difference this made in performance!
Apparently, the JIT compiler is able to enregister local variables but it cannot enregister fields. This makes sense since a field can actually be manipulated by multiple threads simultaneously or via other means behind a method’s back and therefore, the JIT compiler takes the safe route of not enregisterring the local and always saves and fetches its value from memory. On the other hand, a local variable cannot be modified behind a method’s back and therefor the JIT compiler can put it in a register.
I always knew that memory access is slower than CPU register access but I guess I nenver really knew how big a difference it could be. On the machine we were using to test this, the differenece was about 8 times. That is, the method with the field ran 8 times slower than the equivalent method with the local variable! On my notebook computer, the difference was much less: the local variable version ran in ~25 seconds while the field version ran in ~31 seconds. Still a pretty big difference.
So, the moral of the story, is that you should avoid field access as much as possible in performance-sensitive code. It might even make sense to copy a field into a loca, use the local in the method and then copy the result back from the local into the field just before the method exits. Below is some simple code (that requires Whidbey because I’m using the Stopwatch class) that you can compile and test on your own machine to see the perf difference:
using System;
using System.Diagnostics;
class App {
static void Main() {
const Int64 iter = 5000000000;
  Stopwatch sw = Stopwatch.StartNew();
TestLocalAccess(iter);
Console.WriteLine(“time taken:{0} ticks”, sw.Elapsed);
  sw = Stopwatch.StartNew();
TestFieldAccess(iter);
Console.WriteLine(“time taken:{0} ticks”, sw.Elapsed);
}
 private static Int64 j;
 public static void TestLocalAccess(Int64 numIncrement) {
Int64 j=0;
for(Int64 i=0; i<numIncrement; i++) j++;
}
 public static void TestFieldAccess(Int64 numIncrement) {
  for(Int64 i=0; i<numIncrement; i++) j++;
}
}
Jeffrey Richter

View Comments

  • The lack of a synchronization primitive should allow the JIT to replace the function
    public static void TestFieldAccess(Int64 numIncrement) {
    for(Int64 i=0; i<numIncrement; i++) j++;
    }
    with:
    public static void TestFieldAccess(Int64 numIncrement) {
    j += numIncrement;
    }
    Given that Windows isn't a real time system, and the scheduling of threads is unpredictable; both of these functions are identical.
    The fact that the JIT doesn't optimize this case is an QOI (Quality of Implementation) issue. Future JIT's should be able to optimize this. However, since this is a toy example, I'm not surprised that the JIT doesn't handle it better.

  • I ran the sample code on my machine and I'm seeing exact opposite to what you said. Its taking more time for local variable than for field.
    for local variable - 27.41 secs
    for field - 25.08 secs
    why do you think it is happening like this?

  • The issue that MG mentions is likely due to us running on a 32bit processor, whereas, I'd almost guarantee Jeff was running his tests on a 64bit box. This makes since, as the value Jeff is using is a 64bit integer.
    If you change it to be a regular Int32 on 32bit processors you'll notice that Jeff's writing is correct. The following ASM will be generated on a 32bit machine using 32bit integer.
    Local Access:
    inc ebx
    Field Access:
    inc dword ptr ds:[000C8764h]
    Whereas if you're using an Int64 on a 32bit process you get the following:
    Local Access:
    mov eax,ebx
    mov edx,edi
    add eax,1
    adc edx,0
    mov ebx,eax
    mov edi,edx
    Field Access:
    dword ptr ds:[001C8764h]

  • I tried out Jeff's sample and results were pretty surprising. Local variable acces ran almost twice as fast as field acces. I modified the code so that I can access value of j in Main() method and again to my surprise results were reversed. Local ran slower than field access. Sir can you explain why this is happening...
    using System;
    using System.Diagnostics;
    class App
    {
    private static Int64 j;
    static void Main()
    {
    const Int64 iterations = 5000000000;
    Stopwatch sw = Stopwatch.StartNew();
    TestLocalAccess(iterations);
    Console.WriteLine("time taken in local variable access:{0} ticks and value of j is:{1}", sw.Elapsed,j);
    j = 0;
    sw = Stopwatch.StartNew();
    TestFieldAccess(iterations);
    Console.WriteLine("time taken in feild variable access:{0} ticks and value of j is:{1}", sw.Elapsed,j);
    Console.ReadLine();
    }
    public static void TestLocalAccess(Int64 numIncrement)
    {
    Int64 j = 0;
    for (Int64 i = 0; i

  • Its giving exactly opposite result. I also changed the Int64 to Int32 in all place as I have 32 bit processor.

  • A pair ofugg boots from theuggs is necessary. I recommend you to putugg australia, thisugg classic brand is not only in high quality and but also in low price. I like to buyugg boots uk to see thugg classic tall, and the newesttall ugg boots ,short ugg boots, loving beauty is the nature of femals, there are so many kinds, likeuggs salebuy ugg ,buy ugg boots,discount ugg,buy uggs,ugg boots online,discount uggs can be used to decorate, andbuy cheap ugg boots and theknitted ugg boots is perfect. I prefer theugg boats australia, I was lucky to buy a cheap ugg boats last week. You can choosebuy ugg site,buy discount ugg boots,Ladies UGG Boots to comply with your style. People who enjoy travelling don't need to worry,UGG boots sale combining with
    ugg boats help you get more attention. I am a girl this is not easy to be satisfied, in addation to the above, I also likeUGG Classic Tall 5815 Boots,ugg 5879,ugg 5803,ugg 5819,ugg 5833,ugg 5854,ugg 5825,ugg 5815,ugg 5359,ugg 5325,ugg 5225,2010 Hot Selling Boots is really nice to make you more gentle.

Recent Posts

8-Step AWS to Microsoft Azure Migration Strategy

Microsoft Azure and Amazon Web Services (AWS) are two of the most popular cloud platforms.…

2 days ago

How to Navigate Azure Governance

 Cloud management is difficult to do manually, especially if you work with multiple cloud…

1 week ago

Why Azure’s Scalability is Your Key to Business Growth & Efficiency

Azure’s scalable infrastructure is often cited as one of the primary reasons why it's the…

3 weeks ago

Unlocking the Power of AI in your Software Development Life Cycle (SDLC)

https://www.youtube.com/watch?v=wDzCN0d8SeA Watch our "Unlocking the Power of AI in your Software Development Life Cycle (SDLC)"…

1 month ago

The Role of FinOps in Accelerating Business Innovation

FinOps is a strategic approach to managing cloud costs. It combines financial management best practices…

1 month ago

Azure Kubernetes Security Best Practices

Using Kubernetes with Azure combines the power of Kubernetes container orchestration and the cloud capabilities…

2 months ago