[comp.lang.c++] D

gautron@corto.inria.fr (Philippe Gautron) (04/27/88)

    Drystone is a benchmark program which measures processor+compiler
    efficiency in executing a 'typical' program. My purpose is NOT to
    know if this program is good, good enough, bad for these measures
    but to compare them on a same site (and same conditions) :
    - pcc as reference,
    - gcc, the GNU C compiler
    - C++ (ATT version 1.1), cfront translator and pcc: C++.pcc
    - C++ (ATT version 1.1), cfront translator and gcc: C++.gcc
                      [cfront compiled by C++.gcc itself]
    - G++, the GNU C++ compiler (version 1.18)

Machine: SUN 3/260, STANDALONE, 8M RAM
All compilations with -O.  All compiles include the standard Sun
libraries, not gnulib.
Two tests: with and without register declarations.

dry.c: (version C/1.1, 12/01/84)
 *	Date:		PROGRAM updated 01/06/86, RESULTS updated 03/31/86
 *	Compile:	cc -O dry.c -o drynr			: No registers
 *			cc -O -DREG=register dry.c -o dryr	: Registers

1) First, a bug in dry.c: procedure Proc3
struct	Record
{
	struct Record		*PtrComp;
        ...
};

typedef struct Record 	RecordType;
typedef RecordType *	RecordPtr;


Proc1(PtrParIn)
REG RecordPtr	PtrParIn;
{
#define	NextRecord	(*(PtrParIn->PtrComp))
        ...
	Proc3(NextRecord.PtrComp);	<== call with struct Record*
	...
}


Proc3(PtrParOut)
RecordPtr	*PtrParOut;		<== called with struct Record**

This bug does not abort the execution. I have translate dry.c in C++ syntax,
and the first compilation aborts on the Proc3 declaration.


2) Second, the results (average):
Uptime
  9:36am  up 2 mins,  0 user,  load average: 0.08, 0.00, 0.00


- pcc, drynr
Dhrystone(1.1) time for 500000 passes = 84
This machine benchmarks at 5919 dhrystones/second

- pcc, dryr
Dhrystone(1.1) time for 500000 passes = 78
This machine benchmarks at 6378 dhrystones/second

- gcc, drynr
Dhrystone(1.1) time for 500000 passes = 73
This machine benchmarks at 6815 dhrystones/second

- gcc, dryr
Dhrystone(1.1) time for 500000 passes = 73
This machine benchmarks at 6811 dhrystones/second

- C++ 1.1 with pcc, drynr
Dhrystone(1.1 (C++ syntax)) time for 500000 passes = 85
This machine benchmarks at 5850 dhrystones/second

- C++ 1.1 with pcc, drynr
Dhrystone(1.1 (C++ syntax)) time for 500000 passes = 86
This machine benchmarks at 5759 dhrystones/second

- C++ 1.1 with gcc, drynr
Dhrystone(1.1 (C++ syntax)) time for 500000 passes = 73
This machine benchmarks at 6761 dhrystones/second

- C++ 1.1 with gcc, dryr
Dhrystone(1.1 (C++ syntax)) time for 500000 passes = 73
This machine benchmarks at 6765 dhrystones/second

- G++ 1.18, drynr
Dhrystone(1.1 (C++ syntax)) time for 500000 passes = 71
This machine benchmarks at 7038 dhrystones/second

- G++ 1.18, dryr
Dhrystone(1.1 (C++ syntax)) time for 500000 passes = 71
This machine benchmarks at 7040 dhrystones/second

3) Conclusion: 
   register declaration are interesting with pcc, less with gcc
   but pcc is good for loops.

   G++ > gcc > C++.gcc > pcc > C++.pcc

   There is a interesting gap between C++.pcc and C++.gcc. 

If you have some comments..

P. Gautron

------
Here is my C++ source
------

