PDA

View Full Version : CLASS vs. OBJECT and memory management



Ñuño Martínez
13-06-2016, 11:14 AM
I'm working in an old-school 2D game engine (much inspired by Action Arcade Adventure Set (http://www.fastgraph.com/makegames/sidescroller/)) using Allegro.pas (Spam? What Spam? ::)). I have some stuff done, but now that I've finished with Sprites and before to start with the tile-map stuff I start to concern with memory management.

You know, FPC has two ways to define objects: CLASS and OBJECT. Main difference between CLASS and OBJECT is that CLASS is always referenced and must be created explicitly as it is always dynamic while you can created OBJECT “statically” as you do with RECORD. So let's get the Sprite example. Using CLASS you create a list of sprites this way:



VAR
Sprites: ARRAY OF TSprite;
BEGIN
SetLength (Sprites, NumSprites);
FOR Ndx := LOW (Sprites) TO HIGH (Sprite) DO
Sprites[Ndx] := Tsprite.Create;
END;

So you have a list of pointers to the actual objects. Then you may end with the sprite list fragmented in the memory (and the more you add and remove sprites, the more fragmented would be).

Using OBJECT:


VAR
Sprites: ARRAY OF TSprite;
BEGIN
SetLength (Sprites, NumSprites);
FOR Ndx := LOW (Sprites) TO HIGH (Sprite) DO
Sprites[Ndx].Init;
END;

Despite you need to call method Init (mandatory in some cases), you have an actual list of sprites in a continuous memory block.

I’m not an expert in current microprocessor architectures inner (I’ve stuck in the Z-80) but I know a little about caches, and I suspect that the CLASS approach would be a bottleneck when executing next code:


{ Update and render sprites }
FOR Ndx := LOW (Sprites) TO HIGH (Sprite) DO
BEGIN
Sprites[Ndx].Update (DeltaTime);
Sprites[Ndx].PutSprite
END;

An array of OBJECTs may be faster as the whole ARRAY may be uploaded to the microprocessor cache at once, or in runs of n sprites. The array of CLASS references would be much slower if objects are far from the ARRAY forcing to upload cache in each loop iteration.

Wy the way, using an array of OBJECTs prevents you to use virtual methods, as all OBJECT on the array will be the same type. This can be fixed by using pointers to procedures or event properties instead; as far as I know there’s a microprocessor cache for data and another one for code, so performance won’t be affected so much. This is a bit ugly and can be implemented by simple RECORDs too, but CLASS actually uses that approach internally (the virtual table is just an array of pointers to method) just with compiler magic.

Another bad thing is that inserting and removing sprites will be ugly. Also if you do it dynamically it would take much time to resize the ARRAY. Actually I'm using a linked list implementation right now, but Build engine (http://advsys.net/ken/build.htm) uses a fixed sized array for data, including sectors and sprites, then uses several internal linked lists to organize them (for example, one linked list for each map sector that links all sprites that are inside sector); most games that use Build add and remove sprites (bullet holes, explossions, smoke…) and Duke Nukem 3D worked quite well in my old 486.

Am I right in my thoughts? Are OBJECT worth of? Does Delphi support OBJECTs too? How much microprocessor caches affect performance? Does a fixed size array of OBJECT counts as pool?

SilverWarior
13-06-2016, 06:53 PM
In Delphi Objects are still available but are only meant for backward compatibility and their use is therefore not recommended.

From Delphi documentation:

Object types are supported for backward compatibility only. Their use is not recommended.

Perhaps you might consider using what I call a "fake class approach". In this approach you are actually storing your data in array of records but instead of accessing that data directly from records you are accessing them with properties of a standardized class.
Using this approach you have all of your data tightly stored in continuous memory blocks but still use advantages of classes. Here is a quick code example:


type
TDirection = (dirLeft, dirRight);

//Record definition for storing data of a sprite
RSprite = record
XPos: Single;
YPos: Single;
Width: Integer;
Height: Integer;
ImageIndex: Integer;
end;

//Array of sperite records to keep data stored in one continoud block of memory
ASprites = Array of RSprite;

//You can even split your data to multiple arrays keeping specific kinds of data
//like position, helth status, etc.
//This means that if for instance you need to loop through all characters to see
//if any of them should be dead (zero or negative health) only data related to
//health would be put on stack
RHealthStatus = record
HealthPoints: Integer;
MaxHealthPoints: Integer;
RegenerationRate: Integer;
end;

