Dhrystone compiled on BBQL with Digital C SE

Anything QL Software or Programming Related.
User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Dhrystone compiled on BBQL with Digital C SE

Post by bwinkel67 »

For fun I've been trying to compile the Dhrystone code under Digital C SE. A possible goal, besides just wanting to play with my old compiler on the QL, is to maybe write a SysInfo-like application for the QL (i.e. nice interface that gave some info on the model you had and then ran Dhrystone to tell you how fast it is). We will see if I end up doing that.

I finally got it to run though it still have some issues. For whatever reason it seems to balk above 16 bits, i.e. when my LOOPS variable is 33000 (it's normally hard-coded at 50000 for 16 bit machines but I made it input for debugging) it runs into an infinite loop, suggesting something is wrapping around at the 16 bit boundary. Not sure why since the loop variable (i) is a long (i.e. 32 bits).

When I run it for 32000 loops it seems to work but the results are abysmal, running at half the speed of the pre-compiled executable (Bench.zip from Dilwyn's site, which comes with source and an executable). When I run it on QLAY at whatever speed I have it set to, it gives around 1300 Drhystones compared to 2800 from the pre-compiled version, so there is something still not right.

Looking at the source, there are some oddities in there. Has anyone ever looked at the Dhrystone code? Below is my revised version. I kept the original and either commented out stuff I couldn't use or changed it in place. For instance, Digital C SE doesn't offer typedef so I used #define where that worked, and also made changes appropriately. It doesn't allow 2D arrays directly but you can use a nested struct to easily solve that, etc...

Code: Select all

/* dhrystone - benchmark program */ 
 
#define REGISTER 
/* 
 * 
 *	"DHRYSTONE" Benchmark Program 
 * 
 *	Version:	C/1.1, 12/01/84 
 * 
 *	Date:		PROGRAM updated 01/06/86, COMMENTS changed 01/31/87 
 * 
 *	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.  Be careful to 
 *			adjust the HZ parameter below for the units which 
 *			are returned by your times(2) function.  You can 
 *			sometimes find this in <sys/param.h>.  If you have 
 *			neither time(2) nor times(2), use a stopwatch in 
 *			the dead of the 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. 
 * 
 * 
 *	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 */ 
/* #define LOOPS	50000	/* Use this for slow or 16 bit machines */ 
long LOOPS;
/*#define LOOPS	500000		/* Use this for faster machines */ 
 
 
/* Compiler dependent options */ 
#define	NOENUM			/* Define if compiler has no enum's */ 
#define	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) */ 
/*#define HZ	50		/* times(2) returns 1/50 second (europe?) */ 
#define HZ	60		/* times(2) returns 1/60 second (most) */ 
/*#define 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"; 
#endif 
 
 
#ifdef	NOSTRUCTASSIGN 
/* #define	structassign(d, s)	memcpy(&(d), &(s), sizeof(d)) */
#else 
#define	structassign(d, s)	d = s 
#endif 
 

#ifdef	NOENUM 
#define	Ident1	1 
#define	Ident2	2 
#define	Ident3	3 
#define	Ident4	4 
#define	Ident5	5 
#define Enumeration int
#else 
#define { enum
  Ident1, Ident2, Ident3, Ident4, Ident5 
} Enumeration; 
 
#endif 
 
#define register
#define OneToThirty int
#define OneToFifty int
#define CapitalLetter char
#define String30 char
#define Array1Dim int

#define RecordPtr long
#define RecordSize 41

struct Array2Dim {
  Array1Dim col[51];
};

struct Record { 
  RecordPtr PtrComp; 
  Enumeration Discr; 
  Enumeration EnumComp; 
  OneToFifty IntComp; 
  String30 StringComp[31]; 
}; 
 
 
#define boolean int
 
 
#define	NULL		0 
#define	TRUE		1 
#define	FALSE		0 
 
 
#ifndef REG 
#define	REG 
#endif 

 
extern Enumeration Func1(); 
extern boolean Func2(); 
 
 
#ifdef TIMES 
#include <sys/types.h> 
#include <sys/times.h> 
#endif 
 
 
main() 
{ 
  printf("Enter loop: ");
  scanf("%ld",&LOOPS);
  Proc0(); 
  exit(0); 
} 
 
 
/* Package 1  */ 
int IntGlob; 
boolean BoolGlob; 
char Char1Glob; 
char Char2Glob; 
Array1Dim Array1Glob[51]; 
struct Array2Dim Array2Glob[51]; 
struct Record* PtrGlb; 
struct Record* PtrGlbNext; 
 
 
Proc0() 
{ 
  long i;
  OneToFifty IntLoc1; 
  REG OneToFifty IntLoc2; 
  OneToFifty IntLoc3; 
  REG char CharLoc; 
  REG char CharIndex; 
  Enumeration EnumLoc; 
  String30 String1Loc[31]; 
  String30 String2Loc[31]; 
  /* extern char *malloc(); */
  /* register unsigned */ int i; 
 
 
#ifdef TIME 
  /* long time(); */
  long starttime; 
  long benchtime; 
  long nulltime; 
 
 
  starttime = date(); /* time((long *) 0); */
  for (i = 0; i < LOOPS; ++i); 
  nulltime = /* time((long *) 0) */ date() - 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 = malloc(RecordSize); 
  PtrGlb = malloc(RecordSize); 
  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].col[7] = 10;	/* Was missing in published program */ 
 
 
/***************** 
-- Start Timer -- 
*****************/ 
#ifdef TIME 
  starttime = date(); /* time((long *) 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); printf("2"); }
	IntLoc3 = IntLoc2 * IntLoc1; 
	IntLoc2 = IntLoc3 / IntLoc1; 
	IntLoc2 = 7 * (IntLoc3 - IntLoc2) - IntLoc1; 
	Proc2(&IntLoc1); 
  } 
 
 
/***************** 
-- Stop Timer -- 
*****************/ 
 
 
#ifdef TIME 
  benchtime = /* time((long *) 0) */ date() - starttime - nulltime; 
  printf("benchtime = %ld, starttime = %ld, nulltime = %ld\n",
         benchtime, starttime, nulltime);
  printf("Dhrystone(%s) time for %ld passes = %ld\n", 
         Version, 
         LOOPS, benchtime); 
  printf("This machine benchmarks at %ld dhrystones/second\n", 
         LOOPS / benchtime); 
#endif 
 
#ifdef TIMES 
  printf("Should not run\n");
  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 
 
 
} 
 
 
Proc1(PtrParIn) 
REG struct Record* PtrParIn; 
{ 
/* #define	NextRecord	(*(PtrParIn->PtrComp)) */
  struct Record* PtrTmp;
 
  /* structassign(NextRecord, *PtrGlb); */
  memcpy(PtrParIn->PtrComp, PtrGlb, RecordSize);
  PtrParIn->IntComp = 5; 
  PtrTmp = PtrParIn->PtrComp;
  PtrTmp->IntComp = PtrParIn->IntComp; 
  PtrTmp->PtrComp = PtrParIn->PtrComp; 
  Proc3(&(PtrTmp->PtrComp)); 
  if (PtrTmp->Discr == Ident1) { 
	PtrTmp->IntComp = 6; 
	Proc6(PtrParIn->EnumComp, &(PtrTmp->EnumComp)); 
	PtrTmp->PtrComp = PtrGlb->PtrComp; 
	Proc7(PtrTmp->IntComp, 10, &(PtrTmp->IntComp)); 
  } else 
	/* structassign(*PtrParIn, NextRecord); */
        memcpy(PtrParIn, PtrParIn->PtrComp, RecordSize);
 
 
/* #undef	NextRecord */
} 
 
 
Proc2(IntParIO) 
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; 
  } 
} 
 
 
Proc3(PtrParOut) 
long *PtrParOut; 
{ 
  if (PtrGlb != NULL) 
	*PtrParOut = PtrGlb->PtrComp; 
  else 
	IntGlob = 100; 
  Proc7(10, IntGlob, &(PtrGlb->IntComp)); 
} 
 
 
Proc4() 
{ 
  REG boolean BoolLoc; 
 
 
  BoolLoc = Char1Glob == 'A'; 
  BoolLoc |= BoolGlob; 
  Char2Glob = 'B'; 
} 
 
 
Proc5() 
{ 
  Char1Glob = 'A'; 
  BoolGlob = FALSE; 
} 
 
 
extern boolean Func3(); 
 
 
Proc6(EnumParIn, EnumParOut) 
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; 
} 
} 
 
 
Proc7(IntParI1, IntParI2, IntParOut) 
OneToFifty IntParI1; 
OneToFifty IntParI2; 
OneToFifty *IntParOut; 
{ 
  REG OneToFifty IntLoc; 
 
 
  IntLoc = IntParI1 + 2; 
  *IntParOut = IntParI2 + IntLoc; 
} 

 
Proc8(Array1Par, Array2Par, IntParI1, IntParI2) 
Array1Dim *Array1Par; 
struct 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].col[IntIndex] = IntLoc;
  ++Array2Par[IntLoc].col[IntLoc - 1]; 
  Array2Par[IntLoc + 20].col[IntLoc] = Array1Par[IntLoc]; 
  IntGlob = 5; 
} 
 
 
Enumeration Func1(CharPar1, CharPar2) 
CapitalLetter CharPar1; 
CapitalLetter CharPar2; 
{ 
  REG CapitalLetter CharLoc1; 
  REG CapitalLetter CharLoc2; 
 
 
  CharLoc1 = CharPar1; 
  CharLoc2 = CharLoc1; 
  if (CharLoc2 != CharPar2) 
	return(Ident1); 
  else 
	return(Ident2); 
} 
 
 
boolean Func2(StrParI1, StrParI2) 
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(EnumParIn) 
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 
But one of the oddities is in the nested for loop after the "Start Timer" comment. I don't see how the if statement:

