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...
  } 
}