simple structs initialization optimization

Started by
9 comments, last by mdias 11 years, 1 month ago

Hello,

I'm implementing C# (and taking the opportunity to learn it along the way) scripting in the game+engine I'm developing using Mono, and comming from C++ I find it very odd having to "new MyStruct()" for very simple things such as:


myNode.position = new Vector3( 0f, 10f, 0f );

I wonder if this can be a performance problem later on when I have lots of scripts running, or if I should change the interface to something like "myNode.setPosition( 0f, 10f, 0f );" instead.

The syntax is also very weird, specially on even simpler things. For example, I have a Camera.fovY property which will accept radian values, and with the aim of making it obvious for the scripter, I have implemented it as being a Radian type, however actually using this thing is rather ugly:


myCamera.fovY = new Radian( 1.0f );

Should I worry about performance with this kind of thing, or should I ignore it and trust the JIT compiler will understand what I want?

Thanks.

Advertisement

You don't need to call constructor for struct:


MyStruct s;
s.x = 15; // fine

But you won't be able to use this struct until all the fields are initialized. If you initialize a struct using a constructor, it is up to compiler to optimize this.

However, this always creates a new copy of vector and copies it to position.


myNode.position = new Vector3( 0f, 10f, 0f );

It would be interesting to look at CIL and compare various approaches.

Unless your struct's constructor is very long or complex (which should never be the case for structs anyway), I would trust the JIT to inline and optimize it.

For the radian struct you could define an implicit conversion "operator" to make the assignment more straight forward.

current project: Roa

It would be interesting to look at CIL and compare various approaches.

As far as I can see using MonoDevelop, the IL instantiates a new object. The question is whether the JIT optimizes this.

Unless your struct's constructor is very long or complex (which should never be the case for structs anyway), I would trust the JIT to inline and optimize it.



For the radian struct you could define an implicit conversion "operator" to make the assignment more straight forward.