//Array of records containing unit health data
AHelthStatus = Array of RHealthStatus;

//Specialized class for accessing data.
//Note that all properties are indexed properties since you need to tell which
//sprite are you acessing
TSprites = class(TObject)
private
FSprites: ASprites;
protected
function GetSpritesCount: Integer;
function GetXPos(Index: Integer): Single;
function GetYPos(Index: Integer): Single;
function GetWidth(Index: Integer): Integer;
function GetHeight(Index: Integer): Integer;
function GetImageIndex(Index: Integer): Integer;
procedure SetXPos(Index: Integer; const AXPos: Single);
procedure SetYPos(Index: Integer; const AYPos: Single);
procedure SetWidth(Index: Integer; const AWidth: Integer);
procedure SetHeight(Index: Integer; const AHeight: Integer);
procedure SetImageIndex(Index: Integer; const AImageIndex: Integer);
public
procedure Add(ASprite: RSprite);
procedure Remove(SpriteIndex: Integer);
constructor Create(AInitialSize: Integer);
property SpritesCount: Integer read GetSpritesCount;
property XPos[Index: Integer]: Single read GetXPos write SetXPos;
property YPos[Index: Integer]: Single read GetYPos write SetYPos;
property Width[Index: Integer]: Integer read GetWidth write SetWidth;
property Height[Index: Integer]: Integer read GetHeight write SetHeight;
property ImageIndex[Index: Integer]: Integer read GetImageIndex write SetImageIndex;
end;

//You can even subclass the base sprite class for adding additional functionality
TMovableSprite = class(TSprites)
protected
function Move(Distance: Integer; Direction: TDirection): Boolean;
//Inherited properties
property SpritesCount;
property XPos;
property YPos;
property Width;
property Height;
property ImageIndex;
end;

//You can even acces data from different specialized arrays of records
TPlayer = class(TMovableSprite)
private
FHealthStatus: RHealthStatus;
protected
function GetHealthPoints: Integer;
function GetMaxHealthPoints: Integer;
function GetRegenerationRate: Integer;
procedure SetHealthPoints(const AHealthPoints: Integer);
procedure SetMaxHealthPoints(const AMaxHealthPoints: Integer);
procedure SetRegenerationRate(const ARegenerationRate: Integer);
public
property HealthPoints: Integer read GetHealthPoints write SetHealthPoints;
property MaxHealthPoints: Integer read GetMaxHealthPoints write SetMaxHealthPoints;
property RegenarationRate: Integer read GetRegenerationRate write SetRegenerationRate;
//Inherited properties
property SpritesCount;
property XPos;
property YPos;
property Width;
property Height;
property ImageIndex;
end;

implementation

{ TSprites }

procedure TSprites.Add(ASprite: RSprite);
begin
SetLength(FSprites,Length(FSprites)+1);
end;

constructor TSprites.Create(AInitialSize: Integer);
begin
SetLength(FSprites, AInitialSize);
end;

function TSprites.GetHeight(Index: Integer): Integer;
begin
result := FSprites[Index].Height;
end;

function TSprites.GetImageIndex(Index: Integer): Integer;
begin
result := FSprites[Index].ImageIndex;
end;

function TSprites.GetSpritesCount: Integer;
begin
result := Length(FSprites);
end;

function TSprites.GetWidth(Index: Integer): Integer;
begin
result := FSprites[Index].Width;
end;

function TSprites.GetXPos(Index: Integer): Single;
begin
result := FSprites[Index].XPos;
end;

function TSprites.GetYPos(Index: Integer): Single;
begin
result := FSprites[Index].YPos;
end;

procedure TSprites.Remove(SpriteIndex: Integer);
begin
//code for removing object from array
end;

procedure TSprites.SetHeight(Index: Integer; const AHeight: Integer);
begin
FSprites[Index].Height := AHeight;
end;

procedure TSprites.SetImageIndex(Index: Integer; const AImageIndex: Integer);
begin
FSprites[Index].ImageIndex := AImageIndex;
end;

procedure TSprites.SetWidth(Index: Integer; const AWidth: Integer);
begin
FSprites[Index].Width := AWidth;
end;

