Maker Pro
Maker Pro

Writing Scalabe Software in C++

S

Skybuck Flying

Hello,

This morning I had an idea how to write Scalable Software in general.
Unfortunately with Delphi 2007 it can't be done because it does not support
operating overloading for classes, or record inheritance (records do have
operator overloading)

The idea is to write a generic integer class with derived integer classess
for 8 bit, 16 bit, 32 bit, 64 bit and 64 bit emulated.

Then at runtime the computer program can determine which derived integer
class is needed to perform the necessary calculations.

The necessary integer class is instantiated and assigned to a generic
integer class variable/reference and the generic references/variables are
used to write the actual code that performs the calculations.

Below is a demonstration program, it's not yet completely compiling, but
it's getting close.

// TestWritingScalableSoftware.cpp : Defines the entry point for the console
application.
//
#include "stdafx.h"

class TSkybuckGenericInteger
{


};
class TSkybuckInt32 : public TSkybuckGenericInteger
{
private:
int mInteger;

public:

// constructor with initializer parameter
TSkybuckInt32( int ParaValue );

// add operator overloader
TSkybuckInt32& operator+( const TSkybuckInt32& ParaSkybuckInt32 );

void Display();
};

class TSkybuckInt64 : TSkybuckGenericInteger
{
private:
long long mInteger;

public:
// constructor with initializer parameter
TSkybuckInt64( long long ParaValue );

// add operator overloader
TSkybuckInt64& operator+( const TSkybuckInt64& ParaSkybuckInt64 );

void Display();
};

//
// TSkybuckInt32

// constructor
TSkybuckInt32::TSkybuckInt32( int ParaValue )
{
mInteger = ParaValue;
}

// add operator overloader
TSkybuckInt32& TSkybuckInt32::eek:perator+ ( const TSkybuckInt32&
ParaSkybuckInt32 )
{
mInteger = mInteger + ParaSkybuckInt32.mInteger;
return *this;
}

void TSkybuckInt32::Display()
{
printf( "%d \n", mInteger );
}

//
// TSkybuckInt64
//
// constructor
TSkybuckInt64::TSkybuckInt64( long long ParaValue )
{
mInteger = ParaValue;
}

// add operator overloader
TSkybuckInt64& TSkybuckInt64::eek:perator+ ( const TSkybuckInt64&
ParaSkybuckInt64 )
{
mInteger = mInteger + ParaSkybuckInt64.mInteger;
return *this;
}

void TSkybuckInt64::Display()
{
printf( "%lu \n", mInteger );
}

int _tmain(int argc, _TCHAR* argv[])
{
long long FileSize;
long long MaxFileSize32bit;

// must write code like this to use constructor ? can't just declare
a,b,c ?
TSkybuckInt32 A32 = TSkybuckInt32( 30 );
TSkybuckInt32 B32 = TSkybuckInt32( 70 );
TSkybuckInt32 C32 = TSkybuckInt32( 0 );
C32 = A32 + B32;
C32.Display();

TSkybuckInt64 A64 = TSkybuckInt64( 30 );
TSkybuckInt64 B64 = TSkybuckInt64( 70 );
TSkybuckInt64 C64 = TSkybuckInt64( 0 );
C64 = A64 + B64;
C64.Display();

FileSize = 1024; // kilobyte
FileSize = FileSize * 1024; // megabyte
FileSize = FileSize * 1024; // gigabyte
FileSize = FileSize * 1024; // terrabyte

MaxFileSize32bit = 1024; // kilobyte
MaxFileSize32bit = MaxFileSize32bit * 1024; // megabyte
MaxFileSize32bit = MaxFileSize32bit * 1024; // gigabyte
MaxFileSize32bit = MaxFileSize32bit * 4; // 4 gigabyte
if (FileSize < MaxFileSize32bit)
{
TSkybuckGenericInteger AGeneric = TSkybuckInt32( 30 );
TSkybuckGenericInteger BGeneric = TSkybuckInt32( 70 );
TSkybuckGenericInteger CGeneric = TSkybuckInt32( 0 );
} else
{
TSkybuckGenericInteger AGeneric = TSkybuckInt64( 30 );
TSkybuckGenericInteger BGeneric = TSkybuckInt64( 70 );
TSkybuckGenericInteger CGeneric = TSkybuckInt64( 0 );
}

CGeneric = AGeneric + BGeneric;
CGeneric.Display();

while (1)
{
}

return 0;
}

Probably minor compile issue's remain:

Error 1 error C2243: 'type cast' : conversion from 'TSkybuckInt64 *__w64 '
to 'const TSkybuckGenericInteger &' exists, but is inaccessible
y:\cpp\tests\test writing scalable software generic math\version
0.01\testwritingscalablesoftware\testwritingscalablesoftware\testwritingscalablesoftware.cpp
152

Error 2 error C2243: 'type cast' : conversion from 'TSkybuckInt64 *__w64 '
to 'const TSkybuckGenericInteger &' exists, but is inaccessible
y:\cpp\tests\test writing scalable software generic math\version
0.01\testwritingscalablesoftware\testwritingscalablesoftware\testwritingscalablesoftware.cpp
153

Error 3 error C2243: 'type cast' : conversion from 'TSkybuckInt64 *__w64 '
to 'const TSkybuckGenericInteger &' exists, but is inaccessible
y:\cpp\tests\test writing scalable software generic math\version
0.01\testwritingscalablesoftware\testwritingscalablesoftware\testwritingscalablesoftware.cpp
154

Error 4 error C2065: 'CGeneric' : undeclared identifier y:\cpp\tests\test
writing scalable software generic math\version
0.01\testwritingscalablesoftware\testwritingscalablesoftware\testwritingscalablesoftware.cpp
157

Error 5 error C2065: 'AGeneric' : undeclared identifier y:\cpp\tests\test
writing scalable software generic math\version
0.01\testwritingscalablesoftware\testwritingscalablesoftware\testwritingscalablesoftware.cpp
157

Error 6 error C2065: 'BGeneric' : undeclared identifier y:\cpp\tests\test
writing scalable software generic math\version
0.01\testwritingscalablesoftware\testwritingscalablesoftware\testwritingscalablesoftware.cpp
157

How to solve the remaining issue's ?

Bye,
Skybuck.
 
S

Skybuck Flying

For those that missed the other threads here is the explanation why I want
something like this:

For 32 bit compilers:

int (32 bit signed integer) is fast, it's translated to single 32 bit cpu
instructions.

long long (64 bit signed integer) is slow, it's translated to multiple 32
bit cpu instructions.

For 64 bit compilers

long long (64 bit signed integer) should be fast, it's translated to a
single 64 bit cpu instruction.

I want to write code just once ! not three times ! and I want maximum speed
!

So I need a generic integer class which will use the appriorate class, the
program must decide what's necessary at runtime, and still give good
performance !

I believe/hope the provided example after some minor fixes should be able to
do what I want ;) !

Bye,
Skybuck.
 
S

Skybuck Flying

Well, since the code doesn't compile yet I can't look at the asm generated
but now that I think about it...

Maybe C++ does like polymorhpic or virtual function stuff for compileable
code and that might introduce more overhead than it's worth.

Remains to be seen.

Bye,
Skybuck.
 
M

MooseFET

Hello,

This morning I had an idea how to write Scalable Software in general.
Unfortunately with Delphi 2007 it can't be done because it does not support
operating overloading for classes, or record inheritance (records do have
operator overloading)

This argument is wrong in two ways. It assumes things that are not
true and then draws conclusions that don't follow.

Delphi implements objects, and virtual methods. Any language that has
these features is able to operate on values where the type is not
known at compile time.


On the other hand neither this nor what you included below will do
what you started off trying to suggest. They are just methods by
which different instructions can be used at a point in the logic flow
depending on the sort of variable under consideration.
 
S

Skybuck Flying

Well, if this is the case I might adjust my code as follows:

TgenericInteger = int32;

or later

TgenericInteger = int64;

Then all code that doesn't really care about the number of bits can be
written with TgenericInteger.

All integers replaced with TgenericInteger.

some code that might need upper or lower bits might use:

THalfGenericInteger = .. whatever.

One last problem remains.

Suppose a program had multiple modules.

One module might remain 32 bit.

One module might be 64 bit.

Both modules use a third module.

Now there is a problem.

The third module can't be 32 bit and 64 bit at the same time by simply
changing a TgenericInteger type.

That kinda sux.

Not to mention the reduced performance for using 64 bit emulation where 32
bit would have been enough at runtime.

Stinky !

Final possible solution turn as much modules as possible into slower 64 bit
emulated modules... later recompile for 64 bit when 64 bit compiler/ide
available, maybe that already possible.

Minor code adjustments needed: just a few locations to change
TgenericInteger...

Instead of having to look at all the fucking code and change integers
everywhere BLEH !

Bye,
Skybuck.
 
S

Skybuck Flying

Yeah and possibly:

TdoubleGenericInteger = int128; // implemented manually.

These doubles could be used to detected generic integer overflows, range
check errors and other kinds of problems.

Bye,
Skybuck.
 
S

Skybuck Flying

MooseFET said:
This argument is wrong in two ways. It assumes things that are not
true and then draws conclusions that don't follow.

Delphi implements objects, and virtual methods. Any language that has
these features is able to operate on values where the type is not
known at compile time.

Without the mentioned features writing scalable software, including writing
scalable math routines becomes impractical.

Even with virtual methods it would become slow.
On the other hand neither this nor what you included below will do
what you started off trying to suggest. They are just methods by
which different instructions can be used at a point in the logic flow
depending on the sort of variable under consideration.

It does exactly what I want it to do, it does it slowly, so it's not what I
want it to do.

Bye,
Skybuck.
 
S

Skybuck Flying

Somebody else also had an interesting idea:

It comes down to this:

1. Generate multiple libraries, for example:

32 bit version
true 64 bit version
emulated 64 bit version

2. It might have some problems:

Problem 1: parameters for routine are different.
Problem 2: calling for routines are different because of parameters.
Problem 3: debugging problem, different libraries same source <- can't be,
source was slightly modified for each generate library.

These problems could make finding a solution more complex.

It does solve another problem:

Different parts of the application can have different versions.

This idea is definetly worth exploring.

Bye,
Skybuck.
 
S

Skybuck Flying

Problem 4:

Distribution size grows considerable.

3 Different libraries must be supplied.

Only one library has to be loaded.

Maybe two if different parts required it.

Biggest problem:

The debugging problem.

That's what I don't like about it.

Debugging very important for me.

How can different libraries be debugged with the same code ? Where only one
declartion is different, it was modified during the build ?

Strange.

Bye,
Skybuck.
 
S

Stephen Sprunk

Skybuck Flying said:
For those that missed the other threads here is the explanation why I want
something like this:

For 32 bit compilers:

int (32 bit signed integer) is fast, it's translated to single 32 bit cpu
instructions.

long long (64 bit signed integer) is slow, it's translated to multiple 32
bit cpu instructions.

What benchmark are you using that shows it's "slow"? How much is the
supposed performance hit vs 32-bit compared to the overall program program
execution time?
For 64 bit compilers

long long (64 bit signed integer) should be fast, it's translated to a
single 64 bit cpu instruction.

I want to write code just once ! not three times ! and I want maximum
speed !

Those things rarely go together. You can have good, fast, and cheap -- but
only two at a time.

S
 
S

Stephen Sprunk

Skybuck Flying said:
Without the mentioned features writing scalable software, including
writing scalable math routines becomes impractical.

Even with virtual methods it would become slow.
Indeed.


It does exactly what I want it to do, it does it slowly, so it's not what
I want it to do.

That's why I told you that checking for whether emulation is needed and
picking a code path will be slower than just using it all the time. The
cost of emulation, unless you're writing incredibly math-intensive code, is
trivial. If you care about raw math performance and the code is just too
slow to use, buying a faster CPU will be cheaper than trying to figure out
how to make slow machines perform better. It's certainly cheaper than
modifying the ISA.

S
 
M

mpm

This argument is wrong in two ways. It assumes things that are not
true and then draws conclusions that don't follow.

Delphi implements objects, and virtual methods. Any language that has
these features is able to operate on values where the type is not
known at compile time.

On the other hand neither this nor what you included below will do
what you started off trying to suggest. They are just methods by
which different instructions can be used at a point in the logic flow
depending on the sort of variable under consideration.

IMO, this Skybuck poster is whacked. Mentally so.

I'll wager that if you just parse his code and remove all occurances
of the letters "skybuck" you'll discover its someone else's work and
he's just inserted his jibberish to make himself feel more
important. Probably right out of a help file or compiler manual or
something.

I can practically guarantee you he did not write any of this code.
-which of course would explain why it doesn't even do what he claims
it's supposed to do.

And I've never me the guy (girl?, it?, whatever Skybuck is).
 
B

Bo Persson

Skybuck Flying wrote:
:: For those that missed the other threads here is the explanation
:: why I want something like this:
::
:: For 32 bit compilers:
::
:: int (32 bit signed integer) is fast, it's translated to single 32
:: bit cpu instructions.
::
:: long long (64 bit signed integer) is slow, it's translated to
:: multiple 32 bit cpu instructions.
::
:: For 64 bit compilers
::
:: long long (64 bit signed integer) should be fast, it's translated
:: to a single 64 bit cpu instruction.

int (which might be 32 bit) is also fast, it's translated to single 32
bit cpu instruction.


Bo Persson
 
S

Skybuck Flying

Well we can pretty safely forget about this "solution".

It's not really a solution.

The problem is with the data.

Different data types are needed.

32 bit data and 64 bit data.

Trying to cram those into one data type not possible.

Not with classes, not with records, maybe variants but those to slow.

If you do try you will run into all kinds of problems, code problems.

It was an interesting experience though.

I played around with DLL's then Packages. LoadPackage, UnloadPackage,
Tpersistent (Delphi stuff) then I realized let's just copy & paste the code
and try to use unit_someversion.TsomeClass but nope.

The problem with the data remains.

I really do want one data type to perform operations on, and this data type
should scale when necessary.

I want one code on this data type and should change when necessary.

It looks simply to do but currently in Delphi it's probably impossible to do
it fast, even slow versions create problems.

The best solution is probably my own solution:

TgenericInteger = record
mInteger : int64;
end;

Overloaded add operator:
if BitMode = 32 then
begin
int32(mInteger) := int32(mInteger) + etc;
end else
if BitMode = 64 then
begin
int64(mInteger) := int64(mInteger) + etc;
end;

Something like that.

Introduces a few if statements... which is overhead...

Question remains, how much overhead is it really ?

Yeah good question:

Which one is faster:

add
adc

Or:
mov al, bitmode
cmp al, 32
jne lab1
add eax, ecx
lab1:
cmp al, 64
jne lab2
add eax, ecx
adc eax, ecx
lab2:

Something like that...

Well I think always executing add, adc is faster then the compares and jumps
:) LOL.

End of story ? Not yet... this is simple example... what about mul and div ?
<- those complex for int64 emulated.

Maybe using if statement to switch to 32 bit when possible would be much
faster after all ?!

Bye,
Skybuck.
 
S

Skybuck Flying

Well I just had an idea which might be interesting after all:

64 bit emulated mul and div are probably slow.

So if it's possible to switch to 32 bit maybe some speed gains can be
achieved !

So for addition and subtraction the 64 bit emulated versions are always
called.

But for multiplication and division the 32 bit version might be called when
possible and the 64 bit emulated version when absolutely necessary.

I shall inspect what Delphi does for 64 bit (<-emulated) multiplication and
division ;)

