你还记得我先前的问题:What is causing data race in std::async
here?
即使我成功并行化了这个程序,仍然运行得太慢而不实用
所以我试图改进代表康威生命游戏模式的数据结构
新结构的简要说明:
class pattern {
// NDos::Lifecell represents a cell by x and y coordinates.
// NDos::Lifecell is equality comparable, and has std::hash specialization.
private:
std::unordered_map<NDos::Lifecell, std::pair<int, bool>> cells_coor;
std::unordered_set<decltype(cells_coor)::const_iterator> cells_neigh[9];
std::unordered_set<decltype(cells_coor)::const_iterator> cells_onoff[2];
public:
void insert(int x, int y) {
// if coordinate (x,y) isn't already ON,
// turns it ON and increases the neighbor's neighbor count by 1.
}
void erase(int x, int y) {
// if coordinate (x,y) isn't already OFF,
// turns it OFF and decreases the neighbor's neighbor count by 1.
}
pattern generate(NDos::Liferule rule) {
// this advances the generation by 1, according to the rule.
// (For example here, B3/S23)
pattern result;
// inserts every ON cell with 3 neighbors to result.
// inserts every OFF cell with 2 or 3 neighbors to result.
return result;
}
// etc...
};
简而言之,pattern
包含单元格。它包含每个ON单元,以及每个具有1个或多个ON相邻单元的OFF单元。它还可以包含备用OFF单元
cells_coor
通过使用坐标作为键直接存储单元格,并将它们映射到ON邻居单元格的数量(存储为int
)以及它们是否为ON(存储为bool
)
cells_neigh
和cells_onoff
通过迭代器将单元格间接存储为键。
单元的ON邻居数始终为0或更大且为8或更小,因此cells_neigh
为9号数组。
cells_neigh[0]
存储具有0个ON相邻单元的单元,cells_neigh[1]
存储具有1个ON相邻单元的单元,依此类推。
同样,单元格始终为OFF或ON,因此cells_onoff
是2号数组
cells_onoff[false]
存储OFF单元格,cells_onoff[true]
存储ON单元格
必须将单元格插入或删除所有cells_coor
,cells_neigh
和cells_onoff
。换句话说,如果将一个单元插入或从其中一个单元中删除,则对其他单元也必须如此。因此,cells_neigh
和cells_onoff
的元素是std::unordered_set
,将迭代器存储到实际单元格中,从而可以通过邻居计数或OFF / ON状态快速访问单元格。
如果此结构有效,则插入函数的平均时间复杂度为O(1)
,擦除也为O(1)
,而生成O(cells_coor.size())
则具有很大的时间复杂度。先前的结构。
但正如您所见,存在一个问题:如何散列std::unordered_map::const_iterator
?
std::hash
禁止对他们进行专业化,所以我必须制作一个自定义的
取他们的地址是行不通的,因为他们通常被收购为rvalues或temporaries
取消引用它们也不起作用,因为有多个单元格具有0个ON相邻单元格,或多个单元格处于OFF状态等。
那我该怎么办?如果我无法执行任何操作,cells_neigh
和cells_onoff
将为std::vector
或其他内容,会严重降低时间复杂度。
答案 0 :(得分:2)
短篇小说:这不会奏效(非常好)(* 1)。您可能要在地图cells_coor
上执行的大多数操作都会使任何迭代器(but not pointers, as I learned)无效。
如果你想保留我所说的不同的观点&#34;在某些集合中,存储实际数据的基础容器需要不被修改或者不能使其迭代器无效(例如链接列表)。
也许我错过了什么,但为什么不为邻居计数保留9组小区,为开/关保留2组小区? (* 2)换句话说:你真的需要那张地图吗?的(* 3)强>
(* 1):映射仅在发生重新散列时使指针和迭代器无效。你可以检查一下:
// Before inserting
(map.max_load_factor() * map.bucket_count()) > (map.size() + 1)
(* 2):9组可以减少到8:如果单元格(x,y)不在8组中,那么它将在第9组中。因此,不需要存储该信息。打开/关闭相同:它足以存储打开的单元格。所有其他人都关闭了。
(* 3):在不使用地图的情况下访问邻居的数量,但仅使用单元格集,伪代码的种类:
unsigned number_of_neighbours(Cell const & cell) {
for (unsigned neighbours = 9; neighbours > 0; --neighbours) {
if (set_of_cells_with_neighbours(neighbours).count() == 1) {
return neighbours;
}
}
return 0;
}
集合中的重复查找当然会破坏实际性能,您需要对其进行分析。 (渐近运行时不受影响)