procedure TSprites.SetXPos(Index: Integer; const AXPos: Single);
begin
FSprites[Index].XPos := AXPos;
end;

procedure TSprites.SetYPos(Index: Integer; const AYPos: Single);
begin
FSprites[Index].YPos := AYPos;
end;

{ TMovableSprite }

function TMovableSprite.Move(Distance: Integer; Direction: TDirection): Boolean;
begin
//Code for moving sprites
end;

{ TPlayer }

function TPlayer.GetHealthPoints: Integer;
begin
result := FHealthStatus.HealthPoints;
end;

function TPlayer.GetMaxHealthPoints: Integer;
begin
result := FHealthStatus.MaxHealthPoints;
end;

function TPlayer.GetRegenerationRate: Integer;
begin
result := FHealthStatus.Regenerationrate;
end;

procedure TPlayer.SetHealthPoints(const AHealthPoints: Integer);
begin
if AHealthPoints > FHealthStatus.MaxHealthPoints then
FHealthStatus.HealthPoints := FHealthStatus.MaxHealthPoints
else if AHealthPoints < 0 then
FHealthStatus.HealthPoints := 0
else
FHealthStatus.HealthPoints := AHealthPoints;
end;

procedure TPlayer.SetMaxHealthPoints(const AMaxHealthPoints: Integer);
begin
FHealthStatus.MaxHealthPoints := AMaxHealthPoints;
end;

procedure TPlayer.SetRegenerationRate(const ARegenerationRate: Integer);
begin
FHealthStatus.Regenerationrate := ARegenerationRate;
end;

If you decide to go for splitting data into multiple arrays you need to make sure all arrays have same order of items (common index).

This approach comes most useful when you are creating and destroying objects quote often since you don't suffer from class creation/destruction overhead.
Another or its advantages is that because you are storing your data in arrays which are continuous memory blocks you can save them to or load them from files as such instead of needing to loop through a bunch of classes and save/load data for each of them separately.

Notable drawbacks is the fact that if you store all the data in a single array the records that must be of the same type must have all the fields for all the data that most complex object stored in that array would need. This would mean that you might be wasting some space with more simpler objects whose data would not fill all the records fields.
And if you go for multiple arrays approach maintaining data in them becomes more difficult.
And probably the biggest drawback of such approach would be hard way to achieving good data reusability, even thou it is still doable.

If you need to discus some more details of this design please let me know.

Super Vegeta
14-06-2016, 08:17 AM
By the way, using an array of OBJECTs prevents you to use virtual methods, as all OBJECT on the array will be the same type.
Not really. When using virtual methods, each object must also contain a "secret" member that points to the VMT. Setting up this pointer is done by the constructor, so if you typecast some of the objects in the array and call the child constructor on them, the VMT will be properly initialized to that of the child class. One important thing to note here, though, is that the descendant classes cannot declare any new fields, since they would be located after the base class data, thus accessing them would go past the base class memory area and mess stuff up in the next array member.

Ñuño Martínez
15-06-2016, 12:16 PM
(...) I think I don't understand your code. Extending TSprites will duplicate the sprite list, and I don't get benefits. And it's worst with the TPlayer class. I mean, create a "TPlayer" object will create a new list of sprites, doesn't it?


Not really. When using virtual methods, each object must also contain a "secret" member that points to the VMT. Setting up this pointer is done by the constructor, so if you typecast some of the objects in the array and call the child constructor on them, the VMT will be properly initialized to that of the child class. One important thing to note here, though, is that the descendant classes cannot declare any new fields, since they would be located after the base class data, thus accessing them would go past the base class memory area and mess stuff up in the next array member. You're right. If descendant classes use the same data than the parent ones then there's no problems. I see it. The problem is how to know which typecast to use. May be a "SpriteType" field will do the work but I find it quit ugly:


FOR Ndx := LOW (SpriteList) TO HIGH (SpriteList) DO
CASE SpriteList[Ndx].SprType OF
stPlayer : TSprPlayer (SpriteList[Ndx]).Update;
stBullet : TSprBullet (SpriteList[Ndx]).Update;
stEnemy1 : TSprEnemy1 (SpriteList[Ndx]).Update;
stEnemy2 : TSprEnemy2 (SpriteList[Ndx]).Update;
ELSE SpriteList[Ndx].Update;
END;


