Click to See Complete Forum and Search --> : Creating objects on stack


VizOne
February 28th, 2003, 12:14 PM
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_0000: ldc.i4.1
IL_0001: ldc.i4.1
IL_0002: ldc.i4.1
IL_0003: newobj instance void CSharp.MyStruct::.ctor(int32,
int32,
int32)
IL_0008: ret
} // end of method MyStruct::One

Now I tested execution time and I am not really suprised: the the C++ struct
without default c'tor was about 30% slower than the C# version. The C++
Version with default c'tor even was 70% slower than the C# version.

Now I have two questions: first, is the C# version creating the object on
the heap or the stack?
And: how can I make the C++ version compile to the faster version?
Thanks in advance!

- Andre

Paul McKenzie
February 28th, 2003, 12:55 PM
Originally posted by VizOne
Hi!

I am working on a function that returns an object initilized with some specific values, like...
The function returns an object -- then that is where the copy constructor for the class has to be invoked. That is why the extra copy is needed. There is no need to look at assembly listings to know that C++ has to create a copy of a MyStruct() when it is returned to the caller.

It would be better if you showed the actual C++ code you used to test your class with. You have a static member which must be instantiated at program startup that creates a temporary copy (because that is the rule of C++), but that's all the information we know, given that you haven't supplied the code you are using.
Maybe C# has different rules than C++ when returning instances, I don't know C# to know this information. If C# does have different rules than C++ when an object is returned, then you need to code your C++ more efficiently, or don't return objects, or some other means.
Now I tested execution time and I am not really suprised: the the C++ struct without default c'tor was about 30% slower than the C# version. The C++ Version with default c'tor even was 70% slower than the C# version.Again, show us the C++ code you used to test this. Before looking at assembly listings, it is always best to see what you're doing at a higher level.

Also, did you time a debug build instead of a release build? Maybe the optimizations were not turned on for the build you tested.

Regards,

Paul McKenzie

VizOne
February 28th, 2003, 01:14 PM
This is the simple test I did. It's written in C#, though the C++ Version would look similar. I know it's nor very practical, but usefull for comparison.

Timer is a class of mine for high resolution timing (using RDTSC).

double t1, t2;
double r1, r2;
const int COUNT = 1000000;

Timer timer = Timer.SysTimer;
t1 = timer.MicroTime;

CSharp.MyStruct cs = CSharp.MyStruct.One();
Cpp.MyStruct cpp = Cpp.MyStruct.One();

for(int c=0; c< 10; ++c)
{
t1 = timer.MicroTime;
for(int i=0; i < COUNT; ++i)
cs = CSharp.MyStruct.One();
t2 = timer.MicroTime;
r1 = t2-t1;

t1 = timer.MicroTime;
for(int i=0; i < COUNT; ++i)
cpp = Cpp.MyStruct.One();
t2 = timer.MicroTime;
r2 = t2-t1;

Console.WriteLine("C#: {0}\nC++: {1}", r1, r2);
}


Regards,
Andre

Paul McKenzie
February 28th, 2003, 01:46 PM
Looks like you are comparing apples to oranges.

Change your class to print messages when objects are being created, destroyed, and copied. You will see that C++ has to do more work, since the MyStruct has to be returned by value.

#include <iostream>
using namespace std;

struct MyStruct
{
public:
MyStruct(int a, int b, int c)
: a(in_a), b(in_b), c(in_c)
{ cout << "3 arg constructor called" << endl; }

~MyStruct()
{ cout << "Destructor called" << endl; }

MyStruct(const MyStruct& rhs)
{ cout << "MyStruct copy-ctor has been called" << endl; }

MyStruct& operator = (const MyStruct& rhs)
{ cout << "Assignment of MyStruct called" << endl;
return *this;
}

MyStruct()
{ cout << "MyStruct default ctor called" << endl; }

static MyStruct One() { return MyStruct(1,1,1); }

private:
int a, b, c;
};

This will give you a far better picture of what is happening than a cryptic assembly listing.

In C# (again, I know verly little about the C# language, so I'm guessing), you are calling "new" and returning an object / pointer / whatever. I don't know if C# returns a pointer behind the scenes, but if it does, this is the difference.

My guess is that to do a better timing test, this would be more valid:

struct MyStruct
{
public:
MyStruct(int a, int b, int c)
: a(in_a), b(in_b), c(in_c) {}

static MyStruct* One() { return new MyStruct(1,1,1); }

private:
int a, b, c;
};

// In your code
MyStruct *cpp;
t1 = timer.MicroTime;
for(int i=0; i < COUNT; ++i)
cpp = Cpp.MyStruct.One();
t2 = timer.MicroTime;
r2 = t2-t1;

This code returns a pointer (4 bytes) as opposed to calling the copy constructor when having to assign back to the calling function.

However, this code has a problem in that there is a memory leak due to calling "new" and not calling "delete". However, I am just showing it for illustration purposes. Since C++ has no garbage collector as does C#, calling delete each time in the loop may not match with what C# does to clear instances. Maybe you build up the instances and do a "mass" delete, emulating the C# garbage collector. Again, this is a comparison of apples and oranges which makes these tests not a good indicator.

If you want your C++ code to be faster, concentrate on speeding up the C++. Comparing it to other languages will not really help.

Regards,

Paul McKenzie