Code: Select all

	for (CharIndex = 'A'; CharIndex <= Char2Glob; ++CharIndex) 
		if (EnumLoc == Func1(CharIndex, 'C')) 
			{ Proc6(Ident1, &EnumLoc); printf("2"); }   /* I added the printf */
...will ever be true. EnumLoc is always set to Ident2 a few statements before this code and CharIndex will only ever be 'A' or 'B' since Char2Glob is only ever set to 'B' in one place. Func1 returns Ident1 if its two parameters are different and since CharIndex is never 'C' that is always the case. So EnumLoc always set to Ident2 and Func1 always returning Ident1 means Proc6 there is never called (it's why I put a printf in there to check).

Does the Dhrystone code supposed to have non-called code in there? Is this a test of how well a compiler optimizes stuff like that out? I know that Dhrystone was designed to test the speed of a computer within the context of a particular C compiler it used to create the test.

Here is another doozy:

Code: Select all

Proc4() 
{ 
  REG boolean BoolLoc; 
 
  BoolLoc = Char1Glob == 'A'; 
  BoolLoc |= BoolGlob; 
  Char2Glob = 'B'; 
} 
So BoolLoc is local and it is computed to be either true or false, depending on what Char1Glob is. But, then nothing is ever done with it?!? Btw, this is the only place that Char2Glob, used in the questionable code above, is set.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: Dhrystone compiled on BBQL with Digital C SE

Post by bwinkel67 »

So indeed, that code is not supposed to execute. I found this more updated source on ghithub and it's been modified through the years to add more redundant code to prevent parts of generated code from being pruned out...plus heavily commented to talk about what's going on.

https://github.com/Keith-S-Thompson/dhr ... ces/dhry-c

I guess the point is that the code is supposed to represent the running of a somewhat normal program where some things execute and some don't. Presumably they didn't want to add random variations to it, so that all branches would get executed, to get as precise a comparison as possible.

Now I just need to fix my bug (the 3 for loops running on LOOPS variable) and figure out why the dang thing runs so slow...


User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: Dhrystone compiled on BBQL with Digital C SE

Post by bwinkel67 »

Well, after a ton of debugging, I finally got it to work. It was actually working for a while but I needed to debug it to make sure it was running correctly. The previous github link foretold what each procedure/function should do and which parts should not execute. I stuck a printf into each section that shouldn't run (printing BAD) and output what each value for parameters should be:
debug.png
Part of the problem was how slowly it was running. The German benchmark program (Bench.zip from Dilwyn's site) ran at 208 Dhrystone on QLAY configured to be about real-time BBQL (less than 10% faster). The German version was compiled under Lattice C (I'm assuming that means it was cross compiled - EDIT: it's the Metacomco version that requires a ROM to work). My Digital C SE ran at 79 Dhrystones, so very slow.

I discovered that my dataspace was too small (8000) and the sweet spot was 18,000. The German version has a dataspace of 11,374 (so Lattice C must compute the actual dataspace size -- I've tried different values and even at 17,950 it slows down a bit, so 18,000 seems to be just right). Interestingly, the German version executable is 17.5K while Digital C SE's was about 10K so, with dataspace, both were about equal in size. I'm not sure why dataspace has to be so large (I guess I don't fully understand what dataspace is for). The biggest structure in the code is a 2D array of 5200 bytes and then just a bunch of singular variables and no recursion. Nothing that would amount to another 5K (or 12K for Digital C SE). My ZXSimultor works just fine in 8K database, though it mostly lives in the heap.

So with larger dataspace my version sped up form 79 Dhrystones to 89 Dhrystones. I then discovered a small piece of code for structure assignment that's put into place if the language does't allow for one structure to be simply assigned to another (i.e. some languages allow s1 = s2 and it copies all fields fully). Here is the code:

Code: Select all

memcpy(d, s, l) 
register char *d; 
register char *s; 
register int l; 
{ 
  while (l--) *d++ = *s++; 
} 
I replaced that with a specific structure assignment code:

Code: Select all

structassign(d, s)
struct Record* d;
struct Record* s;
{
  d->PtrComp = s->PtrComp; 
  d->Discr = s->Discr; 
  d->EnumComp = s->EnumComp; 
  d->IntComp = s->IntComp; 
  strcpy(d->StringComp,s->StringComp); 
}
I figured that was fair since the Dhrysone_c code did have an option to use the fastest way for the language to do those type of assignments. Well, this improved the runtime to 175 Dhrystones (i.e basically doubled the speed). Still not close, but definitely getting better. I did run the German version on my actual QL and it ran at 190 Dhrystones and I'm going to guess my Digital C SE version is going to come in at close to 160 Dhrystones (if the ratio holds out). So Digital C SE isn't as efficient as it could be.

Not sure where the issue is but some things can't be improved upon...i.e. I know array indexing can be a bit sloppy in Digital C SE but that needs to stay put for the Dhrystone measure to be an accurate representation (I can't replace it with direct pointer manipulation, which I know is faster and I've done in ZXSimulator).

It's been fun learning about what Dhrystone does...feel a bit more fluent about it now. Version 2.1 has some changes that don't impact the runtime but it's interesting to read/understand why it was done. I will follow up with a zip file of the Digital C compiled version, once I get it to where I want it.


User avatar
tofro
Font of All Knowledge
Posts: 2685
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: Dhrystone compiled on BBQL with Digital C SE

Post by tofro »

bwinkel67 wrote: I discovered that my dataspace was too small (8000) and the sweet spot was 18,000. The German version has a dataspace of 11,374 (so Lattice C must compute the actual dataspace size -- I've tried different values and even at 17,950 it slows down a bit, so 18,000 seems to be just right). Interestingly, the German version executable is 17.5K while Digital C SE's was about 10K so, with dataspace, both were about equal in size. I'm not sure why dataspace has to be so large (I guess I don't fully understand what dataspace is for). The biggest structure in the code is a 2D array of 5200 bytes and then just a bunch of singular variables and no recursion. Nothing that would amount to another 5K (or 12K for Digital C SE). My ZXSimultor works just fine in 8K database, though it mostly lives in the heap.

So with larger dataspace my version sped up form 79 Dhrystones to 89 Dhrystones. I then discovered a small piece of code for structure assignment that's put into place if the language does't allow for one structure to be simply assigned to another (i.e. some languages allow s1 = s2 and it copies all fields fully). Here is the code:
Data space has not much to do with speed - programs use it to store data (hence the name ;) ) and how they can organize the data may, however, affect the speed they can run in.

Typically, the data space holds the stack, the uninitialized data (udata) and the initialized data (data) of a compiled C program. udata and data mainly consist of any variables that are declared outside any function, like global and static variables. Anything that is declared within a function (and alloc()ed data) and not declared static goes onto the stack - with a lifetime associated to the function actually being part of the current call hierarchy (so: stack variables do take data space - or stack, which is part of it, but only temporarily - but if you have a lot of deeply nested functions in your program, you will use more data space, because - well, lifetime).

Some compilers will actually put even malloced data into the data space. Metacomco C doesn't, I don't know Digital C well enough to be able to determine. Having more data space may actually speed up such programs, because the memory management functions simply have more elbow room to work with which might affect the time needed to search for free space.

In short: changing the size of the data space shouldn't affect the speed of the program - what it might affect, however, is it might cause the program to run in an incorrect way - because the variables are allocated at the start of the data space, while the stack grows from its opposite end downwards, it might happen that the stack actually overwrites data towards the beginning of the data space - All sorts of havoc might happen if this occurs. (And I guess what you have seen is "light havoc" in your program)


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: Dhrystone compiled on BBQL with Digital C SE

Post by bwinkel67 »

Yeah, it was weird that the program slowed down and you could actually measure it as it got to that 18,000 threshold. At 17,800 it took slightly more time, and at 17,700 it took a bit more, etc. It may be an issue with with QLAY. The data space sizing had no effect when I allocated 640K of memory for the emulator but at 128K is when I saw the slow down.

I would have guessed that if the data space was too small the program would either crash or complain it was out of memory. I plan on trying it on my actual QL but won't get to it until Wednesday of this coming week. Am also going to try another emulator to see if it repeats.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: Dhrystone compiled on BBQL with Digital C SE

Post by bwinkel67 »

Fixed the dataspace issue, it actually runs fine in the default 8000. So the German version compiled with Metacomco (on Dilwyn's site as bench.zip) didn't have #include stdio_h, which is pretty standard. For Digital C SE, it's necessary because any of the non int functions (i.e. all the long ones) are declared in there. So malloc() was wrongly getting its long address cut off as 16 bit since C saw it as an int (this is pretty standard old-school C). The German source, instead of including stdio_h, had an extern reference to malloc():

Code: Select all

extern char* malloc();
...which says that it returns an address to byte memory...again, same as Digital C SE, which instead declares it as (it could have done the same but it seems the Digital Precision folks just looked at it practically as, "hey, it needs to be 32 bits so make it long"):

Code: Select all

long malloc();
If you don't know C, when a function is not prototyped (declared), its return type is assumed to be int...which is 16 bits on the QL. So the malloc() address was being re-mapped, presumably, into the dataspace and screwing things up. That's why it likely worked when I gave it a lot more memory, but in the 128K model it clashed.

Turns out that QL2K (the updated QLAY emulator) adds debugging which flags bad memory accesses...that's how I found it. I also realize now why I don't use QL2K...it's MDV reading is abysmal as it takes forever to read anything from an MDV image.


User avatar
tofro
Font of All Knowledge
Posts: 2685
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: Dhrystone compiled on BBQL with Digital C SE

Post by tofro »

bwinkel67 wrote: If you don't know C, when a function is not prototyped (declared), its return type is assumed to be int...which is 16 bits on the QL. So the malloc() address was being re-mapped, presumably, into the dataspace and screwing things up. That's why it likely worked when I gave it a lot more memory, but in the 128K model it clashed.
That's unlikely. An absolute 16-bit pointer points into ROM space on an original QL (or way up into unreachable space when it's sign-expanded). It could, however, be your code shot the ROM on the emulator. But a crash would even be more likely, then. Does QL2K protect the ROM?


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: Dhrystone compiled on BBQL with Digital C SE

Post by bwinkel67 »

tofro wrote:That's unlikely. An absolute 16-bit pointer points into ROM space on an original QL (or way up into unreachable space when it's sign-expanded). It could, however, be your code shot the ROM on the emulator. But a crash would even be more likely, then. Does QL2K protect the ROM?
I don't think QL2K protects the ROM but it flags when an address is not proper (i.e. say it tries to hit the ROM area). That seemed to be happening. With regard to the dataspace issue, I don't know what was going on.
Last edited by bwinkel67 on Mon Jul 25, 2022 11:04 am, edited 1 time in total.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: Dhrystone compiled on BBQL with Digital C SE