Using a "CLASS OF ..." field may fix the problem, but I think it's not possible...


TYPE
TSprite = CLASS; { Forward declaration. }

TSpriteClass = CLASS OF TSprite;

TSprite = CLASS (TObject)
PRIVATE
{ ... }
fSpriteClass: TSpriteClass;
PUBLIC
{ ... }

PROPERTY SprClass: TSpriteClass READ fSpriteClass;
END;

{ ... }
FOR Ndx := LOW (SpriteList) TO HIGH (SpriteList) DO
{ This doesn't work. }
SpriteList[Ndx].SprClass (SpriteList[Ndx]).Update;


Anyway, I'm reading Game Programming Patterns (http://gameprogrammingpatterns.com/) (you should read it even if you don't like patterns, actually I don't like them but the book gives a lot of good ideas) and I've found some information. What I'm asking for is an Object Pool (http://gameprogrammingpatterns.com/object-pool.html), as you have all objects and reuses them when needed. This is the way Build works, for example.

Also I've found a problem about microprocessor caches: if using a scripting (bytecode (http://gameprogrammingpatterns.com/bytecode.html) ;)) to define the behavior of sprites (allowing a way to keep size of all TSprite while extending them), anytime a script is executed it may kill the cache. :(

Super Vegeta
15-06-2016, 04:16 PM
If descendant classes use the same data than the parent ones then there's no problems. I see it. The problem is how to know which typecast to use.
Unless you want to dynamically switch an array member between types, then you can just call the adequate constructor when creating the member and it will work fine.


program aaa; {$MODE OBJFPC}

type bbb = object
public procedure hello; virtual;
public constructor create;
public destructor destroy; virtual;
end;

type ccc = object(bbb)
public procedure hello; virtual;
public destructor destroy; virtual;
end;

procedure bbb.hello;
begin
writeln('bbb: hello')
end;

constructor bbb.create;
begin
writeln('bbb: create')
end;

destructor bbb.destroy;
begin
writeln('bbb: destroy')
end;


procedure ccc.hello;
begin
writeln('ccc: hello')
end;

destructor ccc.destroy;
begin
writeln('ccc: destroy')
end;


Var
X: Array[0..1] of bbb;

begin
X[0].Create();
ccc(X[1]).Create();

X[0].Hello();
X[1].Hello();

X[0].Destroy();
X[1].Destroy()
end.

Although the compiler says that "X is not initialized" during "X[0].Create()", the above code (with FPC 2.6.4) works fine - after the constructors are called, the VMT pointers are set up, so calls to Hello() and Destroy() are properly resolved to those of the child class:


bbb: create
bbb: create // "ccc" did not have an explicit constructor, so the "bbb" constructor was called, but the VMT is still set up properly
bbb: hello
ccc: hello
bbb: destroy
ccc: destroy


IMO, with what you're trying to achieve, it would actually be simpler to just use records and have a "spriteType" field there. You can use the Case() statement to test for the type and call the appropriate procedure when needed. It may not look the prettiest, but it's the simplest way and there's no risk that compiler optimisations mess something up.
Also, you can have a SPRITETYPE_NONE value for your spriteType, which gives you a way to have "NULL" array members for free.

Also, have you tried using a profiler? It may turn out that the memory accesses aren't really that much of a problem and you're trying to optimize the wrong thing.

SilverWarior
15-06-2016, 07:04 PM
I think I don't understand your code. Extending TSprites will duplicate the sprite list, and I don't get benefits.

Not necessarily. It depends on how you are extending the TSprites class. If you are extending it by just adding additional methods to descendant class like in TMovableSprite it won't duplicate list since you are still accessing it through inherited methods or properties from parent class.


And it's worst with the TPlayer class. I mean, create a "TPlayer" object will create a new list of sprites, doesn't it?

Yes in case of TPlayer I do create a new array to store additional information that is not being stored in ASprites array.

What is advantage of this?

For instance during rendering phase you only need information about sprites positions, size and orientation. You don't need additional information that you might be storing in your objects like in-game unit health.
Now if you are storing all that information in one array when the data from that array is put on stack the stack would contain the data that you need during the rendering phase and also the data that you don't need during rendering phase. So you will be able to fill the stack with information for limited number of units at once.
But if you have one array which basically contains only data that that is needed during rendering phase you would be able to store relevant data for greater number of in-game units at once.

On the other hand in a different part of your code you might only need data related to in-game units health (checking which units are dead, regenerating their health). Here it would also be beneficial if you only put relevant data about units health on the stack without other unneeded data like unit positions etc.

Perhaps it would be more understandable if I create a mind graph representation of the concept

Ñuño Martínez
16-06-2016, 04:36 PM
IMO, with what you're trying to achieve, it would actually be simpler to just use records and have a "spriteType" field there. You can use the Case() statement to test for the type and call the appropriate procedure when needed. It may not look the prettiest, but it's the simplest way and there's no risk that compiler optimisations mess something up.
Also, you can have a SPRITETYPE_NONE value for your spriteType, which gives you a way to have "NULL" array members for free.
This is much like how Build works, and I think I'll go this way.


Also, have you tried using a profiler? It may turn out that the memory accesses aren't really that much of a problem and you're trying to optimize the wrong thing. No, I didn't. I tried to use GNU's profiler a long time ago (+5 years) and it didn't work too much as it couldn't identify several Object Pascal stuff. It was useless. I should try again, may be FPC 3.0 works. [edit] I've just read at the Lazarus wiki (http://wiki.lazarus.freepascal.org/Profiling) that "Support for using gprof under Linux is broken in FPC 2.2.0-2.4.0; it is fixed in FPC 2.6+", which explains why it didn't work.


Perhaps it would be more understandable if I create a mind graph representation of the concept That would be great because I'm a bit lost. ::)

Thyandyr
15-06-2017, 06:47 PM
Can I read this thread as allegory to Object vs Record (with methods)

Ñuño Martínez
19-06-2017, 11:58 AM
Of course you can.

By the way, I've discovered "records with methods" recently. May be they are a better approach for what I want/need as they don't need initialization/constructor and no inheritance is involved (AFAIK FPC OBJECT inherits from an internal TObject the same way CLASS does, doesn't it?).

Akira13
22-06-2017, 02:23 AM
You definitely should use records, with the {$modeswitch AdvancedRecords} define set. The classic Turbo Pascal style "objects" are well known to be buggy (memory leaks, e.t.c.) and as far as I know they aren't being actively maintained or developled in either Delphi or FPC, and I see no reason why they ever would be again in the future.

Ñuño Martínez
04-07-2017, 08:05 AM
Yep.

Actually I think I'll use what SilverWarior suggested (http://www.pascalgamedevelopment.com/showthread.php?32520-CLASS-vs-OBJECT-and-memory-management&p=147441&viewfull=1#post147441): a CLASS containing all sprite information as an ARRAY of RECORDs.

turrican
11-07-2017, 10:55 AM
Yep! What about TObjectList and TDictionary ?

Ñuño Martínez
11-07-2017, 03:56 PM
AFAIK both TObjectList and TDictionary are dynamic and designed for easy insertion and deletion. That is slow.

The best demonstrable game performance were data-pools, where no dynamic memory management is done: just activate and de-activate static memory.

SilverWarior
11-07-2017, 04:13 PM
It is not so hard to upgrade most lists into object pools.
Here is a link to a short article about this topic
https://parnassus.co/custom-object-memory-allocation-in-delphi-bypassing-fastmm-for-fun-and-profit/

I plan to spend some more time studying this subject in the future.

phibermon
12-07-2017, 12:57 AM
Yes - pre-allocate storage for data, pre-construct object instances and then just fetch them from the pools. You can grow as needed and construct double linked lists for quick insertion and deletion if required.

I usually sync my linked list operations to a hash table or spatial partitioning paradigm where appropriate as it's much faster to keep multiple structures in sync per operation than it is to keep them in sync by scanning the whole collection.

I usually add on a thread safe reference count operation that will flag the data/object as unused rather than handing the job over to reference counted interfaces. In games we only really care about getting images, geometry, sounds etc out of memory - compared to the size of that stuff we don't care about instance sizes or constant data pools that rarely extend past dozens of MB. Leave the dynamic allocation for really big data.

For data/objects that are written/read from multiple threads I wrap it in a task that gets passed along in chains across priority queues in each thread to ensure single thread access and operation order. Along with lock free queues this means I don't have to maintain slow locks for bits of data or objects - I just ensure it's impossible to be accessed at the same time (same goes for rendering, I don't lock any data or structures, I just pause the threads processing queues that might access the data the render thread needs - the so called 'render window')

Tasks can be started and stopped at any point to ensure a maximum task time per cycle or flagged as frame critical to ensure the task is complete in time to finish rendering before the start of the next V-synced frame. (so it's like a crude OS scheduler but instead of sharing time on a processor, I'm sharing the time available per frame ( - time to render the frame + jitter overhead))

This is the best way I know of handling time wasted idling in the Swap operation. Disabling v-sync is a stupid thing to do. You can't can't show more frames than the refresh rate of the screen - you only have to minimise time spent in the swap operation and time things carefully so you don't run over into the next window and cause an uneven framerate. You should be measuring performance by the time it takes to render each frame - not by how many frames per second you can push through - that doesn't tell you anything useful at all except if one computer is faster or slower than another on a given static task. Pipelines are too complex to rely on FPS as an indication during optimisation - high precision timers on actual operations are best. You can use the GL timer API to get true frame render times rather than putting a flag either side of the flush and swap - the card may of already started by then so you really want GL timers. (I'm sure DirectX and Vulkan have something similar)

Sorry, I digress. When don't I? ;)

turrican
12-07-2017, 11:21 AM
Here is a little benchmark for Delphi I did.

https://github.com/turric4n/Delphi-Bench-ObjectsVSRecords

Records are more faster than Heap allocated Records (Pointer to Records) and much more faster than Objects.

Thyandyr
12-07-2017, 02:46 PM
Here is a little benchmark for Delphi I did.

https://github.com/turric4n/Delphi-Bench-ObjectsVSRecords

Records are more faster than Heap allocated Records (Pointer to Records) and much more faster than Objects.

What is Quick.Chrono unit and where can I get it?

Ñuño Martínez
13-07-2017, 07:55 AM
Yes - pre-allocate storage for data, pre-construct object instances and then just fetch them from the pools. You can grow as needed and construct double linked lists for quick insertion and deletion if required.
That's just what the engines I know use (Build, Action Arcade Adventure Set). And this is the way I'll use in my engine.

Like this:


TYPE
TSpritePtr = ^TSPrite;

TSprite = RECORD
x, y: INTEGER;
Bmp: ImageRef;
NextSpr: TSpritePtr;
END;

VAR
SpriteList: ARRAY OF TSprite; { Or a class containing the list, or a spezialized GENERIC or...}
PlayerSpr: TSpritePtr;
FirstEnemy: TSpritePtr;
FirstBullet: TSpritePtr;


Since the pointer itself is stored inside the list, there are very little CPU-cache collisions, so traverse the linked list should be quite fast.

[edit]
Fun fact: Actually this may be faster in the old times (i.e. 8086, 80286, 80386), when memory were divided in pages (do you remember the FAR and NEAR pointers?). If the whole list is in one single memory page, no page change is needed, and memory access may be faster specially if the related code is also in the same memory page. The AAAkit book talks about it too.


Here is a little benchmark for Delphi I did.

https://github.com/turric4n/Delphi-Bench-ObjectsVSRecords

Records are more faster than Heap allocated Records (Pointer to Records) and much more faster than Objects.

Thanks pal. It confirms the hypothesis "Objects/CLASSes are the slowest". :)

[I must test it on FPC though, but I think it will be the same ::)]

JC_
15-07-2017, 09:53 AM
Here is a little benchmark for Delphi I did.

https://github.com/turric4n/Delphi-Bench-ObjectsVSRecords

Records are more faster than Heap allocated Records (Pointer to Records) and much more faster than Objects.

It's tricky, I have similar tests and results depends on:
1) How many records/objects exists
2) Complexity of record/object (number or variables)
The results will be completely different with:


