March 28, 2006
Derek Parnell wrote:
> Classes are slower than structs. Prove otherwise. I'm happy to be shown  that I'm wrong.
> 

Here's a slightly modified version of Dave's code.

-------
import std.stdio, std.date;

struct S
{
	char[10000] str;
}

class C
{
	char[10000] str;
}

void main()
{
	const int y = 1_000_000_0;
	
	//structs
	{
		S s;
		d_time st = getUTCtime();
		for(int x = 0; x < y; x++)
		{
			fooS(s);
		}
		d_time en = getUTCtime();
		writef("struct: ");
		writefln((en-st) / cast(double)TicksPerSecond);
	}
		
	//classes
	{
		C c = new C;
		d_time st = getUTCtime();
		for(int x = 0; x < y; x++)
		{
			fooC(c);
		}
		d_time en = getUTCtime();
		writef("class: ");
		writefln((en-st) / cast(double)TicksPerSecond);
	}
}

void fooS(S s)
{
	s.str[9999] = 15;
}

void fooC(C c)
{
	c.str[9999] = 15;
}
----------

on my machine:
C:\test\d\struct.vs.class\pass>dave
struct: 44.422
class: 0.046

that's about 1000 times faster

44.422/.046 = 965.696

Note: I don't believe this to be a valid benchmark, but I believe it's just as valid as Hong's benchmark.

It's designed to make the class version win.


March 28, 2006
In article <e09vl7$2u5f$1@digitaldaemon.com>, Hasan Aljudy says...
>
>Derek Parnell wrote:
>> Classes are slower than structs. Prove otherwise. I'm happy to be shown that I'm wrong.
>> 
>
>Note: I don't believe this to be a valid benchmark, but I believe it's just as valid as Hong's benchmark.
>
>It's designed to make the class version win.
>

That wasn't my intention <g> I just wanted to point out that classes can have an advantage as well.

I think Don Clungston's recent post regarding optimizing temp. creation out of things like opAdd could solve many concerns about having to use structs in place of classes because of heap allocation bottlenecks.

- Dave


March 28, 2006
Basically what your benchmark (along with Hong's) prove is that classes and structs each have different uses (duh!). Use structs in situations like Hong's case where classes would require a ton of allocation, and use classes in situations like your case where passing by value would induce enormous overhead costs.

In article <e09vl7$2u5f$1@digitaldaemon.com>, Hasan Aljudy says...
>
>Derek Parnell wrote:
>> Classes are slower than structs. Prove otherwise. I'm happy to be shown that I'm wrong.
>> 
>
>Here's a slightly modified version of Dave's code.
>
>-------
>import std.stdio, std.date;
>
>struct S
>{
>	char[10000] str;
>}
>
>class C
>{
>	char[10000] str;
>}
>
>void main()
>{
>	const int y = 1_000_000_0;
>
>	//structs
>	{
>		S s;
>		d_time st = getUTCtime();
>		for(int x = 0; x < y; x++)
>		{
>			fooS(s);
>		}
>		d_time en = getUTCtime();
>		writef("struct: ");
>		writefln((en-st) / cast(double)TicksPerSecond);
>	}
>
>	//classes
>	{
>		C c = new C;
>		d_time st = getUTCtime();
>		for(int x = 0; x < y; x++)
>		{
>			fooC(c);
>		}
>		d_time en = getUTCtime();
>		writef("class: ");
>		writefln((en-st) / cast(double)TicksPerSecond);
>	}
>}
>
>void fooS(S s)
>{
>	s.str[9999] = 15;
>}
>
>void fooC(C c)
>{
>	c.str[9999] = 15;
>}
>----------
>
>on my machine:
>C:\test\d\struct.vs.class\pass>dave
>struct: 44.422
>class: 0.046
>
>that's about 1000 times faster
>
>44.422/.046 = 965.696
>
>Note: I don't believe this to be a valid benchmark, but I believe it's just as valid as Hong's benchmark.
>
>It's designed to make the class version win.
>
>


March 28, 2006
On Mon, 27 Mar 2006 17:23:05 -0700, Hasan Aljudy wrote:


> Note: I don't believe this to be a valid benchmark, but I believe it's just as valid as Hong's benchmark.
> 
> It's designed to make the class version win.

I understand.

To make them equivalent, use the 'inout' calling convention for the struct. Change to 'void fooS(inout S s)' and the timings are the same.

-- 
Derek
(skype: derek.j.parnell)
Melbourne, Australia
"Down with mediocracy!"
28/03/2006 12:51:07 PM
March 28, 2006
I partly agree with you.

1) A constructor for structs would definitely be necessary: Currently, a struct is always initialized to all-zero fields. The "static opCall" is nice syntactic sugar for changing the contents of the struct afterwards, but it does not allow a guaranteed initialization, since D does not force you to call opCall.

2) Reference handling similar to C++ would be very desirable. Of course, a pointer can always be used instead, but it makes very ugly code and is probably one of the reasons why structs are used so rarely in D.

3) Yes, classes are inefficient when used for purposes that should be done with a struct. I have seen implementations of 3D-vector arithmetics in D where the vector was defined as class. This means: each vector has to be allocated on the heap!!! The benchmark example that you give may be artificial but it is not at all unrealistic to have a loop that allocates and deallocates 100000 small data structures. It makes a tremendous difference whether this is done on the heap or on the stack!

It is good that D has a clear distinction between classes and structs, but structs should be viewed as an important concept and given all the power possible. Saying "use a class instead" is hardly ever an acceptable excuse for lacking power in structs.

Greetings,
Norbert
March 28, 2006
Oskar Linde wrote:
> Stewart Gordon wrote:
>> Hong wrote:
>>> Structs are second class citizens of... or maybe refugees of D, they are victims
>>> of discrimination.
>>> Following are the reasons:
> 
> [snip]
> 
>>> 4. structs cannot have constructor, "static opCall" is best you can do.
>>
>> What's wrong with that?  Do you simply miss the word "new" in uses of static opCall?
> 
> The problem with not having real constructors is that you can not guarantee that your struct gets initialized correctly.

Is the definition of a "real" constructor simply such that one is required to use a constructor rather than a static initialiser or leaving default initialisers to it?

<snip>
>>> 6. yes, you can use pointers to change a struct member without copying, if you can be bothered to define 2 accessor methods, one returns a copy another one returns a pointer.
>>
>> Why would anyone want to do that?  What's wrong with simply
>>
>>     Qwert yuiop = asdfg;    // to create a copy
>>     Qwert* zxcvb = &asdfg;  // to create a pointer
> 
> If I understand correctly, Hong is talking about struct members (i.e. a member variable of a struct type), that should be accessed through an accessor method. I.e:
> 
> Array!(Position) points = ...;
> points[5].x = 7;

Note that the OP hasn't mentioned container methods by this point.  It therefore appears that this was about properties of the struct itself. But looking down, it now appears that it was *meant* to be about containers.

<snip>
>>> 7. Most standard containers (DTL) do not allow a pointer to a contained struct to be retrieved, all changes, or just about
>>> anything has to be done via copies.
>> 
>> That's an issue with those containers, rather than with structs themselves.
> 
> I agree that this is not an issue with structs.  But it is an issue with the language. Given:
> 
> struct Position { int x,y; }
> 
> There is no way to duplicate the behavior of:
> 
> Position[] points = ...;
> points[5].y = 5;
> points[3].x += 2;
> 
> With an user defined container type.

You can if you define opIndex to return a pointer.  But that would require you to use a * when it's the value you need.  Or, as was suggested, define a separate accessor method that returns a pointer. But I see your point now.

>>> Imho, following are little suggestions for structs to make first class citizens
>>> of D:
>>>
>>> 1. have some sort of reference semantic to replace pointers. Pointers are not
>>> necessary unless structs are used with "new"
>>
>> Maybe you could elaborate on how these reference semantics would work such that they'd be any different from classes.
> 
> You could consider:
> 
> struct MyPointContainer {
>     Position[] points;
>     inout Position opIndex(uint ix) { return &points[ix]; }
> }
> 
> Where inout denotes that the struct is returned by reference.

Yes, that's a possibility.  Though the & ought not to be there - the referencing/dereferencing would be implicit on both sides with inout returns, just as it is with out/inout parameters.

And it isn't giving structs reference semantics, it's a way of enabling returns from functions to have reference semantics in the same way as out/inout function parameters have.  It would work on any type, not just structs.

I suppose that, when this is done

    container[42] = something;

would be interpreted by looking first for an opIndexAssign and then for an opIndex with inout return.  But how would overload resolution work between the two forms?

Stewart.

-- 
-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GCS/M d- s:-@ C++@ a->--- UB@ P+ L E@ W++@ N+++ o K-@ w++@ O? M V? PS- PE- Y? PGP- t- 5? X? R b DI? D G e++>++++ h-- r-- !y
------END GEEK CODE BLOCK------

My e-mail is valid but not my primary mailbox.  Please keep replies on the 'group where everyone may benefit.
March 28, 2006
Norbert Nemec wrote:
> I partly agree with you.
> 
> 1) A constructor for structs would definitely be necessary: Currently, a
> struct is always initialized to all-zero fields. The "static opCall" is
> nice syntactic sugar for changing the contents of the struct afterwards,
> but it does not allow a guaranteed initialization, since D does not
> force you to call opCall.
<snip>

