C++ 加速图中邻域的迭代
我有一个静态图(拓扑不随时间变化,在编译时已知),图中的每个节点可以有三种状态之一。然后,我模拟了一个动态过程,其中一个节点有可能随时间改变其状态,而这种概率取决于其邻居的状态。随着图形变大,模拟开始变得非常缓慢,但经过一些分析后,我发现大部分计算时间都花在迭代邻居列表上 我可以通过改变用于访问图中邻居的数据结构来提高模拟的速度,但我想知道是否有更好(更快)的方法来实现这一点。 我当前的实现如下所示: 对于标记为从C++ 加速图中邻域的迭代,c++,c++11,optimization,graph,neighbours,C++,C++11,Optimization,Graph,Neighbours,我有一个静态图(拓扑不随时间变化,在编译时已知),图中的每个节点可以有三种状态之一。然后,我模拟了一个动态过程,其中一个节点有可能随时间改变其状态,而这种概率取决于其邻居的状态。随着图形变大,模拟开始变得非常缓慢,但经过一些分析后,我发现大部分计算时间都花在迭代邻居列表上 我可以通过改变用于访问图中邻居的数据结构来提高模拟的速度,但我想知道是否有更好(更快)的方法来实现这一点。 我当前的实现如下所示: 对于标记为从0到N-1的N节点以及K的平均邻居数的图,我将每个状态作为整数存储在std::ve
0
到N-1
的N
节点以及K
的平均邻居数的图,我将每个状态作为整数存储在std::vector states
中,并将每个节点的邻居数存储在std::vector number_of_neights
中
为了存储邻居信息,我又创建了两个向量:std::vector neighbor_list
,按顺序存储节点0
、节点1
、,节点N
,以及索引向量std::vector index
,该索引向量为每个节点存储其在邻居列表中的第一个邻居的索引
我一共有四个向量:
printf( states.size() ); // N
printf( number_of_neighbors.size() ); // N
printf( neighbor_lists.size() ); // N * k
printf( index.size() ); // N
更新节点i
i时,我访问其邻居,如下所示:
// access neighbors of node i:
for ( int s=0; s<number_of_neighbors[i]; s++ ) {
int neighbor_node = neighbor_lists[index[i] + s];
int state_of_neighbor = states[neighbor_node];
// use neighbor state for stuff...
}
//访问节点i的邻居:
对于(int s=0;s知道N
的数量级很重要,因为如果数量级不太高,可以使用知道编译时拓扑的事实,这样就可以将数据放入已知维度的std::array
s中(而不是std::vector
s),使用尽可能最小的类型to(如果需要)保存堆栈内存,并将其中一些定义为constexpr
(除状态外的所有状态均为)
因此,如果N
不是太大(堆栈限制!),您可以定义
状态
作为标准::数组
(3个状态8位就足够了)
- 作为
constepr std::array
的邻居数
(如果最大邻居数小于256,则使用更大的类型)
neighbor\u列表
为constepr std::array
(其中M
是邻居数量的已知总和),如果16位足够N
,则为更大的类型
index
作为constepr std::array
索引,如果16位足够M
;否则为更大的类型
我认为(我希望)使用已知维度的数组,即constexpr
(如果可能),编译器可以创建最快的代码
关于更新代码…我是一个老C程序员,所以我习惯于用现代编译器做得更好的方式来优化代码,所以我不知道下面的代码是否是个好主意;无论如何,我会这样写代码
auto first = index[i];
auto top = first + number_of_neighbors[i];
for ( auto s = first ; s < top ; ++s ) {
auto neighbor_node = neighbor_lists[s];
auto state_of_neighbor = states[neighbor_node];
// use neighbor state for stuff...
}
auto first=索引[i];
自动顶部=第一个+相邻的数量[i];
用于(自动s=第一;s
--编辑--
OP指定
目前,我已经将模拟时间增加到了N=5000,但如果可能的话,我的目标是N~15000
因此,对于邻居列表
和索引
中的类型,16位应该足够了
状态
和邻居的数量
各约为15KB(使用16位变量为30KB)
索引约为30KB
在我看来,这是堆栈变量的合理值
问题可能是neighbor\u list
;如果中间的邻居数很低,比如说10来固定一个数字,那么M
(邻居之和)大约是150000,所以neighbor\u list
大约是300KB;不低,但对于某些环境来说是合理的
如果中间数很高,比如说100,来修正另一个数字,邻居列表
大约为3MB;在某些环境中,它应该很高。目前,您正在为每次迭代访问sum(K)节点。这听起来并不坏……直到您点击访问缓存
对于少于2^16个节点,您只需要一个uint16\u t
来标识节点,但是对于K个邻居,您将需要一个uint32\u t
来索引邻居列表。
如前所述,这3种状态可以存储在2位中
所以
// your nodes neighbours, N elements, 16K*4 bytes=64KB
// really the start of the next nodes neighbour as we start in zero.
std::vector<uint32_t> nbOffset;
// states of your nodes, N elements, 16K* 1 byte=16K
std::vector<uint8_t> states;
// list of all neighbour relations,
// sum(K) > 2^16, sum(K) elements, sum(K)*2 byte (E.g. for average K=16, 16K*2*16=512KB
std::vector<uint16_t> nbList;
//节点邻居,N个元素,16K*4字节=64KB
//实际上,当我们从零开始时,下一个节点开始相邻。
std::矢量偏移量;
//节点状态,N个元素,16K*1字节=16K
向量状态;
//所有邻国关系清单,
//sum(K)>2^16,sum(K)元素,sum(K)*2字节(例如,对于平均K=16,16K*2*16=512KB
std::向量表;
您的代码:
// access neighbors of node i:
for ( int s=0; s<number_of_neighbors[i]; s++ ) {
int neighbor_node = neighbor_lists[index[i] + s];
int state_of_neighbor = states[neighbor_node];
// use neighbor state for stuff...
}
//访问节点i的邻居:
对于(int s=0;……知道……代码< N>代码>的数量级是……一些GPU函数比CPU快迭代。但是我从来没有看过如何在C++中实现这一点。。我有32GB的可用ram,因此我可以对我可以声明的阵列的大小进行一些估计。谢谢。一个节点多久更改一次状态?一个节点有多少个邻居(平均和最大)?如果更改很少,您可以存储有关邻居的统计信息,并在节点更改时更新所有邻居,而不是迭代这些邻居以获取这些统计信息。您还可以将数组放入结构中,以强制它们在内存中相邻,并可能改善局部性。(虽然
uint32_t curNb = 0;
for (auto curOffset : nbOffset) {
for (; curNb < curOffset; curNb++) {
int neighbor_node = nbList[curNb]; // done away with one indirection.
int state_of_neighbor = states[neighbor_node];
// use neighbor state for stuff...
}
}