TSpriteRec = record
ID : integer;
x, y : integer;
Name : String[80];
angle : double;
alpha : byte;
components : array [0..9] of integer;
p : pointer;
end;

3) If features like sorting are needed

In general array of pointers is fastest.

Ñuño Martínez
17-07-2017, 08:38 AM
Interesting.

There are a lot of variables* involved. All these tests are useful but only the actual implementation will tell what is the fastest.
_____________________________

* Pun not intended ;)

SilverWarior
17-07-2017, 04:27 PM
Actually these shown test aren't so useful because they are poorly implemented. What is wrong with them?

When testing WRITE performance of classes/objects the above tests measure both the creation time and first write to fields together. This of course does not provide real picture. Why? Because most of the measured time is actually spent in class creation. You see the main drawback of classes versus records is the fact that their creations takes longer time than record creation. Why is that.
Because when creating class you don't just allocate memory for it like with records but you also execute some special initialization code that is required for classes.
Now if you would split the test to measure class creation and field writing times separately you would see that writing to class fields isn't much slower than writing to record fields if it even is.
When testing array of records tests only perform sequential access but not random access. Sequential access to array items is faster due the fact that arrays are always stored in memory as one single memory block and becouse stem memory also excels in perform in sequential memory access. You can observe this by using some memory diagnostic and measuring utils like MemTest.
There is no test that would measure performance of adding or removing records during run-time. Here arrays lose quite a lot of performance because when removing of items you need to rearrange all other items in order to fill the gap. This can become quite slow when you have huge number of larger records as there is quite a bit of memory that needs to be moved. Also due the fact that arrays are always stored as one continuous block of memory it means that when you are extending the size of that array there needs to be sufficient free space after the existing array for resizing. If there isn't then the contents of the whole array needs to be copied to another location in memory where there is enough free space to hold the entire extended array in one continuous block of memory.
When using lists of classes the above mentioned drawback are barely noticeable because their internal arrays only store references (pointers) to these classes so rearranging their items does not require so big memory movements. Also the data of these classes doesn't have to be stored in one continuous memory block.
Another thing that can screw up tests results is doing random number generation inside the test method/loop itself. Why? Because time taken for generating single random number can vary quite a lot. Of course it most depends on what kind of random algorithm is being used. And it can also depend on whether your CPU perhaps have built-in support for random number generation. So I strongly recommend you generate all needed random numbers before testing and then read them from memory during the testing itself.
And finally the problem of all of the above tests is that they are to short. What do I mean with that? Today's computers are made for multitasking which means that it is not necessary that when you are performing the test your application will be given all the available processing power of CPU core. Not to mention that since most modern CPU use automatic throttling to achieve better power saving it is not necessary that the CPU is working at max speed at the time when you are performing your tests. You can observe this by starting the above test multiple times where you will see varying results.
So in order to get more accurate results it is best to make sure that the test are running for longer time or are repeated multiple times and then average time is calculated from the results.