Post by bwinkel67 »

Update on the Digital C SE version of Dhrystone. It has gotten a bit closer with a realtime run on QL2K giving 166 with Digital C SE, compared to the 179 for the Metacomco C version. I did have it up to 167 but I kind of felt I cheated so I undid that change.

I have made a few optimizations that I feel are legitimate (i.e. I'm not cheating but doing stuff a good compiler would do). Some examples:

Code: Select all

b = c;
a = b;
I re-wrote to:

Code: Select all

a = b = c;
The hope is that, after computing b=c, the code the compiler generates keeps that result and assigns a. Doesn't seem like an improvement, except there is subscripting in play:

Code: Select all

b[i] = c;
a[i+1] = b[i];
...so trying to avoid computing the subscript for b twice because Digital C SE doesn't do that efficiently:

Code: Select all

a[i+1] = b[i] = c;
...which seems to have worked as it sped up. I'm hoping that it reuses the address for b when computing and assigning a[i+1] and thus you save on computation. It's small but since you run it 50,000 times it adds up. Seemed to have saved 2 seconds. I need to disassemble the code to better understand what code is generated in each case.

Btw, the cheating way would be to just do this:

Code: Select all

b[i] = c;
a[i+1] = c;
...since I'm forcibly getting rid of a subscript and now assigning c directly to a[i+1] as opposed to assigning b to a[i+1] (i.e. I want the compiler to do it not me...but I may resort to that eventually since that gave me that slightly better value of 167).


Another simple one was:

Code: Select all

for (i = 0; i <= (j+1); i++)
...changed to this:

Code: Select all

for (i = 0; i < j; i++)

And my favorite, that I've done back in the 90's. So from this:

Code: Select all

func1(n)
int n;
{
   int i;
   
   while (i<10) {
      if (i == n) break;
         i++;
   }
}
...to this

Code: Select all

func1(n)
int n;
{
   int i;
   
   while (i<10) {
      if (i == n) return;
         i++;
   }
}
...this avoids the break jumping to the end of the loop to then jump again for the implicit return.

I may have to eventually cheat as I want to really get to that 179 mark so I can develop a nicer user interface for Dhrystone and have it check some other stuff as well. Don't really want to have a speed check that is inherently slower than another program out there.

So the source has local variables set up with the register directive (REG), which suggests that some of the older compilers can assign a register to a specific variable. I imagine that would speed things up. Unfortunately Digital C SE doesn't give that feature. I'm guessing Metacomco C does? That's got to be how it is still faster.
Last edited by bwinkel67 on Mon Jul 25, 2022 11:29 am, edited 3 times in total.


User avatar
bwinkel67
QL Wafer Drive
Posts: 1187
Joined: Thu Oct 03, 2019 2:09 am

Re: Dhrystone compiled on BBQL with Digital C SE

Post by bwinkel67 »

Oh, and here is the slightly cleaned up, latest version in case anyone else wants to see what could be improved without breaking any major rules (i.e. no re-ordering of code, etc...)

Code: Select all

/* dhrystone - benchmark program */ 
 
#include stdio_h
 
#define REGISTER 
/* 
 * 
 *   "DHRYSTONE" Benchmark Program 
 * 
 *   Version:     C/1.1, 12/01/84 
 * 
 *   Date:        PROGRAM updated 01/06/86, COMMENTS changed 01/31/87 
 * 
 *   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.  Be careful to 
 *                adjust the HZ parameter below for the units which 
 *                are returned by your times(2) function.  You can 
 *                sometimes find this in <sys/param.h>.  If you have 
 *                neither time(2) nor times(2), use a stopwatch in 
 *                the dead of the 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. 
 * 
 * 
 *   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 */ 
#define LOOPS 50000              /* Use this for slow or 16 bit machines */ 
/*#define LOOPS 500000           /* Use this for faster machines */ 
/* long LOOPS; */
 
 
/* Compiler dependent options */ 
#define NOENUM                   /* Define if compiler has no enum's */ 
/*#define 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) */ 
/*#define HZ 50                  /* times(2) returns 1/50 second (europe?) */ 
/*#define HZ 60                  /* times(2) returns 1/60 second (most) */ 
/*#define 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"; 
#endif 
 
 
#ifdef NOSTRUCTASSIGN 
#define structassign(d, s)   memcpy(&(d), &(s), sizeof(d))
#else 
/* #define structassign(d, s)   d = s */
#endif 
 

#ifdef NOENUM 
#define Ident1 1 
#define Ident2 2 
#define Ident3 3 
#define Ident4 4 
#define Ident5 5 
#define Enumeration int
#else 
#define { enum
  Ident1, Ident2, Ident3, Ident4, Ident5 
} Enumeration; 
 