/*	EVERBODY:	Please read "APOLOGY" below. -rick 01/06/85
 *
 *	"DHRYSTONE" Benchmark Program
 *
 *	Version:	C/1.1, 12/01/84
 *
 *	Date:		PROGRAM updated 01/06/86, RESULTS updated 03/31/86
 *
 *	Author:		Reinhold P. Weicker,  CACM Vol 27, No 10, 10/84 pg. 1013
 *			Translated from ADA by Rick Richardson
 *			Every method to preserve ADA-likeness has been used,
 *			at the expense of C-ness.
 *
 *	Compile:	cc -O dry.c -o drynr			: No registers
 *			cc -O -DREG=register dry.c -o dryr	: Registers
 *
 *	Defines:	Defines are provided for old C compiler's
 *			which don't have enums, and can't assign structures.
 *			The time(2) function is library dependant; Most
 *			return the time in seconds, but beware of some, like
 *			Aztec C, which return other units.
 *			The LOOPS define is initially set for 50000 loops.
 *			If you have a machine with large integers and is
 *			very fast, please change this number to 500000 to
 *			get better accuracy.  Please select the way to
 *			measure the execution time using the TIME define.
 *			For single user machines, time(2) is adequate. For
 *			multi-user machines where you cannot get single-user
 *			access, use the times(2) function.  If you have
 *			neither, use a stopwatch in the dead of night.
 *			Use a "printf" at the point marked "start timer"
 *			to begin your timings. DO NOT use the UNIX "time(1)"
 *			command, as this will measure the total time to
 *			run this program, which will (erroneously) include
 *			the time to malloc(3) storage and to compute the
 *			time it takes to do nothing.
 *
 *	Run:		drynr; dryr
 *
 *	Results:	If you get any new machine/OS results, please send to:
 *
 *				ihnp4!castor!pcrat!rick
 *
 *			and thanks to all that do.
 *
 *	Note:		I order the list in increasing performance of the
 *			"with registers" benchmark.  If the compiler doesn't
 *			provide register variables, then the benchmark
 *			is the same for both REG and NOREG.
 *
 *	PLEASE:		Send complete information about the machine type,
 *			clock speed, OS and C manufacturer/version.  If
 *			the machine is modified, tell me what was done.
 *			On UNIX, execute uname -a and cc -V to get this info.
 *
 *	80x8x NOTE:	80x8x benchers: please try to do all memory models
 *			for a particular compiler.
 *
 *	APOLOGY (1/30/86):
 *		Well, I goofed things up!  As pointed out by Haakon Bugge,
 *		the line of code marked "GOOF" below was missing from the
 *		Dhrystone distribution for the last several months.  It
 *		*WAS* in a backup copy I made last winter, so no doubt it
 *		was victimized by sleepy fingers operating vi!
 *
 *		The effect of the line missing is that the reported benchmarks
 *		are 15% too fast (at least on a 80286).  Now, this creates
 *		a dilema - do I throw out ALL the data so far collected
 *		and use only results from this (corrected) version, or
 *		do I just keep collecting data for the old version?
 *
 *		Since the data collected so far *is* valid as long as it
 *		is compared with like data, I have decided to keep
 *		TWO lists- one for the old benchmark, and one for the
 *		new.  This also gives me an opportunity to correct one
 *		other error I made in the instructions for this benchmark.
 *		My experience with C compilers has been mostly with
 *		UNIX 'pcc' derived compilers, where the 'optimizer' simply
 *		fixes sloppy code generation (peephole optimization).
 *		But today, there exist C compiler optimizers that will actually
 *		perform optimization in the Computer Science sense of the word,
 *		by removing, for example, assignments to a variable whose
 *		value is never used.  Dhrystone, unfortunately, provides
 *		lots of opportunities for this sort of optimization.
 *
 *		I request that benchmarkers re-run this new, corrected
 *		version of Dhrystone, turning off or bypassing optimizers
 *		which perform more than peephole optimization.  Please
 *		indicate the version of Dhrystone used when reporting the
 *		results to me.
 *		
 *
 *	The following program contains statements of a high-level programming
 *	language (C) in a distribution considered representative:
 *
 *	assignments			53%
 *	control statements		32%
 *	procedure, function calls	15%
 *
 *	100 statements are dynamically executed.  The program is balanced with
 *	respect to the three aspects:
 *		- statement type
 *		- operand type (for simple data types)
 *		- operand access
 *			operand global, local, parameter, or constant.
 *
 *	The combination of these three aspects is balanced only approximately.
 *
 *	The program does not compute anything meaningfull, but it is
 *	syntactically and semantically correct.
 *
 */

/* Accuracy of timings and human fatigue controlled by next two lines */
//const LOOPS =	50000;		/* Use this for slow or 16 bit machines */
const LOOPS =	500000;		/* Use this for faster machines */

/* Compiler dependent options */
#undef	NOENUM			/* Define if compiler has no enum's */
#undef	NOSTRUCTASSIGN		/* Define if compiler can't assign structures */

/* define only one of the next two defines */
#define TIMES			/* Use times(2) time function */
/*#define TIME			/* Use time(2) time function */

/* define the granularity of your times(2) function (when used) */
const HZ =	60;		/* times(2) returns 1/60 second (most) */
//const HZ =	100;		/* times(2) returns 1/100 second (WECo) */

/* for compatibility with goofed up version */
/*#undef GOOF			/* Define if you want the goofed up version */