Bye,
Skybuck.
 
S

Skybuck Flying

Oh shit.

I was wondering who added alt.math.

But it was me LOL.

I added the wrong newsgroup.

I wanted to add alt.lang.asm.

Oh well

I ll start new thread there and post a question about this ;)

Bye,
Skybuck.
 
S

Skybuck Flying

Ok,

I fixed the code somewhat.

I don't completely understand all the syntax shit and such, simply followed
some other code examples.

The 3 lines which are commented probably need a type conversion or
something.

I changed the operators to be virtual, now they can be overloaded.

That's kinda interesting/cool... virtual overloaded operators.

However: IT'S HELLISH SLOW when looking at the assembler.

It might not be as slow as delphi's dynamic array referencing count and such
but still.. (however not fair to compare... because this c+= example ain't
dynamic infinite scalable ;))

Way to slow to be usualable for my purposes.

It was interesting to see C++ virtual overloaded operators in action...
maybe making the operators virtual would not be necessary if other tricks
used ? I am not good enough in C++ to try other tricks.

I also explored some other ideas, non of which are statisfieng.

Really, really sad.

I will probably have to convert my code to 64 bit emulated ints and say
goodbye to performance for 32 bit cases !

Here is the fixed up code:

// TestWritingScalableSoftware.cpp : Defines the entry point for the console
application.
//
#include "stdafx.h"

class TSkybuckGenericInteger
{
public:
virtual TSkybuckGenericInteger& operator+( const TSkybuckGenericInteger&
ParaSkybuckGenericInteger );
virtual void Display();
};
class TSkybuckInt32 : public TSkybuckGenericInteger
{
private:
int mInteger;
public:
// constructor with initializer parameter
TSkybuckInt32( int ParaValue );
// first solution:
virtual TSkybuckInt32& operator+( const TSkybuckInt32& ParaSkybuckInt32 );
virtual void Display();
};
class TSkybuckInt64 : TSkybuckGenericInteger
{
private:
long long mInteger;
public:
// constructor with initializer parameter
TSkybuckInt64( long long ParaValue );
// first solution:
virtual TSkybuckInt64& operator+( const TSkybuckInt64& ParaSkybuckInt64 );
virtual void Display();
};
//
// TSkybuckGenericInteger
//
TSkybuckGenericInteger& TSkybuckGenericInteger::eek:perator+( const
TSkybuckGenericInteger& ParaSkybuckGenericInteger )
{
return *this;
}
void TSkybuckGenericInteger::Display()
{
printf("nothing \n");
}

//
// TSkybuckInt32
//
// binary arithmetic add operator overloader
// adds A and B together and returns a new C
// this might not be what I want disabled for now
/*
TSkybuckInt32 operator + ( const TSkybuckInt32 &A, const TSkybuckInt32 &B );
*/
// constructor
TSkybuckInt32::TSkybuckInt32( int ParaValue )
{
mInteger = ParaValue;
}

// add operator overloader
/*
TSkybuckInt32 operator + ( const TSkybuckInt32& A, const TSkybuckInt32& B)
{
TSkybuckInt32 C = TSkybuckInt32( 0 );
C.mInteger = A.mInteger + B.mInteger;
return C.mInteger;
}
*/
// add operator overloader
TSkybuckInt32& TSkybuckInt32::eek:perator+ ( const TSkybuckInt32&
ParaSkybuckInt32 )
{
mInteger = mInteger + ParaSkybuckInt32.mInteger;
return *this;
}

void TSkybuckInt32::Display()
{
printf( "%d \n", mInteger );
}
//
// TSkybuckInt64
//
// constructor
TSkybuckInt64::TSkybuckInt64( long long ParaValue )
{
mInteger = ParaValue;
}
// add operator overloader
TSkybuckInt64& TSkybuckInt64::eek:perator+ ( const TSkybuckInt64&
ParaSkybuckInt64 )
{
mInteger = mInteger + ParaSkybuckInt64.mInteger;
return *this;
}