Thyandyr
18-07-2017, 11:43 AM
Very good, very good.

JC_
21-07-2017, 11:08 AM
1) If is class creation and destruction slow - during game loop it's no win. In case that you need some pre-generated data like tilemap, so you can wait xxx ms with classes or have it done until 1 ms with simple array of records.
2) If you mean access to list of entities by some ID so it depends if you need go through the list or how it's done.
And somehow I doubt that you can get the result faster by using classes.
3) That's problem, maybe it can be fixed through better/different implementation. With classes don't forget 1)
4) In my test are no random numbers in the loop

Everyone can use for game whatever he want, I'm happy with lists of pointers to records for my entities.
Classes are generating much bigger CPU load (FPC) and so far I have not found any benefits in performance especially if there are thousands of "objects"/game entities there.

phibermon
27-07-2017, 09:34 AM
There's a modern revival of functional programming that has some very interesting points. Even if you're a die hard fan of functional or object orientated coding - there are advantages and 'disadvantages' to both approaches.

Yes classes are slower to initialise and destroy but when it comes to even fairly recent hardware? the cost is negligible - the vast majority of modern engines and games are object orientated - engines used in games such as the original 'max payne' were amongst the first fully OO engines and we still have OO engines leading the field today. Forget the performance hit, just pre-initialise if you're using thousands of class instances and it won't matter compared to the computationally heavy sections of code.

Some parts of your design are complex lumbering beasts that are easier to maintain and modify when designed as classes.

Some parts of your design are time critical or logically flat and functional programming and the minor performance gains are worth losing the inheritance flexibility of high level classes.

I think a combination of both is a good idea - as a general rule of thumb anything that's being touched many times per frame (such as entities etc) should be as functional as possible and everything else should be as modular as possible.

Ñuño Martínez
27-07-2017, 10:33 AM
By functional programming, are you talking about this (https://en.wikipedia.org/wiki/Functional_programming)?