#ifdef GOOF
char	Version[] = "1.0";
#else
char	Version[] = "1.1 (C++ syntax)";
#endif

#ifdef	NOSTRUCTASSIGN
#define	structassign(d, s)	memcpy(&(d), &(s), sizeof(d))
#else
#define	structassign(d, s)	d = s
#endif

#ifdef	NOENUM
const	Ident1 =	1;
const	Ident2 =	2;
const	Ident3 =	3;
const	Ident4 =	4;
const	Ident5 =	5;
#else
enum Enumeration {Ident1, Ident2, Ident3, Ident4, Ident5};
#endif

typedef int	OneToThirty;
typedef int	OneToFifty;
typedef char	CapitalLetter;
typedef char	String30[31];
typedef int	Array1Dim[51];
typedef int	Array2Dim[51][51];

struct	Record
{
	Record			*PtrComp;
	Enumeration		Discr;
	Enumeration		EnumComp;
	OneToFifty		IntComp;
	String30		StringComp;
};

typedef int		boolean;

const	NULL =		0;
const	TRUE =		1;
const	FALSE =		0;

#ifndef REG
#define	REG
#endif

extern Enumeration	Func1( CapitalLetter, CapitalLetter);
extern boolean		Func2( String30, String30 );

// C++ declararions
extern boolean		Func3( Enumeration );
extern void		Proc0(), Proc1( Record* ), Proc2( OneToFifty* ),
			Proc3( Record** ), Proc4(), Proc5(),
			Proc6( Enumeration, Enumeration* ),
			Proc7( OneToFifty, OneToFifty, OneToFifty* ),
			Proc8( Array1Dim, Array2Dim, OneToFifty, OneToFifty );
extern int		exit( int ),
			printf( const char*, ... );
extern char*		strcpy( const char*, const char* ),
			strcmp( const char*, const char* );

#ifdef TIMES
#include <sys/types.h>
#include <sys/times.h>
#endif

main()
{
	Proc0();
	exit(0);
}

/*
 * Package 1
 */
int		IntGlob;
boolean		BoolGlob;
char		Char1Glob;
char		Char2Glob;
Array1Dim	Array1Glob;
Array2Dim	Array2Glob;
Record*	PtrGlb;
Record*	PtrGlbNext;

void Proc0()
{
	OneToFifty		IntLoc1;
	REG OneToFifty		IntLoc2;
	OneToFifty		IntLoc3;
	REG char		CharLoc;
	REG char		CharIndex;
	Enumeration	 	EnumLoc;
	String30		String1Loc;
	String30		String2Loc;
//	extern char		*malloc();
	register unsigned int	i;

#ifdef TIME
	long			time( long* );
	long			starttime;
	long			benchtime;
	long			nulltime;

	starttime = time(0);
	for (i = 0; i < LOOPS; ++i);
	nulltime = time(0) - starttime; /* Computes o'head of loop */
#endif
#ifdef TIMES
	time_t			starttime;
	time_t			benchtime;
	time_t			nulltime;
	struct tms		tms;

	times(&tms); starttime = tms.tms_utime;
	for (i = 0; i < LOOPS; ++i);
	times(&tms);
	nulltime = tms.tms_utime - starttime; /* Computes overhead of looping */
#endif

	PtrGlbNext = new Record;
	PtrGlb = new Record;
	PtrGlb->PtrComp = PtrGlbNext;
	PtrGlb->Discr = Ident1;
	PtrGlb->EnumComp = Ident3;
	PtrGlb->IntComp = 40;
	strcpy(PtrGlb->StringComp, "DHRYSTONE PROGRAM, SOME STRING");
#ifndef	GOOF
	strcpy(String1Loc, "DHRYSTONE PROGRAM, 1'ST STRING");	/*GOOF*/
#endif
	Array2Glob[8][7] = 10;	/* Was missing in published program */

/*****************
-- Start Timer --
*****************/
#ifdef TIME
	starttime = time(0);
#endif
#ifdef TIMES
	times(&tms); starttime = tms.tms_utime;
#endif
	for (i = 0; i < LOOPS; ++i)
	{

		Proc5();
		Proc4();
		IntLoc1 = 2;
		IntLoc2 = 3;
		strcpy(String2Loc, "DHRYSTONE PROGRAM, 2'ND STRING");
		EnumLoc = Ident2;
		BoolGlob = ! Func2(String1Loc, String2Loc);
		while (IntLoc1 < IntLoc2)
		{
			IntLoc3 = 5 * IntLoc1 - IntLoc2;
			Proc7(IntLoc1, IntLoc2, &IntLoc3);
			++IntLoc1;
		}
		Proc8(Array1Glob, Array2Glob, IntLoc1, IntLoc3);
		Proc1(PtrGlb);
		for (CharIndex = 'A'; CharIndex <= Char2Glob; ++CharIndex)
			if (EnumLoc == Func1(CharIndex, 'C'))
				Proc6(Ident1, &EnumLoc);
		IntLoc3 = IntLoc2 * IntLoc1;
		IntLoc2 = IntLoc3 / IntLoc1;
		IntLoc2 = 7 * (IntLoc3 - IntLoc2) - IntLoc1;
		Proc2(&IntLoc1);
	}

/*****************
-- Stop Timer --
*****************/

#ifdef TIME
	benchtime = time(0) - starttime - nulltime;
	printf("Dhrystone(%s) time for %ld passes = %ld\n",
		Version,
		(long) LOOPS, benchtime);
	printf("This machine benchmarks at %ld dhrystones/second\n",
		((long) LOOPS) / benchtime);
#endif
#ifdef TIMES
	times(&tms);
	benchtime = tms.tms_utime - starttime - nulltime;
	printf("Dhrystone(%s) time for %ld passes = %ld\n",
		Version,
		(long) LOOPS, benchtime/HZ);
	printf("This machine benchmarks at %ld dhrystones/second\n",
		((long) LOOPS) * HZ / benchtime);
#endif

}

