Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between ldobj and ldind.<type>, and why is ldobj faster?

Tags:

c#

clr

bytecode

il

When using a 64-bit sized struct, the following code snippet

[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 8)]
  unsafe struct BUF
  {
  }

((BUF*)dst) = *((BUF*)src); 

Produces

    IL_0046: nop          
    IL_0047: ldloc.s dst                              
    IL_0049: ldloc.2                                       
    IL_004a: ldobj MyClass/BUF           
    IL_004f: stobj MyClass/BUF

However, when just using a long, the following code produces

*((long*)dst) = *((long*)src); 

Produces:

IL_0046: nop
IL_0047: ldloc.s dst
IL_0049: ldloc.2
IL_004a: ldind.i8                       
IL_004b: stind.i8         

Does anyone know what difference ldobj/stobj and ldind.i8/stind.i8 makes, if any, for this example?

ldobj/stobj seems to give a 20% performance improvement, but I cannot figure out why that is happening. Aren't these two lines doing the exact same thing?

Thanks!

edit: [64-bit release mode] The bytecode looks the same when compiled in release mode. The performance measurement was done a while ago in release mode.

like image 787
r3su Avatar asked Mar 01 '13 16:03

r3su


1 Answers

I've replicated the two distinct methods you're using, and see the same IL being generated, however the jitted code for the two is absolutely identical when run in Release mode:

Here's the test method I used:

using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Test
{
    [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 8)]
    unsafe struct BUF
    {
    }

    static class Program
    {
        static void Main()
        {
            BUF x, y, z;
            unsafe
            {
                Do1(&x, &y);
                Do2(&y, &z);
            }
            // Readline here to allow attaching debugger and dumping jitted code
            Console.ReadLine();
        }

        // Disable inlining to permit easier identification of the code
        [MethodImpl(MethodImplOptions.NoInlining)]
        unsafe static void Do1(BUF* src, BUF* dst)
        {
            *((BUF*)dst) = *((BUF*)src);
        }

        // Disable inlining to permit easier identification of the code
        [MethodImpl(MethodImplOptions.NoInlining)]
        unsafe static void Do2(BUF* src, BUF* dst)
        {
            *((long*)dst) = *((long*)src);
        }
    }
}

The IL for the two methods match yours:

Do1:

IL_0000: ldarg.1 
IL_0001: ldarg.0 
IL_0002: ldobj Test.BUF
IL_0007: stobj Test.BUF
IL_000c: ret 

Do2:

IL_0000: ldarg.1 
IL_0001: ldarg.0 
IL_0002: ldind.i8 
IL_0003: stind.i8 
IL_0004: ret 

And dumping the jitted code:

Do1:

Test.Program.Do1(Test.BUF*, Test.BUF*)
Begin 000007ff00170190, size 7
000007ff`00170190 488b01          mov     rax,qword ptr [rcx]
000007ff`00170193 488902          mov     qword ptr [rdx],rax
000007ff`00170196 c3              ret

Do2:

Test.Program.Do2(Test.BUF*, Test.BUF*)
Begin 000007ff001701b0, size 7
000007ff`001701b0 488b01          mov     rax,qword ptr [rcx]
000007ff`001701b3 488902          mov     qword ptr [rdx],rax
000007ff`001701b6 c3              ret

They look pretty identical to me.

like image 192
Iridium Avatar answered Oct 18 '22 08:10

Iridium