Answer the question
In order to leave comments, you need to log in
How to speed up this code (cellular automaton)?
I am making an engine for playing cellular automata. Everything was good enough, until I started processing large cards, where the calculation speed sags. And I have no idea how to increase it.
There is a lot of code, but it is extremely simple.
The program works like this: I create a 2D array filled with objects of the Cell class. There is a Grid class for an array. I will give the code, and I will explain the meaning of some variables and methods later.
// Grid.h
private:
Rule* rule;
int ** _ranges;
int _width;
int _height;
/// обычный массив, только с перегруженым оператором []
OverflowArray<OverflowArray<Cell>> _grid;
std::vector<Cell*> _cells_to_draw;
ThreadPool * pool;
std::vector<Cell*> * storage;
static int ** DivideGridIntoZones(int zones, int length);
void CalculateZone(int id,const int range[2]);
void UpdateCellsStates(const int * range);
public:
Grid(int width, int height, int threadsAmount,sf::RenderTarget& screen);
~Grid();
Cell& GetCell(int x, int y);
void CalculateCells(int threads=4);
void DisplayCells();
// Cell.h
enum CellState{
Empty,
Alive,
};
class Cell{
private:
CellState _state;
// сначала программа считает следующее состояние клетки, а в следующем цикле их обновляет
// для этого и нужен _nextState
CellState _nextState;
public:
Cell();
Cell(int x,int y,CellState state=CellState::Empty){...};
bool IsAlive(){ return _state==CellState::Alive;}
CellState & GetNextState() { return _nextState;}
CellState & GetState() { return _state;}
void SetNextState(CellState state){ _nextState = state;}
};
Grid::Grid(...){
// DivideGridIntoZones делит ширину на промежутки, количество которых равно threadsAmount
// код многопоточный, поэтому каждый поток обрабатывает свою зону
// _width=100, threadsAmount = 2
// _ranges =
_ranges = DivideGridIntoZones(threadsAmount,_width);
}
// Grid.cpp
// _width=1000, _height=1000, threadsAmount = 8
// с такими параметрами на большой карте с большим количеством живых клеток выжимаю 8 кадров
void Grid::CalculateCells(const int threadsAmount) {
// этот код более-менее держит нагрузку и при таких параметрах выполняется за 0.01с
// тут и происходит замена значения _state на значение _nextState
for(int i=0;i<threadsAmount;i++){
pool->AddJob( [this,i](){this->UpdateCellsStates(_ranges[i]);});
}
pool->WaitAll();
//
// моя главная проблема. При очень большом паттерне выполняется за 0.032с
for(int i=0;i<threadsAmount;i++){
pool->AddJob( [this,i]() {this->CalculateZone(i,_ranges[i]);});
}
pool->WaitAll();
// some code
}
void Grid::CalculateZone(int id,const int range[2]){
std::vector<Cell*> temp;
Cell * cell;
// вот и использование range
for(int y=0;y<_height;y++){
for(int x=range[0];x<range[1];x++){
cell = &GetCell(x,y);
// сохраняю в переменную temp, чтобы её сохранить в storage
if(cell->GetState()==CellState::Alive){
temp.emplace_back(cell);
}
if(!_isPaused) {
// тут считается следующее состояние клетки и изменяется
// код приводить не буду, но скажу, что тут есть 9 обращений к массиву
rule->Execute(cell,x,y);
}
}
}
// сохраняю storage
storage[id] = temp;
}
Answer the question
In order to leave comments, you need to log in
In a general sense, as I see from your code, you got into True Sharing, getting smeared with Cache Misses along the way and finally killing your performance with the help of an unreasonably huge cell size.
8B per cell, the state of which can fit in 1B, this is really a huge size.
enum CellState : uint8_t
will reduce the state size from 4B to 1B. And this type should also be renamed, because. it is not CellState
, but something related to the behavior of the cell. And it CellState
will look like this:
// Renamed from `CellState`.
enum CellBehavior : uint8_t
{
Empty,
Alive,
};
struct CellState final
{
CellBehavior current_behavior : 4;
CellBehavior next_behavior : 4;
};
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question