void Proc1 (REG Record* PtrParIn)
{
#define	NextRecord	(*(PtrParIn->PtrComp))

	structassign(NextRecord, *PtrGlb);
	PtrParIn->IntComp = 5;
	NextRecord.IntComp = PtrParIn->IntComp;
	NextRecord.PtrComp = PtrParIn->PtrComp;
	Proc3((Record**)NextRecord.PtrComp);
	if (NextRecord.Discr == Ident1)
	{
		NextRecord.IntComp = 6;
		Proc6(PtrParIn->EnumComp, &NextRecord.EnumComp);
		NextRecord.PtrComp = PtrGlb->PtrComp;
		Proc7(NextRecord.IntComp, 10, &NextRecord.IntComp);
	}
	else
		structassign(*PtrParIn, NextRecord);

#undef	NextRecord
}

void Proc2 (OneToFifty* IntParIO)
{
	REG OneToFifty		IntLoc;
	REG Enumeration		EnumLoc;

	IntLoc = *IntParIO + 10;
	for(;;)
	{
		if (Char1Glob == 'A')
		{
			--IntLoc;
			*IntParIO = IntLoc - IntGlob;
			EnumLoc = Ident1;
		}
		if (EnumLoc == Ident1)
			break;
	}
}

void Proc3 (Record** PtrParOut)
{
	if (PtrGlb != 0)
		*PtrParOut = PtrGlb->PtrComp;
	else
		IntGlob = 100;
	Proc7(10, IntGlob, &PtrGlb->IntComp);
}

void Proc4()
{
	REG boolean	BoolLoc;

	BoolLoc = Char1Glob == 'A';
	BoolLoc |= BoolGlob;
	Char2Glob = 'B';
}

void Proc5()
{
	Char1Glob = 'A';
	BoolGlob = FALSE;
}

// extern boolean Func3();

void Proc6( REG Enumeration	EnumParIn, REG Enumeration* EnumParOut )
{
	*EnumParOut = EnumParIn;
	if (! Func3(EnumParIn) )
		*EnumParOut = Ident4;
	switch (EnumParIn)
	{
	case Ident1:	*EnumParOut = Ident1; break;
	case Ident2:	if (IntGlob > 100) *EnumParOut = Ident1;
			else *EnumParOut = Ident4;
			break;
	case Ident3:	*EnumParOut = Ident2; break;
	case Ident4:	break;
	case Ident5:	*EnumParOut = Ident3;
	}
}

void Proc7 (OneToFifty IntParI1, OneToFifty IntParI2, OneToFifty* IntParOut )
{
	REG OneToFifty	IntLoc;

	IntLoc = IntParI1 + 2;
	*IntParOut = IntParI2 + IntLoc;
}

void Proc8 (Array1Dim Array1Par, Array2Dim Array2Par,
                                   OneToFifty IntParI1, OneToFifty IntParI2)
{
	REG OneToFifty	IntLoc;
	REG OneToFifty	IntIndex;

	IntLoc = IntParI1 + 5;
	Array1Par[IntLoc] = IntParI2;
	Array1Par[IntLoc+1] = Array1Par[IntLoc];
	Array1Par[IntLoc+30] = IntLoc;
	for (IntIndex = IntLoc; IntIndex <= (IntLoc+1); ++IntIndex)
		Array2Par[IntLoc][IntIndex] = IntLoc;
	++Array2Par[IntLoc][IntLoc-1];
	Array2Par[IntLoc+20][IntLoc] = Array1Par[IntLoc];
	IntGlob = 5;
}