#endif 
 
#define register
#define OneToThirty int
#define OneToFifty int
#define CapitalLetter char
#define String30 char
#define Array1Dim int

#define RecordPtr long
#define RecordSize 41

struct Array2Dim {
  Array1Dim col[51];
};

struct Record { 
  RecordPtr PtrComp; 
  Enumeration Discr; 
  Enumeration EnumComp; 
  OneToFifty IntComp; 
  String30 StringComp[31]; 
}; 
 
 
#define boolean int
 
 
/* #define NULL 0 */
#define TRUE  1 
#define FALSE 0 
#define BREAK return 
 
 
#ifndef REG 
#define REG 
#endif 

 
extern Enumeration Func1(); 
extern boolean Func2(); 
 
 
#ifdef TIMES 
#include <sys/types.h> 
#include <sys/times.h> 
#endif 
 
 
main() 
{ 
   printf("Enter loops: ");
   /* scanf("%ld",&LOOPS); */
   printf("%ld\n", LOOPS);
   Proc0(); 
   exit(0); 
} 
 
 
/* Package 1  */ 
int IntGlob; 
boolean BoolGlob; 
char Char1Glob; 
char Char2Glob; 
Array1Dim Array1Glob[51]; 
struct Array2Dim Array2Glob[51]; 
struct Record* PtrGlb; 
struct Record* PtrGlbNext; 
 
 
Proc0() 
{
   OneToFifty IntLoc1; 
   REG OneToFifty IntLoc2; 
   OneToFifty IntLoc3; 
   REG char CharLoc; 
   REG char CharIndex; 
   Enumeration EnumLoc; 
   String30 String1Loc[31]; 
   String30 String2Loc[31]; 
   /* extern char *malloc(); */
   /* register unsigned */ long i; 
 
 
#ifdef TIME 
   /* long time(); */
   long starttime; 
   long benchtime; 
   long nulltime; 
  
   starttime = date(); /* time((long *) 0); */
   for (i = 0; i < LOOPS; ++i); 
   nulltime = /* time((long *) 0) */ date() - 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 = malloc(RecordSize); 
   PtrGlb = malloc(RecordSize); 
   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].col[7] = 10;   /* Was missing in published program */ 
 
 