The constructors are really simple (just assigning the arguments to it's members).

I will look into the operators for C#.

Thanks.

As far as I can see using MonoDevelop, the IL instantiates a new object. The question is whether the JIT optimizes this.

"newing" a struct should not instantiate a new object.


var x = new MyClass(1.5f);

will turn into


ldc.r4 1.5 // push 1.5f onto the evaluation stack
newobj instance void MyClass::.ctor(float32) // allocate object, call .ctor on it with value from stack and push reference to new object
stloc.0 // store reference in x (x is first local variable in this case)

whereas


var x = new MyStruct(1.5f);

will turn into


ldloca.s x // push reference to x
ldc.r4 1.5 // push 1.5f
call instance void MyStruct::.ctor(float32)

The struct's constructor is just a "normal" method. This call will almost certainly be inlined. Unless you start boxing the value, use it in a lambda expression or things like that, there should be no need for any heap allocations.

current project: Roa

Well, this is what I get:


ldc.r4 0.0
ldc.r4 1
ldc.r4 2
newobj instance void [MyEngine]MyEngine.Vector3::.ctor(float32, float32, float32)
callvirt instance void [MyEngine]MyEngine.Transform::set_position(valuetype [MyEngine]MyEngine.Vector3)


for this:


main_camera_node.transform.position = new Vector3( 0f, 1f, 2f );


Maybe I should clarify that "Transform.position" is a getter that gets the Vector3 data struct from internal C++ code:


class Transform {
...
		public Vector3 position {
			get {
				Vector3 ret;
				internal_get_position( out ret );
				return ret;
			}
			set {
				internal_set_position( ref value );
			}
		}

		[MethodImplAttribute(MethodImplOptions.InternalCall)]
		private extern void internal_get_position (out Vector3 vec);

		[MethodImplAttribute(MethodImplOptions.InternalCall)]
		private extern void internal_set_position (ref Vector3 vec);
...}


So the compiler can't really inline the actual assignment to internal data, which is ok for me. What I want to know is if the data reaches "internal_set_position" without being heap allocated, much like a call to a "void set_position( const Vector3& pos );" would make it fast to call it in the form of "set_position( Vector3( 0.f, 1.f, 2.f ) );" in C++.

Obvisually there will be an extra copy of values, on the C++ side, but as the data reaches it in "float*" format, I think the compiler can - in theory - instantiate the Vector3 on the stack and be as fast as native.

In short, I think my question really is: Is C#'s "var myVec = new Vector(0f, 0f, 0f);" as fast as C++'s "Vector3 myVec( 0.f, 0.f, 0.f );" ?

In short, I think my question really is: Is C#'s "var myVec = new Vector(0f, 0f, 0f);" as fast as C++'s "Vector3 myVec( 0.f, 0.f, 0.f );" ?


As per lwm's answer IL looks like it should be so. My gut feeling tells me that it will be quite hard to even measure the difference (all the variables land on stack and method most likely is inlined). And the real answer, as always, is in actually measuring it smile.png

I found this:

http://stackoverflow.com/questions/11966930/difference-between-call-instance-vs-newobj-instance-in-il

which quotes a part of the spec saying that newobj can be used to stack allocate value types. So that may be what you're seeing?

In short, I think my question really is: Is C#'s "var myVec = new Vector(0f, 0f, 0f);" as fast as C++'s "Vector3 myVec( 0.f, 0.f, 0.f );" ?


As per lwm's answer IL looks like it should be so. My gut feeling tells me that it will be quite hard to even measure the difference (all the variables land on stack and method most likely is inlined). And the real answer, as always, is in actually measuring it smile.png

Well, given that kind of problem at hand, it would be quiet difficult to create a test case to actually measure performance reliably.

I found this:

http://stackoverflow.com/questions/11966930/difference-between-call-instance-vs-newobj-instance-in-il

which quotes a part of the spec saying that newobj can be used to stack allocate value types. So that may be what you're seeing?

Yes. This is exacly what I was searching for. Thanks.

Here's some C# code and the JIT optimized x86 for it (Debug information, JIT optimizations not suppressed)

struct Vector3
{
	public float x;
	public float y;
	public float z;

	public Vector3(float x, float y, float z)
	{
		this.x = x;
		this.y = y;
		this.z = z;
	}
}

class Program
{
	static void Main(string[] args)
	{
		Vector3 a;
		a.x = 5;

		Console.WriteLine(a.x);
	}
}
Resulting x86:

// Frame setup
00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  push        edi 
00000004  push        esi 
00000005  push        ebx 

// Local variable block allocation
00000006  sub         esp,3Ch

// register push
00000009  mov         esi,ecx

// memset the local variable stack memory to zero
0000000b  lea         edi,[ebp-48h] 
0000000e  mov         ecx,0Fh 
00000013  xor         eax,eax 
00000015  rep stos    dword ptr es:[edi] 

// register pop
00000017  mov         ecx,esi

// Not sure what this stuff is.  It doesn't seem like it's the vector constructor because the call is conditional.
00000019  mov         dword ptr [ebp-3Ch],ecx 
0000001c  cmp         dword ptr ds:[00760B00h],0 
00000023  je          0000002A 
00000025  call        64B54A82 
0000002a  nop 

			Vector3 a;
			a.x = 5;

// Moving the float32 form of 5.0f onto the stack slot containing a.x
0000002b  mov         dword ptr [ebp-48h],40A00000h 

			Console.WriteLine(a.x);
// Push 5.0f
00000032  push        dword ptr [ebp-48h] 

// Call Console.WriteLine(float)
00000035  call        641C5470 
0000003a  nop 
		}
0000003b  nop 

// Frame teardown
0000003c  lea         esp,[ebp-0Ch] 
0000003f  pop         ebx 
00000040  pop         esi 
00000041  pop         edi 
00000042  pop         ebp 
00000043  ret 

This topic is closed to new replies.

Advertisement