Enumeration Func1 (CapitalLetter CharPar1, CapitalLetter CharPar2)
{
	REG CapitalLetter	CharLoc1;
	REG CapitalLetter	CharLoc2;

	CharLoc1 = CharPar1;
	CharLoc2 = CharLoc1;
	if (CharLoc2 != CharPar2)
		return (Ident1);
	else
		return (Ident2);
}

boolean Func2 (String30	StrParI1, String30 StrParI2)
{
	REG OneToThirty		IntLoc;
	REG CapitalLetter	CharLoc;

	IntLoc = 1;
	while (IntLoc <= 1)
		if (Func1(StrParI1[IntLoc], StrParI2[IntLoc+1]) == Ident1)
		{
			CharLoc = 'A';
			++IntLoc;
		}
	if (CharLoc >= 'W' && CharLoc <= 'Z')
		IntLoc = 7;
	if (CharLoc == 'X')
		return(TRUE);
	else
	{
		if (strcmp(StrParI1, StrParI2) > 0)
		{
			IntLoc += 7;
			return (TRUE);
		}
		else
			return (FALSE);
	}
}

boolean Func3( REG Enumeration EnumParIn )
{
	REG Enumeration	EnumLoc;

	EnumLoc = EnumParIn;
	if (EnumLoc == Ident3) return (TRUE);
	return (FALSE);
}

#ifdef	NOSTRUCTASSIGN
memcpy(d, s, l)
register char	*d;
register char	*s;
register int	l;
{
	while (l--) *d++ = *s++;
}
#endif

jima@hplsla.HP.COM ( Jim Adcock) (04/30/88)

Here's some results for running this "C++" compatible version
of Dhrystone program on various hp machines I have around
me.  Other than on my 320 system the "C++" results were
generated by transferring the ..c intermediate file to
the target machine, changing _new to malloc and removing
_main, then compiling on that machines available "cc"
compilers.  The definition of "HZ" was changed to match the
respective HP machines -- 50Hz for HP series 300, 100Hz for
HP series 800.  Caveat: I do not believe these results are
particularly useful for comparing the relative merits of
cc compilers, C++ compilers, or various host machines if
the results are at all similar in speed.  If the results 
are radically different [>2X], then maybe these kinds of tests
CAN be considered somewhat meaningful in comparing compilers
and machines.  The HP320 is a mid-range 68020 based engineering
workstation.  The HP350 is a high-range 68020 based engineering
workstation.  The 840 and 850 are engineering minicomputers
based on HP's new proprietory "RISC" architecture.  The HP320
is the machine I have presently, which explains why more 
benchmarks are available for it.  These results were generated
"casually", just for my own edification and amusement, and shouldn't
be considered gospel.  In my opinion, this test program is not
doing a good job of exercising the power/lack-of-power available
in the various compilers optimization routines.  Compile and
test some "real-world" C++ routines, like oopslib, to "seriously"
compare C++ compilers.  Better yet, run comparisons using the
kind of programs you really write.


CPU	Compiler[s]				     "Dhrystones"

HP320	CC 1.11 / HP-UX 6.0 cc				 2083
HP320	CC 1.11 / HP-UX 6.0 cc -O			 2181
HP320	g++ 1.18					 2194
HP320	g++ 1.18 -O					 2194
HP320	g++ 1.18 -O -fomit-frame-pointer 
		    -finline-functions 	 		 2203
HP320	CC 1.11 / Microtec 3.0 [options unknown]	 2278
HP350	CC 1.11 / HP-UX 6.0 cc -O	 		 4752
HP350	g++ 1.18 -O -fomit-frame-pointer 
		    -finline-functions 	 		 4902
HP350	CC 1.11 / Microtec 3.0 [options unknown]	 5122
HP840   CC 1.11 / HP-UX cc -O				10940
HP850   CC 1.11 / HP-UX cc -O				20408

Conclusions: On THIS benchmark, choice of compilers doesn't
matter too much.  Choice of computer system makes a much bigger
difference.  The HP320 might be a little on the low side
for doing much C++ compiling -- at least until incremental 
compilation/link of C++ programs becomes a reality.  The HP350
isn't a bad match for C++ compilation work.  Access to a lightly
loaded HP850 system would make C++ development work a gas.