/***************** 
-- Start Timer -- 
*****************/ 
#ifdef TIME 
   starttime = date(); /* time((long *) 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((long *) 0) */ date() - starttime - nulltime; 

   printf("benchtime = %ld, starttime = %ld, nulltime = %ld\n",
          benchtime, starttime, nulltime);
   printf("Dhrystone(%s) time for %ld passes = %ld\n", 
          Version,
          LOOPS, benchtime); 
   printf("This machine benchmarks at %ld dhrystones/second\n", 
          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 
 
 
} 
 
 
Proc1(PtrParIn) 
REG struct Record* PtrParIn; 
{ 
   /* #define NextRecord   (*(PtrParIn->PtrComp)) */
   struct Record* PtrTmp;
 
 
   structassign(PtrParIn->PtrComp, PtrGlb);
   PtrParIn->IntComp = 5; 
   PtrTmp = PtrParIn->PtrComp;
   PtrTmp->IntComp = PtrParIn->IntComp; 
   PtrTmp->PtrComp = PtrParIn->PtrComp; 
   Proc3(&PtrTmp->PtrComp); 
   if (PtrTmp->Discr == Ident1) { 
      PtrTmp->IntComp = 6; 
      Proc6(PtrParIn->EnumComp, &PtrTmp->EnumComp); 
      PtrTmp->PtrComp = PtrGlb->PtrComp; 
      Proc7(PtrTmp->IntComp, 10, &PtrTmp->IntComp); 
   } else 
      structassign(PtrParIn, PtrParIn->PtrComp);
 
   /* #undef   NextRecord */
} 
 
 
Proc2(IntParIO) 
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; 
   }
} 
 
 
Proc3(PtrParOut) 
long *PtrParOut; 
{ 
   if (PtrGlb != NULL) 
      *PtrParOut = PtrGlb->PtrComp; 
   else 
      IntGlob = 100; 
   Proc7(10, IntGlob, &PtrGlb->IntComp); 
} 
 
 
Proc4() 
{ 
   REG boolean BoolLoc; 
 
 
   BoolLoc = Char1Glob == 'A'; 
   BoolLoc |= BoolGlob; 
   Char2Glob = 'B'; 
} 
 
 
Proc5() 
{ 
   Char1Glob = 'A'; 
   BoolGlob = FALSE; 
} 
 
 
extern boolean Func3(); 
 
 
Proc6(EnumParIn, EnumParOut) 
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; 
  } 
} 
 
 
Proc7(IntParI1, IntParI2, IntParOut) 
OneToFifty IntParI1; 
OneToFifty IntParI2; 
OneToFifty *IntParOut; 
{ 
   REG OneToFifty IntLoc; 
 
 
   IntLoc = IntParI1 + 2; 
   *IntParOut = IntParI2 + IntLoc; 
} 

 
Proc8(Array1Par, Array2Par, IntParI1, IntParI2) 
Array1Dim *Array1Par; 
struct 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 + 1] = Array1Par[IntLoc] = IntParI2; /* helps optimize */

   Array1Par[IntLoc + 30] = IntLoc; 
   for (IntIndex = IntLoc; IntIndex < IntLoc; ++IntIndex) 
      Array2Par[IntLoc].col[IntIndex] = IntLoc;
   ++(Array2Par[IntLoc].col[IntLoc - 1]); 
   Array2Par[IntLoc + 20].col[IntLoc] = Array1Par[IntLoc]; 
   IntGlob = 5; 
} 
 
 
Enumeration Func1(CharPar1, CharPar2) 
CapitalLetter CharPar1; 
CapitalLetter CharPar2; 
{ 
   REG CapitalLetter CharLoc1; 
   REG CapitalLetter CharLoc2; 
 
 
   CharLoc1 = CharPar1; 
   CharLoc2 = CharLoc1; 
   if (CharLoc2 != CharPar2) 
      return(Ident1); 
   else 
      return(Ident2); 
} 
 
 
boolean Func2(StrParI1, StrParI2) 
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') if (CharLoc <= 'Z') IntLoc = 7; 
   if (CharLoc == 'X') 
      return(TRUE); 
   else { 
      if (strcmp(StrParI1, StrParI2) > 0) { 
         IntLoc += 7; 
         return(TRUE); 
      } else 
         return(FALSE); 
   } 
} 
 
 
boolean Func3(EnumParIn) 
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++; 
} 
#else
structassign(d, s)
struct Record* d;
struct Record* s;
{
   d->PtrComp = s->PtrComp; 
   d->Discr = s->Discr; 
   d->EnumComp = s->EnumComp; 
   d->IntComp = s->IntComp; 
   strcpy(d->StringComp,s->StringComp); 
}
#endif 


Post Reply