Struct members can have default initialisers.  The only way in which it "does not allow a guaranteed initialization" is that it's possible to override default initialisers with a static initialiser.  But that's just like changing members one-by-one after it's been initialised.

Stewart.

-- 
-----BEGIN GEEK CODE BLOCK-----
Version: 3.1
GCS/M d- s:-@ C++@ a->--- UB@ P+ L E@ W++@ N+++ o K-@ w++@ O? M V? PS- PE- Y? PGP- t- 5? X? R b DI? D G e++>++++ h-- r-- !y
------END GEEK CODE BLOCK------

My e-mail is valid but not my primary mailbox.  Please keep replies on the 'group where everyone may benefit.
March 28, 2006
Ben Phillips wrote:
> Basically what your benchmark (along with Hong's) prove is that classes and
> structs each have different uses (duh!). Use structs in situations like Hong's
> case where classes would require a ton of allocation, and use classes in
> situations like your case where passing by value would induce enormous overhead
> costs.
> 

Or if you still want to use classes, you can use an object pool.

Since object pools are a nice paradigm in some instances, I wonder if a good standard set of library primitives could be built into phobos?

-DavidM


March 28, 2006
> Or if you still want to use classes, you can use an object pool.
>
> Since object pools are a nice paradigm in some instances, I wonder if a
> good standard set of library primitives could be built into phobos?


Good idea , I was wanting this recently too.



David Medlock wrote:
> Ben Phillips wrote:
> 
>> Basically what your benchmark (along with Hong's) prove is that classes and
>> structs each have different uses (duh!). Use structs in situations like Hong's
>> case where classes would require a ton of allocation, and use classes in
>> situations like your case where passing by value would induce enormous overhead
>> costs.
>>
> 
> Or if you still want to use classes, you can use an object pool.
> 
> Since object pools are a nice paradigm in some instances, I wonder if a good standard set of library primitives could be built into phobos?
> 
> -DavidM
> 
> 
April 02, 2006
Derek Parnell wrote:
> 
> But I think my example does show that classes (allocated on the heap) are slower than structs whether allocated on the heap or on the stack.
> 
> Classes are slower than structs. Prove otherwise. I'm happy to be shown that I'm wrong.
> 
> --Derek Parnell
> Melbourne, Australia

Now this is odd. I didn't expect classes on the heap to be much slower than structs on the heap. After all, what is the extra overhead of classes?
It has the size overhead of having 2 more pointers (for ClassInfo and for the monitor) and the overhead of initializing those pointers when instantiating a class. Anything else? I thought not, however, I tried to run a test (see code below), where I would simulate a class creation with a struct, adding that overhead, yet the struct version is still twice as fast (with or without optimization)! Why is that?? The best that I could find was by looking at the assembly code, and that in the class version a _d_newclass function is called instead of _d_new for the struct version, yet does _d_newclass do more than _d_new other than just initialize the pointers?


// -------------- code starts -------------------
private import std.c.stdio;
private import std.c.time;
private import std.stdio;

const static uint n = 100000000;

struct PointStruct
{
	void * classinfo;
	void * monitor;

	void Construct(void *newclassinfo) {
	    this.classinfo = newclassinfo;
	    this.monitor = cast(void *) 0x98765;
	}
}

void doitStruct()
{
    PointStruct *p = new PointStruct;
    p.Construct(cast(void *) 0x12345678);
}


final class PointClass
{
}


void doitClass()
{
    PointClass p = new PointClass;
}

int main()
{
	writefln(PointClass.classinfo.init.length," : ", PointStruct.sizeof);

	clock_t time1 = clock();
	for (uint i = 0; i < n; ++i)
	{
		version(test_struct_heap) {		
			doitStruct();
		}
		version(test_class_heap) {		
			doitClass();
		}
	}

	double seconds = (clock() - time1);
	seconds /= CLOCKS_PER_SEC;
	writefln("Testing operation took %ss",  seconds);
	return 0;
}

// -------------- code ends -------------------

sh-2.04$ build main -version=test_struct_heap -Tbtsh -full
c:\devel\D.tools\dmd\bin\..\..\dm\bin\link.exe main,btsh.exe,,user32+kernel32,btsh.def/noi;

sh-2.04$ build main -version=test_class_heap -Tbtch -full
c:\devel\D.tools\dmd\bin\..\..\dm\bin\link.exe main,btch.exe,,user32+kernel32,btch.def/noi;

sh-2.04$ time ./btsh.exe
8 - 8
Testing operation took 18.204s

real    0m18.328s
user    0m0.015s
sys     0m0.015s

sh-2.04$ time ./btch.exe
8 - 8
Testing operation took 42.516s

real    0m42.625s
user    0m0.015s
sys     0m0.015s

-- 
Bruno Medeiros - CS/E student
http://www.prowiki.org/wiki4d/wiki.cgi?BrunoMedeiros#D