void TSkybuckInt64::Display()
{
printf( "%lu \n", mInteger );
}

int _tmain(int argc, _TCHAR* argv[])
{
long long FileSize;
long long MaxFileSize32bit;
// must write code like this to use constructor ? can't just declare a,b,c ?
TSkybuckInt32 A32 = TSkybuckInt32( 30 );
TSkybuckInt32 B32 = TSkybuckInt32( 70 );
TSkybuckInt32 C32 = TSkybuckInt32( 0 );
C32 = A32 + B32;
C32.Display();
TSkybuckInt64 A64 = TSkybuckInt64( 30 );
TSkybuckInt64 B64 = TSkybuckInt64( 70 );
TSkybuckInt64 C64 = TSkybuckInt64( 0 );
C64 = A64 + B64;
C64.Display();
FileSize = 1024; // kilobyte
FileSize = FileSize * 1024; // megabyte
FileSize = FileSize * 1024; // gigabyte
FileSize = FileSize * 1024; // terrabyte
MaxFileSize32bit = 1024; // kilobyte
MaxFileSize32bit = MaxFileSize32bit * 1024; // megabyte
MaxFileSize32bit = MaxFileSize32bit * 1024; // gigabyte
MaxFileSize32bit = MaxFileSize32bit * 4; // 4 gigabyte
TSkybuckGenericInteger AGeneric = TSkybuckGenericInteger();
TSkybuckGenericInteger BGeneric = TSkybuckGenericInteger();
TSkybuckGenericInteger CGeneric = TSkybuckGenericInteger();
if (FileSize < MaxFileSize32bit)
{
TSkybuckGenericInteger AGeneric = TSkybuckInt32( 30 );
TSkybuckGenericInteger BGeneric = TSkybuckInt32( 70 );
TSkybuckGenericInteger CGeneric = TSkybuckInt32( 0 );
} else
{
// TSkybuckGenericInteger AGeneric = TSkybuckInt64( 30 );
// TSkybuckGenericInteger BGeneric = TSkybuckInt64( 70 );
// TSkybuckGenericInteger CGeneric = TSkybuckInt64( 0 );
}
CGeneric = AGeneric + BGeneric;
CGeneric.Display();
while (1)
{
}
return 0;
}

Bye,
Skybuck.
 
M

MooseFET

Without the mentioned features writing scalable software, including writing
scalable math routines becomes impractical.

That is simply false. Why do you think it can't be done with virtual
methods?
Even with virtual methods it would become slow.

Virtual methods when done the way that Borland Pascal, Delphi and a
good implementation of C++ add very little extra time to the total run
time. The virtual method dispatch code takes less instructions than
the entry code of most routines.


It does exactly what I want it to do, it does it slowly, so it's not what I
want it to do.

No, it don't. It only gives the appearance of doing what you want.
There is nothing scalable going on.
 
S

Stephen Sprunk

Skybuck Flying said:
That's kinda interesting/cool... virtual overloaded operators.

However: IT'S HELLISH SLOW when looking at the assembler.

It might not be as slow as delphi's dynamic array referencing count and
such but still.. (however not fair to compare... because this c+= example
ain't dynamic infinite scalable ;))

Way to slow to be usualable for my purposes.

It was interesting to see C++ virtual overloaded operators in action...
maybe making the operators virtual would not be necessary if other tricks
used ? I am not good enough in C++ to try other tricks.

I also explored some other ideas, non of which are statisfieng.

Really, really sad.

I will probably have to convert my code to 64 bit emulated ints and say
goodbye to performance for 32 bit cases !

And, if you'd bothered to read my posts, you'd see that's exactly what I
told you you'd see: "Simply running in 64-bit mode (even emulated) all the
time will be faster on modern CPUs than trying to decide at runtime which is
better."

I didn't have to run any tests to know that, merely understanding how the
CPUs and compilers actually work. You might try investigating those things
before posting, as it'll save you (and us) a lot of time and effort.

S
 
Top