Jump to content

  • Log In with Google      Sign In   
  • Create Account

simple structs initialization optimization


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
10 replies to this topic

#1 mdias   Members   -  Reputation: 788

Like
0Likes
Like

Posted 27 February 2013 - 09:05 PM

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.


Edited by Kamikaze15, 27 February 2013 - 09:05 PM.


Sponsor:

#2 Nercury   Crossbones+   -  Reputation: 766

Like
0Likes
Like

Posted 28 February 2013 - 03:53 AM

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.


Edited by Nercury, 28 February 2013 - 04:11 AM.


#3 lwm   Members   -  Reputation: 1418

Like
1Likes
Like

Posted 28 February 2013 - 06:25 AM

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


#4 mdias   Members   -  Reputation: 788

Like
0Likes
Like

Posted 28 February 2013 - 08:05 AM

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.



#5 lwm   Members   -  Reputation: 1418

Like
2Likes
Like

Posted 28 February 2013 - 10:22 AM

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.


Edited by lwm, 28 February 2013 - 10:28 AM.

current project: Roa


#6 mdias   Members   -  Reputation: 788

Like
0Likes
Like

Posted 28 February 2013 - 11:44 AM

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 );" ?


Edited by mdias, 28 February 2013 - 11:46 AM.


#7 Nercury   Crossbones+   -  Reputation: 766

Like
1Likes
Like

Posted 28 February 2013 - 11:57 AM

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

#8 phil_t   Crossbones+   -  Reputation: 3915

Like
1Likes
Like

Posted 28 February 2013 - 12:40 PM

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?



#9 mdias   Members   -  Reputation: 788

Like
0Likes
Like

Posted 28 February 2013 - 02:30 PM

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.



#10 Nypyren   Crossbones+   -  Reputation: 4302

Like
0Likes
Like

Posted 28 February 2013 - 02:50 PM

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 

Edited by Nypyren, 28 February 2013 - 02:57 PM.


#11 mdias   Members   -  Reputation: 788

Like
0Likes
Like

Posted 28 February 2013 - 07:12 PM

That looks to be what I want, but will it remain the same (except with 3 movs instead of 1) if using the "Vector3 a = new Vector3( x, y, z );" form?

 

Also, what tool did you use to get the x86 disassembled code?






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS