Update
This commit is contained in:
parent
f354398fd2
commit
53768ca2ea
20 changed files with 51 additions and 29 deletions
|
@ -205,7 +205,7 @@ The world cells are kept in the `world` array -- each cell holds the current sta
|
|||
|
||||
For real serious projects there exist highly optimized [algorithms](algorithm.md) such as [QuickLife](quicklife.md) and [HashLife](hashlife.md) -- if you are aiming to create a state-of-the-art program, check them out. Here we will not be discussing them further as the are beyond the scope of this article.
|
||||
|
||||
**Implementing infinite world:** it is possible to program the game so that the world has no boundaries (or possibly has boundaries imposed only by maximum values of the used integer [data type](data_type.md); these could of course be removed by using an advanced arbitrary size integer type). The immediate straightforward idea is to simply resize the world when we need more space, i.e initially we allocate some space to the world (let's say 128x128 cells) and once a cell comes to life outside this area we resize it by [allocating](memory_allocation.md) more memory -- of course this resizing should happen by some bigger step than one because the pattern will likely grow further (so we may resize e.g. from 128x128 right to 256x256). This can of course be highly inefficient, a single glider traveling far away in one direction may cause resizing the world to astronomical size; therefore more smartness can be applied, for example we may allocate spaces by big tiles (let's say 64x64) wherever they are needed (and of course deallocate/free the ones that no longer have any live cells) -- this will require a lot of code for managing the tiles and being able to actually quickly simulate such representation of the world. It would also be possible to have no world array at all but rather only keep a [list](list.md) of cells that are alive, each one storing its coordinates -- this might of course become inefficient for a big number of live cells, however good optimization could make this approach bearable; a basic optimization here would have to focus on very quick determination of each cell's neighbors, which could be achieved e.g. by keeping the list of the cells sorted (e.g. from northwestmost to southeastmost). Another idea (used e.g. by the QuickLife algorithm) is to use a dynamic [tree](tree.md) to represent the world.
|
||||
**Implementing infinite world:** it is possible to program the game so that the world has no boundaries (or possibly has boundaries imposed only by maximum values of the used integer [data type](data_type.md); these could of course be removed by using an advanced arbitrary size integer type). The immediate straightforward idea is to simply resize the world when we need more space, i.e initially we allocate some space to the world (let's say 128x128 cells) and once a cell comes to life outside this area we resize it by [allocating](memory_allocation.md) more memory -- of course this resizing should happen by some bigger step than one because the pattern will likely grow further (so we may resize e.g. from 128x128 right to 256x256). This can of course be highly inefficient, a single glider traveling far away in one direction may cause resizing the world to astronomical size; therefore more smartness can be applied, for example we may allocate spaces by big tiles (let's say 64x64) wherever they are needed (and of course deallocate/free the ones that no longer have any live cells) -- this will require a lot of code for managing the tiles and being able to actually quickly simulate such representation of the world. It would also be possible to have no world array at all but rather only keep a [list](list.md) of cells that are alive, each one storing its coordinates -- this might of course become inefficient for a big number of live cells, however good optimization could make this approach bearable; a basic optimization here would have to focus on very quick determination of each cell's neighbor count, which could be achieved e.g. by keeping the list of the cells sorted (e.g. from northwestmost to southeastmost). Another idea (used e.g. by the QuickLife algorithm) is to use a dynamic [tree](tree.md) to represent the world.
|
||||
|
||||
Some basic **[optimization](optimization.md)** ideas are following: firstly, as shown in the code above, even though we could theoretically only allocate 1 bit for each cell, it is better to store each cell as a whole byte or possibly a whole integer (which will help memory alignment and likely speed up the simulation greatly) -- this also comes with the great feature of being able to store the current state in the lowest bit and older states in higher bits, which firstly allows rewinding time a few states back (which as seen below will be useful further) and secondly we don't need an extra array for performing the cell updates. Next, as another simplest optimization, we may try to skip big empty areas of the world during the update (however watch out for the border where a new cell can spawn due to a neighboring pattern). We may take this further and also skip areas that contain static, unchanging still life -- this could all be done e.g. by dividing the world into tiles (let's say 64x64) and keeping a record about each tile. This can be taken yet further and also detect e.g. periodically repeating still life (such as blinkers); if for example we know a tile contains pattern that repeats with period 2 and we are able to rewind time one step back (which we can easily do, as shown above), we can simply do this step back in time instead of simulating the whole cell. Next we may try to use [dynamic programming](dynamic_programming.md), e.g. [caches](caches.md) and [hash tables](hash_table.md) to keep results of recently performed big pattern simulations to reuse in the future, so that we don't have to simulate them again (i.e. for example remembering how a glider evolved from one frame to another so that next frame we simply copy-paste the result instead of actually simulating each cell again); HashLife algorithm is doing something like this. Also try to focus greatly on the [bottle necks](bottle_neck.md) such as counting the cell's neighbors -- it will be greatly worth it if you speed this code up, even for the cost of using more memory, i.e. consider things like loop unrolling, function inlining and [look up tables](lut.md) for counting the neighbors. Further speedup may be achieved by [parallelization](parallelism.md) ([multithreading](multithreading.md), [GPU](gpu.md), [SIMD](simd.md), ...) as isolated parts of the world may be simulated independently, though this will introduce hardware dependencies and [bloat](bloat.md) and is therefore discouraged.
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue