IP Forward
rinetd
https://www.cnblogs.com/operationhome/p/11284559.html
https://blog.csdn.net/weixin_42298754/article/details/116799937
IP转发
https://www.modb.pro/db/32772
需要进一步的研究学习
暂无
遇到的问题
暂无
开题缘由、总结、反思、吐槽~~
参考文献
无
https://www.cnblogs.com/operationhome/p/11284559.html
https://blog.csdn.net/weixin_42298754/article/details/116799937
https://www.modb.pro/db/32772
暂无
暂无
无
一种通过在程序运行之前自动检查源代码的调试方法。
经常和source code analysis结合(貌似sourcetail的代码结构分析?
可以分析addresses weaknesses漏洞
好吧,都是机密,就不写了。
提高访存的方法 C-AMAT
机密
暂无
暂无
无
TMA intel 2014论文
基于先验知识的
TSV taishan 9110
OSACA
IACA
暂无
无
MPI_Send & MPI_receive
MPI_AllTogether()更慢,需要4s
发现不对劲,打印更多输出。第一次循环肯定是对的因为和DBL_MAX比较。
为什么明明有56GB的IB网,传输速度还是这么慢呢?写比较慢?
7*8=56 8条通道
暂无
无
https://www.nhr.kit.edu/userdocs/horeka/batch_slurm_mpi_multithread/ 看这个
还有个ppt 16
google hydrid openmpi openmp
这里值得要注意的是,似乎直接用mpif90/mpicxx编译的库会报错,所以需要用
icc -openmp hello.cpp -o hello -DMPICH_IGNORE_CXX_SEEK -L/Path/to/mpi/lib/ -lmpi_mt -lmpiic -I/path/to/mpi/include 其中-DMPICH_IGNORE_CXX_SEEK为防止MPI2协议中一个重复定义问题所使用的选项,为了保证线程安全,必须使用mpi_mt库
对于intel的mpirun,必须在mpirun后加上-env I_MPI_PIN_DOMAIN omp使得每个mpi进程会启动openmp线程。
通过export OMP_NUM_THREADS来控制每个MPI产生多少线程。
shell$ ompi_info | grep "Thread support"
Thread support: posix (MPI_THREAD_MULTIPLE: yes, OPAL support: yes, OMPI progress: no, Event lib: yes)
shell$
#include <mpi.h>
int MPI_Init_thread(int *argc, char ***argv,
int required, int *provided)
argc
C/C++ only: Pointer to the number of arguments.
argv
C/C++ only: Argument vector.
required
Desired level of thread support (integer).
provided
Available level of thread support (integer).
MPI_THREAD_SINGLE
Only one thread will execute.
MPI_THREAD_FUNNELED
If the process is multithreaded, only the thread that called MPI_Init_thread will make MPI calls.
MPI_THREAD_SERIALIZED
If the process is multithreaded, only one thread will make MPI library calls at one time.
MPI_THREAD_MULTIPLE
If the process is multithreaded, multiple threads may call MPI at once with no restrictions.
3.1.6的多线程支持还在初级阶段。开销很高(虽然我不知道为什么)
学习MapReduce或者Hadoop? pthread vs openmp?
暂无
https://blog.csdn.net/Morizen/article/details/113863591
技术路线 | 描述 | 总时间 | 加速比 | 备注 |
---|---|---|---|---|
Baseline | 串行程序 | 161.7s s | 1 | |
more3omp | 前面都是可以证明的有效优化 omp_num=32 | 14.08s | ||
more3omp | 前面都是可以证明的有效优化 omp_num=64 | 11.4s | ||
deletevector | 把sz大小的3个vector,移到全局变量,但是需要提前知道sz大小/声明一个特别大的 | 10.64s | 可以看出写成全局变量也不会影响访问时间 | |
enforce_Lscan | IPCC opt 4 | 8.49s | 19 | |
enforce_Lscan_MPI_intel | intel icpc | 3.8s | 42.36 | |
Baseline2-max ppm | 1.2GB ppm 10*1024*40*1024 | 928s | ||
enforce_Lscan | Baseline2 | 43.79s | 21.2 | |
enforce_Lscan_MPI_intel | intel icpc + 双节点两个时间 + MPI(DoRGBtoLABConversion) | 18.8s / 20s | 46.4 | |
enforce_Lscan_intel | intel icpc + 单节点 | 15.8s | 58.74 | MPI(DoRGBtoLABConversion)负优化了2s |
manualSIMD | 13.9s | |||
stream | 13.6s | |||
vec2mallocOMP | 11.0s | |||
mmap | 10.6s | |||
+ -O3 | enforce_Lscan_intel | 16.2s | ||
+ -xHost | 结果不对 | 17.8s | ||
-Ofast | 16.9s | |||
-ipo | 15.9s | |||
-O3 -ipo | 16.8s | |||
-O3 -march=core-avx2 -fma -ftz -fomit-frame-pointer | 16.0s | |||
g++ suggested options | -O3 -march-znver1 -mtune=znver1 -fma -mavx2 -m3dnow -fomit-frame-pointer | 18.1s | ||
g++ suggested options2 | -O3 -march-znver2 -mtune=znver2 -fma -mavx2 -m3dnow -fomit-frame-pointer | 19.79s | ||
g++ -Ofast | 16.9s | |||
aocc -Ofast | 16.3s | |||
aocc suggested options | 16.2s |
由于是打算两节点两进程MPI,虽然没有OpenMP的共享内存,但是也希望通信能少一点。
下面关于同步区域的想法是错误的: 因为中心点移动会十分不确定,所以全部同步是最好的。
用MPI_Send写,但是一开始没注意是阻塞的,但是为什么这么慢呢?
慢了10~20倍猜测: 1. printf的原因? no 不打印也一样 2. omp_num的值不对? maybe no 3. 不在两个节点上? no 4. g++ mpicxx? no 5. 没有用IB ? 貌似也不是 6. openmpi不支持openmp ? 探究方向
好像是openmp没正常运行omp_num的值为 1,32,64时间都一样。感觉是混合编程的编译问题, 而且好像是假Openmp并行,哪里有锁的样子。突然想起来,Quest的混合变成cmake需要打开multthread类似的支持,但是这里并没用。
好像也不是mpi_init_thread的问题
果然有奇效。(结果是对的,后面我没截图了)。看到这里,可能你会觉得这个问题是OpenMPI有地方不支持openmp。但是后面有神奇的事情,如果NODELIST是fa,而不是fb就不能跑,会直接卡住。😰
首先没找到官方手册说明不同,然后研究一下这两个分区的不同。好吧从IB,cpu,内存都没区别。
限制nodelist再跑一遍。
加上打印时间,用fb分区
这个问题又没有了,但是fa分区由于经常跑可能会热一些。
由于时间已经进5s了。所以我们需要更大的例子,再讨论2节点的开销收益,之前的例子是256034000。 这里生成了1024040960的ppm.再大ppm程序的数组都申请不到栈空间了,需要重新数据结构。
重跑当前最快的enforce_Lscan
icpc + enforce_Lscan_MPI(DoRGBtoLABConversion) icpc + enforce_Lscan g++ suggested options icpc + manualSIMD + lessLscan icpc + manualSIMD + LscanSimple icpc + manualSIMD + LscanSimple + stream icpc + manualSIMD + LscanSimple + stream + mallocOMPinit icpc + manualSIMD + LscanSimple + stream + mallocOMPinit + mmap icpc + manualSIMD + LscanSimple + stream + mallocOMPinit + mmap + unrollLoop
https://www.bilibili.com/video/BV1a44y1q782 58mins-58min50s
暂无
无
技术路线 | 描述 | 总时间 | 加速比 | 备注 |
---|---|---|---|---|
Baseline | 串行程序 | 207 s | 1 | |
more3omp | 0.4+5+0.3 | 23.0s | ||
时间细划,初始化omp | 0.03+5+0.1 | 21.2s | ||
不换算法,必须加锁 | 特别满 | |||
扫描行算法 | 0.03+2.2+0.1 | 18.5s | ||
扫描行算法 + task动态线程池 | 26s | |||
扫描行算法 + task动态线程池 + 延迟发射 | 26s | |||
扫描行算法 + task动态线程池 + 延迟发射 | 26s | |||
扫描行算法 + 化解重复,提高粒度:每个线程一行,不同线程杜绝同一行扫描行算法 | 但是没并行起来 | 106s | ||
扫描行算法 + 常驻64线程 | 86s | |||
## 初始时间 | ||||
## 时间细划,初始化omp | ||||
细致划分,malloc size大小的空间不耗时,是初始化为-1耗时 | ||||
修改后 | ||||
## 改 dx4,dy4 实现访存连续性 | ||||
但是可能会导致adjlabel的值不对,导致结果不对 |
4分钟+, 满核结束不了,已经混乱了。
5分钟+,满核结束不了,大翻车。
可能的原因: 1. 本来不是计算密集型,加锁导致是串行,而且还有sz次锁的获取与释放的开销。 2. 某个线程改了nlabels,其余运行时读取可能还要同步修改。
我又想到是不是只有一个锁,有没有多个锁的实现。还是超时结束不了。
omp_set_lock(&lock[nindex]); //获得互斥器
if( 0 > nlabels[nindex] && labels[oindex] == labels[nindex] )
{
xvec[count] = x;
yvec[count] = y;
nlabels[nindex] = label;
count++;
}
omp_unset_lock(&lock[nindex]); //释放互斥器
好耶,segmentation fault (core dumped)。果然读到外面去了。
不好耶了,并行的地方加了锁,还是会
200~400行不等seg fault。
然后我打了时间戳 可以看出至少前面是正常的。 多运行几次,有时候segfault,有时corruption,我服了。 但是位置好像还是在上面的循环 每次报错位置还不一样,但是迭代的点还是对的。
https://stackoverflow.com/questions/32227321/atomic-operation-on-queuet
没办法,只能加锁,读取,写入都加锁,但是就是特别慢,4分钟+。
omp_set_lock(&lock); //获得互斥器
qindex = workq.front();
workq.pop();
omp_unset_lock(&lock); //释放互斥器
omp_set_lock(&lock2); //获得互斥器
if( 0 > nlabels[nindex] && labels[oindex] == labels[nindex] )
{
nlabels[nindex] = label;
workq2.push(nindex);
saveq.push(nindex);
}
omp_unset_lock(&lock2); //释放互斥器
q.front()变成了q.top()
扫描线算法至少比每像素算法快一个数量级。
Time taken is 16.144375 13.062605 PerformSuperpixelSegmentation_VariableSandM 循环
Time taken is 16.144399 0.000025 EnforceLabelConnectivity numlable
Time taken is 16.177300 0.032901 EnforceLabelConnectivity xvec yvec
Time taken is 48.978709 32.801409 EnforceLabelConnectivity iteration
Time taken is 49.086252 0.107543 EnforceLabelConnectivity klabelsComputing time=49086 ms
There are 86475718 points' labels are different from original file.
Time taken is 15.670141 0.000024 EnforceLabelConnectivity numlable
Time taken is 15.718014 0.047873 EnforceLabelConnectivity xvec yvec
Time taken is 22.103680 6.385666 EnforceLabelConnectivity iteration
Time taken is 22.219160 0.115480 EnforceLabelConnectivity klabelsComputing time=22219 ms
There are 0 points' labels are different from original file.
优化一下变量,快了3秒,大胜利!!!
Time taken is 16.203514 0.000029 EnforceLabelConnectivity numlable
Time taken is 16.234977 0.031463 EnforceLabelConnectivity xvec yvec
Time taken is 18.428990 2.194013 EnforceLabelConnectivity iteration
Time taken is 18.527664 0.098674 EnforceLabelConnectivity klabelsComputing time=18527 ms
There are 0 points' labels are different from original file.
虽然我在总结里写了,很难控制。但是,哎,我就是不信邪,就是玩😉
喜提segfault,打印task调用,发现task从上到下,之字形调用,而且没用一个结束的。按照设想,横向x增加比调用task快的,现在好像task堵塞的样子。
好像是没加,但是结果不对
让我们仔细分析一下是怎么偏离预期的: 1. (0,2)调用(0,3),(0,3)调用(0,4)很正常。但是(0,3)竟然调用了(2,4),这说明(0,3)循环到(1,3)时,发现(1,4)是已经处理的,而(2,4)是未处理的。进一步说明了(0,4)在被(0,3)创建了之后,先一步循环到(1,4),并将其处理。 2. (0,4)先循环到(1,4),反手还调用(1,3)。然后由于(0,3)调用了(2,4)。导致(0,4)循环到后面以为到(2,4)就截止了。 3. 虽然我说不出有什么问题,但是这不受控制的混乱调用,肯定不是我们想见的。尝试把占用时间的print去掉。时间不短(重复调用),还是错的。(后面才发现,错误是threadcount,threadq里,每次循环完忘记清空了。日~)
Time taken is 16.226124 0.000024 EnforceLabelConnectivity numlable
Time taken is 16.258697 0.032573 EnforceLabelConnectivity xvec yvec
Time taken is 26.320222 10.061525 EnforceLabelConnectivity iteration
Time taken is 26.401399 0.081177 EnforceLabelConnectivity klabelsComputing time=26401 ms
There are 86588716 points' labels are different from original file.
Time taken is 15.743455 0.000025 EnforceLabelConnectivity numlable
Time taken is 15.773654 0.030198 EnforceLabelConnectivity xvec yvec
Time taken is 26.348979 10.575326 EnforceLabelConnectivity iteration
Time taken is 26.442129 0.093150 EnforceLabelConnectivity klabelsComputing time=26442 ms
There are 0 points' labels are different from original file.
Time taken is 17.344073 0.000027 EnforceLabelConnectivity numlable
Time taken is 17.377535 0.033462 EnforceLabelConnectivity xvec yvec
Time taken is 28.461901 11.084366 EnforceLabelConnectivity iteration
Time taken is 28.544698 0.082797 EnforceLabelConnectivity klabelsComputing time=28544 ms
There are 86588716 points' labels are different from original file.
把delay的值从10调整到750,甚至是2600,大于宽度了,结果还是不对。这是不对劲的,因为这时相当于把对(x,y)一行都处理完,再发射task。
这时我才感觉到是其他地方写错了,错误是threadcount,threadq里,每次循环完忘记清空了。日~
delay = 2600 结果是对了,但是也太慢了,至少要比串行快啊?
Time taken is 15.538704 0.000026 EnforceLabelConnectivity numlable
Time taken is 15.577671 0.038968 EnforceLabelConnectivity xvec yvec
Time taken is 28.233859 12.656188 EnforceLabelConnectivity iteration
Time taken is 28.332256 0.098396 EnforceLabelConnectivity klabelsComputing time=28332 ms
delay = 20 快了一点,哭
Time taken is 15.631368 0.000025 EnforceLabelConnectivity numlable
Time taken is 15.661496 0.030128 EnforceLabelConnectivity xvec yvec
Time taken is 26.788105 11.126609 EnforceLabelConnectivity iteration
Time taken is 26.869487 0.081382 EnforceLabelConnectivity klabelsComputing time=26869 ms
There are 0 points' labels are different from original file.
打上时间戳
说明还是并行没写好。检查是否调用64核,htop显示是64核
想法很美好,但是最后的效果并不是每次64线程,基本都只有1-5个任务。导致近似单线程还有调用开销。(node6有人,node5慢些)
Time taken is 36.212269 32.876626 PerformSuperpixelSegmentation_VariableSandM 循环
Time taken is 36.212297 0.000028 EnforceLabelConnectivity numlable
Time taken is 36.247536 0.035239 EnforceLabelConnectivity xvec yvec
Time taken is 106.097341 69.849805 EnforceLabelConnectivity iteration
Time taken is 106.204154 0.106813 EnforceLabelConnectivity klabelsComputing time=106204 ms
There are 0 points' labels are different from original file.
但是如何一开始启动64个呢,我又提前不知道任务。
写完又是segFault,debug 1. [64][64][10000]太大了,每次的队列应该没这么多[64][64][100] 2. 对于结束的统计,要用同步一下,需要加critical。结果就对了 但是,这也太慢了
Time taken is 28.219408 0.000017 EnforceLabelConnectivity numlable
Time taken is 28.271994 0.052586 EnforceLabelConnectivity xvec yvec
Time taken is 83.591540 55.319546 EnforceLabelConnectivity iteration
Time taken is 83.696990 0.105450 EnforceLabelConnectivity klabelsComputing time=83696 ms
There are 0 points' labels are different from original file.
没时间研究
没时间研究
感觉要自己写个结构体 1. 数据可以无序 2. 最好数据各异 3. 支持并行读每个元素(数组? 4. 支持并行写一堆元素,并且维护size大小
暂无
在这次并行中,让我意识到几点 1. 任务的划分一定不能重复,相互干扰。比如,四邻域泛洪任务重复会导致竞争问题,需要加锁。但是,描绘线,任务不重复,直接避免了加锁的低效。而且重复会导致计算重复,同时占用线程。 2. 并行任务的结果,如果不是一定要存在同一个变量就分开存,既不需要线程私有变量,最后归约;也不会存同一个位置导致竞争。比如,这次的任务会产生一堆不相关的index,那直接每个线程一个数组存,既不会冲突,之后还能接着并行。或者用更大的sz大小数组存index,结果更不会冲突了。 3. 对于任务数增加且不确定的情况,不推荐使用task进程。因为自动调度很难控制,既不知道迭代了多少,也不确定之后会不会有隐藏的竞争。 推荐类似双队列的调度,确定一批任务,并行一批任务,同步一批任务的结果,然后重新并行。 1. 问题:中间并行一批任务的时候还是记得分开存结果。同步的时候再处理一下就行。 2. 双队列可能有任务量过少的问题,导致变单线程。 3. 想到了一种启动64常驻线程,产生任务又等待任务的结构。但是问题是:任务的保存要满足产生任务的写入和处理任务的读取。在不考虑写爆的情况(循环),维护数组的写入与读取位置是可行的。任务的结束通过每个线程在读取不到任务时,判断自己发布的所有任务也被完成了,标记自己发布的任务完成了。所有发布的任务都完成,再结束。
好吧,我感觉我分析了一堆,就是在放屁。还是串行快,这个问题就难划分。就不是并行的算法。
这次编程遇到的问题,大多数如下:
无
需要: 1. 知道总进程/线程数, 2. 增加任务的api 3. 队列
网上的实现c++ : https://zhuanlan.zhihu.com/p/95819747
不知道 #pragma omp parallel for num_threads(ndata) schedule(dynamic)行不行
这个动态调度,和openmp的线程池的概念,让我感觉应该是有线程动态调度池的概念的,因为只要有个for子句加任务的api。但是for指令在进行并行执行之前,就需要”静态“的知道任务该如何划分。
for和sections指令的”缺陷“:无法根据运行时的环境动态的进行任务划分,必须是预先能知道的任务划分的情况。
所以OpenMP3.0提供task指令,主要适用于不规则的循环迭代和递归的函数调用。OpenMP遇到了task之后,就会使用当前的线程或者延迟一会后使用其他的线程来执行task定义的任务。
#pragma omp parallel num_threads(2)
{
#pragma omp single
{
for(int i = 0;i < N; i=i+a[i])
{
#pragma omp task
task(a[i]);
}
}
}
#pragma omp single
{
i = 0;
while (i < p.n)
{
for (; i < p.n; ++i)
{
#pragma omp task
DoSomething(p, i);
}
#pragma omp taskwait
#pragma omp flush
}
}
int count(1);
#pragma omp parallel num_threads(64)
{
#pragma omp single
{
int c = 0;
while(c < count)
{
for( ; c < count; c++ )
{
#pragma omp task{
for( int n = 0; n < 4; n++ )
{
int x = xvec[c] + dx4[n];
int y = yvec[c] + dy4[n];
if( (x >= 0 && x < width) && (y >= 0 && y < height) )
{
int nindex = y*width + x;
if( 0 > nlabels[nindex] && labels[oindex] == labels[nindex] )
{
xvec[count] = x;
yvec[count] = y;
nlabels[nindex] = label;
count++;
}
}
}
}
}
#pragma omp taskwait
#pragma omp flush
}
}
}
python的多进程里有动态进程管理
我感觉,意义在于对于完全不相关的,或者没有顺序关系的任务,可以用池调度来并行。
实现每个线程执行完全不同的任务
#include <iostream>
#include <functional>
#include <vector>
using namespace std;
void fun (int a, int b)
{
cout<< "fun exec :"<< a << '+' << b << '=' << a + b <<endl;
}
class C{
private:
float m_c = 2.0f;
public:
void mp( float d)
{
cout<<"c::mp exec :"<< m_c << 'x' << d << '=' << m_c * d <<endl;
}
};
int main(int argc, char * argv[])
{
const int task_groups = 5;
C c [task_groups];
vector<function<void (void) > > tasks;
for (int i=0;i<task_groups;++i)
{
tasks.push_back(bind( fun , 10, i * 10 ) );
tasks.push_back(bind( &C::mp , &c[i], i*2.0f ) );
tasks.push_back(bind(
[=] (void) {cout << "lambada :" <<i << endl; }
) );
}
size_t sz = tasks.size();
#pragma omp parallel for
for (size_t i=0;i<sz;++i)
{
tasks[i]();
}
return 0;
}
输出:
fun exec :10+0=10
c::mp exec :2x0=0
lambada :0
fun exec :10+10=20
c::mp exec :2x2=4
lambada :1
fun exec :10+20=30
c::mp exec :2x4=8
lambada :2
fun exec :10+30=40
c::mp exec :2x6=12
lambada :3
fun exec :10+40=50
c::mp exec :2x8=16
lambada :4
当然可以根据 num_threads 和 omp_get_thread_num()实现不同线程执行完全不同类型任务
#pragma omp parallel num_threads(2)
{
int i = omp_get_thread_num();
if (i == 0){
do_long(data1, sub_threads);
}
if (i == 1 || omp_get_num_threads() != 2){
do_long(data2, sub_threads);
}
}
void do_long(int threads) {
#pragma omp parallel for num_threads(threads)
for(...) {
// do proccessing
}
}
int main(){
omp_set_nested(1);
int threads = 8;
int sub_threads = (threads + 1) / 2;
#pragma omp parallel num_threads(2)
{
int i = omp_get_thread_num();
if (i == 0){
do_long(data1, sub_threads);
}
if (i == 1 || omp_get_num_threads() != 2){
do_long(data2, sub_threads);
}
}
return 0;
}
openmp 对不同的子句的关系种类没弄清。
暂无
对于for循环次数增加的情况,这么处理呢。
OpenMP由于是fork/join结构,fork的线程数可以一开始设置,但是for循环任务总数是一开始固定的吗?还是可以中途增加,
https://www.it1352.com/359097.html
https://blog.csdn.net/gengshenghong/article/details/7004594
简单bfs是每次从队列中取出当前的访问节点后,之后就将它的邻接节点加入到队列中,这样明显不利于并行化。
使用了两个队列,第一个队列存当前同一层的节点,第二个队列用来存储当前层节点的所有邻接节点(下一层节点),等到当前层的节点全部访问完毕后,再将第一个队列与第二个队列进行交换,即可。
这样做的优势在于便于以后的并行化。同一层的节点可以一起运行,不会受到下一层节点的干扰。
但是写有问题,并行计算要储存下一层节点时,push操作不是原子的,需要加锁。
q1.push(1);
distance[1]=0;
while(!q1.empty()){
clear(q2);
for(int i=0;i<q1.size();i++){
int node=q1.front();
q1.pop();
cout<<node<<"->";
int start_edge=g.outgoing_starts[node];
int end_edge=(node==g.num_nodes-1)?g.num_edges:g.outgoing_starts[node+1];
for(int j=start_edge;j<end_edge;j++){
int outgoing=g.outgoing_edges[j];
if(distance[outgoing]==-1){
distance[outgoing]=1;
q2.push(outgoing);
}
}
}
swap(q1, q2);
}
思想和实现一一样,但是对并行时竞争情况有处理。
思想:都是将并行处理的一层节点bag的相邻节点存到下一次处理的bag中。
竞争:
最后还是要加锁,屮。
辅助数据结构,称为 "pennant", 是一个\(2^k\)个节点的特殊树。根只有个left子节点,该子节点有个完整是二叉树。
通过这个辅助数据结构,可以实现动态无序集的 bag 数据结构
注意,普通的k层完全二叉树,有\(\(1+2+4+2^{k-1}=2^k-1\)\)个节点。
bag是pennant的集合,其中没有相同大小的pennant。PBFS采用固定大小的数组S[0...r]称作 backbone 骨干。 数组中的第k元素S[k]为空或者保存着指向大小为\(2k\)的pennant。可知,整个数组S[0...r]最多保存\(2\)元素。 所以BAG-CREATE分配了保存空指针固定大小的数组backbone,会花费Θ(r)时间。
This bound can be improved to O(1) by keeping track of the largest nonempty index in the backbone.
BAG-Insert类似二进制计数器递增的过程
BAG-INSERT(S,x)
1 k = 0
2 while S[k] != NULL
3 x = PENNANT-UNION(S[k],x)
4 S[k++] = NULL
5 S[k] = x #不执行循环 S[0] = x,不是在图7这种插入。
其中FA(x,y,z)相当于三个数的加法,c是进位,s是余位。
BAG-CREATE O(1) BAG-INSERT(n) O(1)-O(lgn) BAG-UNION(n) ~O(lgn)
暂无
PBFS: 1. 11行为什么是lg呢,全部遍历不好吗? 1. 因为存的是\(2^r\)的元素
This bound can be improved to O(1) by keeping track of the largest nonempty index in the backbone.
flood fill的需要
https://www.cnblogs.com/JsonZhangAA/p/9016896.html
https://zhuanlan.zhihu.com/p/134637247
A Work-Efficient Parallel Breadth-First Search Algorithm (or How to Cope with the Nondeterminism of Reducers) from ACM SPAA 2010