Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • World
  • Users
  • Groups
Skins
  • Light
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dark
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Default (No Skin)
  • No Skin
Collapse
Code Project
  1. Home
  2. General Programming
  3. Managed C++/CLI
  4. Creating objects on stack

Creating objects on stack

Scheduled Pinned Locked Moved Managed C++/CLI
csharpc++dotnetdata-structures
8 Posts 3 Posters 0 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • V Offline
    V Offline
    VizOne
    wrote on last edited by
    #1

    Hi! I am working on a function that returns an object initilized with some specific values, like __value struct MyStruct { public: MyStruct(int a, int b, int c) : a(in_a), b(in_b), c(in_c) {} static MyStruct One() { return MyStruct(1,1,1); } private: int a, b, c; }; This is compiled to the following MSIL-code: .method public static valuetype Cpp.MyStruct One() cil managed { // Code size 26 (0x1a) .maxstack 4 .locals (valuetype Cpp.MyStruct V_0) IL_0000: ldloca.s V_0 IL_0002: initobj Cpp.MyStruct IL_0008: ldloca.s V_0 IL_000a: ldc.i4.1 IL_000b: ldc.i4.1 IL_000c: ldc.i4.1 IL_000d: call instance void Cpp.MyStruct::.ctor(int32, int32, int32) IL_0012: ldloca.s V_0 IL_0014: ldobj Cpp.MyStruct IL_0019: ret } // end of method MyStruct::One I looked at IL_0002 and found a initobj. I tried to avoid initializing of the members, so I defined an empty default c'tor: MyStruct() {} However, now my IL code expanded to: .method public static valuetype Cpp.MyStruct One() cil managed { // Code size 35 (0x23) .maxstack 4 .locals (valuetype Cpp.MyStruct V_0, valuetype Cpp.MyStruct V_1) IL_0000: ldloca.s V_1 IL_0002: initobj Cpp.MyStruct IL_0008: ldloca.s V_1 IL_000a: ldc.i4.1 IL_000b: ldc.i4.1 IL_000c: ldc.i4.1 IL_000d: call instance void Cpp.MyStruct::.ctor(int32, int32, int32) IL_0012: ldloca.s V_0 IL_0014: ldloca.s V_1 IL_0016: cpobj Cpp.MyStruct IL_001b: ldloca.s V_0 IL_001d: ldobj Cpp.MyStruct IL_0022: ret } // end of method MyStruct::One Which contains a cpobj and two local instances of MyStruct and therefore seems to be even worse. I wrote a similar struct in C# to compare compilation output: public struct MyStruct { public MyStruct(int in_a, int in_b, int in_c) { a = in_a; b = in_b; c = in_c; } public static MyStruct One() { return new MyStruct(1, 1, 1); } int a, b, c; }; This compiled to the following code: .method public hidebysig static valuetype CSharp.MyStruct One() cil managed { // Code size 9 (0x9) .maxstack 8 IL

    J P 2 Replies Last reply
    0
    • V VizOne

      Hi! I am working on a function that returns an object initilized with some specific values, like __value struct MyStruct { public: MyStruct(int a, int b, int c) : a(in_a), b(in_b), c(in_c) {} static MyStruct One() { return MyStruct(1,1,1); } private: int a, b, c; }; This is compiled to the following MSIL-code: .method public static valuetype Cpp.MyStruct One() cil managed { // Code size 26 (0x1a) .maxstack 4 .locals (valuetype Cpp.MyStruct V_0) IL_0000: ldloca.s V_0 IL_0002: initobj Cpp.MyStruct IL_0008: ldloca.s V_0 IL_000a: ldc.i4.1 IL_000b: ldc.i4.1 IL_000c: ldc.i4.1 IL_000d: call instance void Cpp.MyStruct::.ctor(int32, int32, int32) IL_0012: ldloca.s V_0 IL_0014: ldobj Cpp.MyStruct IL_0019: ret } // end of method MyStruct::One I looked at IL_0002 and found a initobj. I tried to avoid initializing of the members, so I defined an empty default c'tor: MyStruct() {} However, now my IL code expanded to: .method public static valuetype Cpp.MyStruct One() cil managed { // Code size 35 (0x23) .maxstack 4 .locals (valuetype Cpp.MyStruct V_0, valuetype Cpp.MyStruct V_1) IL_0000: ldloca.s V_1 IL_0002: initobj Cpp.MyStruct IL_0008: ldloca.s V_1 IL_000a: ldc.i4.1 IL_000b: ldc.i4.1 IL_000c: ldc.i4.1 IL_000d: call instance void Cpp.MyStruct::.ctor(int32, int32, int32) IL_0012: ldloca.s V_0 IL_0014: ldloca.s V_1 IL_0016: cpobj Cpp.MyStruct IL_001b: ldloca.s V_0 IL_001d: ldobj Cpp.MyStruct IL_0022: ret } // end of method MyStruct::One Which contains a cpobj and two local instances of MyStruct and therefore seems to be even worse. I wrote a similar struct in C# to compare compilation output: public struct MyStruct { public MyStruct(int in_a, int in_b, int in_c) { a = in_a; b = in_b; c = in_c; } public static MyStruct One() { return new MyStruct(1, 1, 1); } int a, b, c; }; This compiled to the following code: .method public hidebysig static valuetype CSharp.MyStruct One() cil managed { // Code size 9 (0x9) .maxstack 8 IL

      J Offline
      J Offline
      Jeff J
      wrote on last edited by
      #2

      Hello Andre, VizOne wrote: ...is the C# version creating the object on the heap or the stack? As you suspected, the C# version is allocating on the heap, which is what the 'newobj' IL instruction normally does. So, the two pieces of code are not equivalent, and is partly why the instructions differ so. The MC++ bit is dealing with a stack object, and even more importantly, is returning a copy of the object, not just a pointer (object reference in .Net lingo) to it. The 'ldobj' instruction verifies this, and I think a large part of the performance difference. Not only is the __value object initialised (initobj Cpp.MyStruct), but it's address is repeatedly pushed onto the stack (ldloca.s V_0) for various ops. So the address of the static struct is loaded, it's initialised, the address is again loaded, constant params loaded, the ctor called, the object's address loaded yet again, then an entire copy made before returning. I can see why performance was bad. However, I'm thinking the number of ops is not the most reliable way to gauge performance here. In particular, newobj does a lot for a single instruction, including runtime allocation on the GC heap, initialising, and then calling the ctor. Admittedly GC heap allocation is designed to be very fast, and nearly as fast as stack allocation, but the C# code does not make a copy. I suspect that's the biggest factor. I'm curious if the code was optimised during compile, as that might make a significant difference. You have me digging deeper into this one, so I'll post back if I come up with anything more. I'll try making the MC++ and C# functions both return copies and pointers. Shame on you for stimulating my attrophied neurons! ;) Interesting stuff. Cheers

      P J 2 Replies Last reply
      0
      • J Jeff J

        Hello Andre, VizOne wrote: ...is the C# version creating the object on the heap or the stack? As you suspected, the C# version is allocating on the heap, which is what the 'newobj' IL instruction normally does. So, the two pieces of code are not equivalent, and is partly why the instructions differ so. The MC++ bit is dealing with a stack object, and even more importantly, is returning a copy of the object, not just a pointer (object reference in .Net lingo) to it. The 'ldobj' instruction verifies this, and I think a large part of the performance difference. Not only is the __value object initialised (initobj Cpp.MyStruct), but it's address is repeatedly pushed onto the stack (ldloca.s V_0) for various ops. So the address of the static struct is loaded, it's initialised, the address is again loaded, constant params loaded, the ctor called, the object's address loaded yet again, then an entire copy made before returning. I can see why performance was bad. However, I'm thinking the number of ops is not the most reliable way to gauge performance here. In particular, newobj does a lot for a single instruction, including runtime allocation on the GC heap, initialising, and then calling the ctor. Admittedly GC heap allocation is designed to be very fast, and nearly as fast as stack allocation, but the C# code does not make a copy. I suspect that's the biggest factor. I'm curious if the code was optimised during compile, as that might make a significant difference. You have me digging deeper into this one, so I'll post back if I come up with anything more. I'll try making the MC++ and C# functions both return copies and pointers. Shame on you for stimulating my attrophied neurons! ;) Interesting stuff. Cheers

        P Offline
        P Offline
        Paul Selormey
        wrote on last edited by
        #3

        Jeff J wrote: Shame on you for stimulating my attrophied neurons! ...and on you for coming out with something reasonable. I am still searching my references :(( Anyway, let us know the results of your research. MC++ is here to stay!!! Best regards, Paul. Jesus Christ is LOVE! Please tell somebody.

        J 1 Reply Last reply
        0
        • J Jeff J

          Hello Andre, VizOne wrote: ...is the C# version creating the object on the heap or the stack? As you suspected, the C# version is allocating on the heap, which is what the 'newobj' IL instruction normally does. So, the two pieces of code are not equivalent, and is partly why the instructions differ so. The MC++ bit is dealing with a stack object, and even more importantly, is returning a copy of the object, not just a pointer (object reference in .Net lingo) to it. The 'ldobj' instruction verifies this, and I think a large part of the performance difference. Not only is the __value object initialised (initobj Cpp.MyStruct), but it's address is repeatedly pushed onto the stack (ldloca.s V_0) for various ops. So the address of the static struct is loaded, it's initialised, the address is again loaded, constant params loaded, the ctor called, the object's address loaded yet again, then an entire copy made before returning. I can see why performance was bad. However, I'm thinking the number of ops is not the most reliable way to gauge performance here. In particular, newobj does a lot for a single instruction, including runtime allocation on the GC heap, initialising, and then calling the ctor. Admittedly GC heap allocation is designed to be very fast, and nearly as fast as stack allocation, but the C# code does not make a copy. I suspect that's the biggest factor. I'm curious if the code was optimised during compile, as that might make a significant difference. You have me digging deeper into this one, so I'll post back if I come up with anything more. I'll try making the MC++ and C# functions both return copies and pointers. Shame on you for stimulating my attrophied neurons! ;) Interesting stuff. Cheers

          J Offline
          J Offline
          Jeff J
          wrote on last edited by
          #4

          Hello Andre (and Paul), I played with the code you posted and some other stuff, but could not find a way to optimise the MC++ MSIL any further, but that may not be as much of a problem as we first thought. BTW, I can now see your MSIL was taken from optimised compiles. Regarding your earlier question of whether the C# struct was being allocated on the stack or on the heap, I think I judged too quickly based on the MSIL. CIL documentation does not say much about newobj being applied to value types, however it "can" be used to allocate on the stack. It also says that it is rarely done this way. The blanks leave questions, but my guess is that newobj is itself optimised to return value types/structs as efficiently as possible, and in this unusual case, not on the heap. CLI instructions are not like low-level assembler op-codes, but more like convenient groups of instructions (actually, so are some op-codes). Frustrated with MSIL, I moved to assembler, and found the following:

          static MCppStruct GetStack() | public static CshStruct GetNew()
          { return MCppStruct(1,2,3); } | { return new CshStruct(1, 2, 3); }

          00 push ebp | 00 push ebp
          01 mov ebp,esp | 01 mov ebp,esp
          03 sub esp,10h | 03 sub esp,10h
          06 push edi | 06 push edi
          07 push esi | 07 push esi
          08 push ebx | 08 push ebx
          09 mov ebx,ecx |
          0b lea edi,[ebp-10h] |
          0e xor eax,eax | 09 xor eax,eax
          10 stos dword ptr [edi] | 0b mov dword ptr [ebp-10h],eax
          11 stos dword ptr [edi] | 0e mov dword ptr [ebp-0Ch],eax
          12 stos dword ptr [edi] | 11 mov dword ptr [ebp-8],eax
          | 14 mov ebx,ecx
          13 push 2 | 16 push 2
          15 push 3 | 18 push 3
          17 lea ecx,[ebp-10h] | 1a lea ecx,[ebp-10h]
          1a mov edx,1 | 1d mov edx,1
          1f call dword ptr ds:

          P 1 Reply Last reply
          0
          • P Paul Selormey

            Jeff J wrote: Shame on you for stimulating my attrophied neurons! ...and on you for coming out with something reasonable. I am still searching my references :(( Anyway, let us know the results of your research. MC++ is here to stay!!! Best regards, Paul. Jesus Christ is LOVE! Please tell somebody.

            J Offline
            J Offline
            Jeff J
            wrote on last edited by
            #5

            Hello Paul, Paul Selormey wrote: MC++ is here to stay!!! Yes it is! In fact, I'm counting on VS.Net 2003 making it a lot more popular. Looks like we'll be some of the few ahead of the game. Cheers, Jeff

            1 Reply Last reply
            0
            • J Jeff J

              Hello Andre (and Paul), I played with the code you posted and some other stuff, but could not find a way to optimise the MC++ MSIL any further, but that may not be as much of a problem as we first thought. BTW, I can now see your MSIL was taken from optimised compiles. Regarding your earlier question of whether the C# struct was being allocated on the stack or on the heap, I think I judged too quickly based on the MSIL. CIL documentation does not say much about newobj being applied to value types, however it "can" be used to allocate on the stack. It also says that it is rarely done this way. The blanks leave questions, but my guess is that newobj is itself optimised to return value types/structs as efficiently as possible, and in this unusual case, not on the heap. CLI instructions are not like low-level assembler op-codes, but more like convenient groups of instructions (actually, so are some op-codes). Frustrated with MSIL, I moved to assembler, and found the following:

              static MCppStruct GetStack() | public static CshStruct GetNew()
              { return MCppStruct(1,2,3); } | { return new CshStruct(1, 2, 3); }

              00 push ebp | 00 push ebp
              01 mov ebp,esp | 01 mov ebp,esp
              03 sub esp,10h | 03 sub esp,10h
              06 push edi | 06 push edi
              07 push esi | 07 push esi
              08 push ebx | 08 push ebx
              09 mov ebx,ecx |
              0b lea edi,[ebp-10h] |
              0e xor eax,eax | 09 xor eax,eax
              10 stos dword ptr [edi] | 0b mov dword ptr [ebp-10h],eax
              11 stos dword ptr [edi] | 0e mov dword ptr [ebp-0Ch],eax
              12 stos dword ptr [edi] | 11 mov dword ptr [ebp-8],eax
              | 14 mov ebx,ecx
              13 push 2 | 16 push 2
              15 push 3 | 18 push 3
              17 lea ecx,[ebp-10h] | 1a lea ecx,[ebp-10h]
              1a mov edx,1 | 1d mov edx,1
              1f call dword ptr ds:

              P Offline
              P Offline
              Paul Selormey
              wrote on last edited by
              #6

              Hello Jeff, Thanks for the great work you are doing. The assembler results is interesting. I now wish Andre could post his test projects so that we could examine them too. Anyway, keep it up. I now have to take my IL lessons serious :(( Best regards, Paul. Jesus Christ is LOVE! Please tell somebody.

              V 1 Reply Last reply
              0
              • V VizOne

                Hi! I am working on a function that returns an object initilized with some specific values, like __value struct MyStruct { public: MyStruct(int a, int b, int c) : a(in_a), b(in_b), c(in_c) {} static MyStruct One() { return MyStruct(1,1,1); } private: int a, b, c; }; This is compiled to the following MSIL-code: .method public static valuetype Cpp.MyStruct One() cil managed { // Code size 26 (0x1a) .maxstack 4 .locals (valuetype Cpp.MyStruct V_0) IL_0000: ldloca.s V_0 IL_0002: initobj Cpp.MyStruct IL_0008: ldloca.s V_0 IL_000a: ldc.i4.1 IL_000b: ldc.i4.1 IL_000c: ldc.i4.1 IL_000d: call instance void Cpp.MyStruct::.ctor(int32, int32, int32) IL_0012: ldloca.s V_0 IL_0014: ldobj Cpp.MyStruct IL_0019: ret } // end of method MyStruct::One I looked at IL_0002 and found a initobj. I tried to avoid initializing of the members, so I defined an empty default c'tor: MyStruct() {} However, now my IL code expanded to: .method public static valuetype Cpp.MyStruct One() cil managed { // Code size 35 (0x23) .maxstack 4 .locals (valuetype Cpp.MyStruct V_0, valuetype Cpp.MyStruct V_1) IL_0000: ldloca.s V_1 IL_0002: initobj Cpp.MyStruct IL_0008: ldloca.s V_1 IL_000a: ldc.i4.1 IL_000b: ldc.i4.1 IL_000c: ldc.i4.1 IL_000d: call instance void Cpp.MyStruct::.ctor(int32, int32, int32) IL_0012: ldloca.s V_0 IL_0014: ldloca.s V_1 IL_0016: cpobj Cpp.MyStruct IL_001b: ldloca.s V_0 IL_001d: ldobj Cpp.MyStruct IL_0022: ret } // end of method MyStruct::One Which contains a cpobj and two local instances of MyStruct and therefore seems to be even worse. I wrote a similar struct in C# to compare compilation output: public struct MyStruct { public MyStruct(int in_a, int in_b, int in_c) { a = in_a; b = in_b; c = in_c; } public static MyStruct One() { return new MyStruct(1, 1, 1); } int a, b, c; }; This compiled to the following code: .method public hidebysig static valuetype CSharp.MyStruct One() cil managed { // Code size 9 (0x9) .maxstack 8 IL

                P Offline
                P Offline
                Paul Selormey
                wrote on last edited by
                #7

                Do not know if you read this, this was a late response from MS:

                In response to your questions:

                The C# version is creating the struct on the stack. Turns out newobj
                instruction can be used to create value types on the stack. I verified
                this from the CIL spec (http://msdn.microsoft.com/net/ecma/ , see the link
                for CLI Partition III , which documents the CIL instruction set), and by
                writing a small MC++ sample that successfully consumes a value type
                returned from C# in this manner.

                Our team has entered a suggestion to see if we can generate more efficient
                CIL for the sample you provided.

                --
                Andrew Brown, Visual C++ Team
                This posting is provided AS IS with no warranties, and confers no rights.

                Best regards, Paul. Jesus Christ is LOVE! Please tell somebody.

                1 Reply Last reply
                0
                • P Paul Selormey

                  Hello Jeff, Thanks for the great work you are doing. The assembler results is interesting. I now wish Andre could post his test projects so that we could examine them too. Anyway, keep it up. I now have to take my IL lessons serious :(( Best regards, Paul. Jesus Christ is LOVE! Please tell somebody.

                  V Offline
                  V Offline
                  VizOne
                  wrote on last edited by
                  #8

                  Whoops, it seems I did not get an e-mail notification. Something's going on in here, I see. I'm short on time right now, so I cannot post the test code (maybe it was a test way too naiv at all). I'll post it when I have a little more time. Thanks already for the interesting research, Jeff! - Andre

                  1 Reply Last reply
                  0
                  Reply
                  • Reply as topic
                  Log in to reply
                  • Oldest to Newest
                  • Newest to Oldest
                  • Most Votes


                  • Login

                  • Don't have an account? Register

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • World
                  • Users
                  • Groups