为什么D中的并行代码扩展得如此糟糕? 这是我在C++和D.中执行的一个比较实验,我用两种语言实现了一种算法(网络中的并行检测标签传播方案),使用相同的设计:并行迭代器得到句柄函数(通常是闭包),并将其应用于图中的每个节点。p>
这是D中的迭代器,使用为什么D中的并行代码扩展得如此糟糕? 这是我在C++和D.中执行的一个比较实验,我用两种语言实现了一种算法(网络中的并行检测标签传播方案),使用相同的设计:并行迭代器得到句柄函数(通常是闭包),并将其应用于图中的每个节点。p>,c++,performance,parallel-processing,d,C++,Performance,Parallel Processing,D,这是D中的迭代器,使用std.parallelism中的taskPool实现: /** * Iterate in parallel over all nodes of the graph and call handler (lambda closure). */ void parallelForNodes(F)(F handle) { foreach (node v; taskPool.parallel(std.range.iota(z))) { // call he
std.parallelism
中的taskPool
实现:
/**
* Iterate in parallel over all nodes of the graph and call handler (lambda closure).
*/
void parallelForNodes(F)(F handle) {
foreach (node v; taskPool.parallel(std.range.iota(z))) {
// call here
handle(v);
}
}
这是传递的句柄函数:
auto propagateLabels = (node v){
if (active[v] && (G.degree(v) > 0)) {
integer[label] labelCounts;
G.forNeighborsOf(v, (node w) {
label lw = labels[w];
labelCounts[lw] += 1; // add weight of edge {v, w}
});
// get dominant label
label dominant;
integer lcmax = 0;
foreach (label l, integer lc; labelCounts) {
if (lc > lcmax) {
dominant = l;
lcmax = lc;
}
}
if (labels[v] != dominant) { // UPDATE
labels[v] = dominant;
nUpdated += 1; // TODO: atomic update?
G.forNeighborsOf(v, (node u) {
active[u] = 1;
});
} else {
active[v] = 0;
}
}
};
C++11实现几乎相同,但使用OpenMP进行并行化。那么缩放实验显示了什么呢
在这里,我检查弱伸缩性,将输入图形大小加倍,同时将线程数加倍,并测量运行时间。理想的情况是一条直线,但并行性当然有一些开销。我在main函数中使用defaultPoolThreads(nThreads)
来设置D程序的线程数。C++的曲线看起来不错,但是D的曲线看起来很糟糕。我是否在做错事w.r.t.D并行,或者这严重影响了并行D程序的可伸缩性
p、 美国编译器标志
对于D:rdmd-release-O-inline-noboundscheck
对于C++:-std=C++11-fopenmp-O3-DNDEBUG
pps。一定是出了什么问题,因为并行D实现比顺序D实现慢:
购买力平价。出于好奇,以下是两种实现的Mercurial克隆URL:
- 这很难说,因为我不完全理解您的算法应该如何工作,但看起来您的代码不是线程安全的,这导致算法运行的迭代次数超过了需要的次数
我将此添加到
PLP的末尾。运行:
writeln(nIterations);
带有1个线程nIterations=19
有10个线程nIterations=34
有100个螺纹nIterations=90
因此,正如您所看到的,它花费的时间更长并不是因为std.parallelism
的一些问题,而是因为它做了更多的工作
为什么你的代码不是线程安全的
并行运行的函数是propagateLabels
,它具有共享的、未同步的对标签的访问权
、更新的
,以及活动的
。谁知道这会导致什么奇怪的行为,但这不可能是好事
在开始评测之前,您需要将算法修复为线程安全的。正如Peter Alexander指出的,您的算法似乎是线程不安全的。为了保证线程安全,您需要消除可能同时发生在不同线程中或以未定义顺序发生的事件之间的所有数据依赖关系。一种方法是使用WorkerLocalStorage
(在std.parallelism中提供)跨线程复制一些状态,并可能在算法结束时将结果合并到一个相对便宜的循环中
在某些情况下,复制此状态的过程可以通过将算法编写为约简并使用std.parallelism.reduce
(可能与std.algorithm.map
或std.parallelism.map
结合使用)来实现自动化完成繁重的工作。如果没有openmp,性能会是什么样子?从检查结果来看,dmd编译器目前似乎不支持openmp。如果一个版本使用openmp,而另一个版本不使用openmp,我觉得这不像是苹果对苹果的比较。可能是错误的共享?你能试着在GDC下编译D程序吗?我可以证实你的发现。我使用的线程越多,它运行的时间就越长:平台Win32。我知道您使用了64位(因为我必须将一些“long”更改为“size\t”来编译它)。你用的是Win还是Nix?很好的观察。有趣的问题是:为什么D和几乎完全相同的C++实现行为如此不同?我知道线程共享标签
,活动的
和更新的
。这种情况对于C++/OpenMP实现也是一样的,在C++/OpenMP实现中,这不是一个问题。不幸的是,我不熟悉OpenMP,但它将作业分包的方式可能不同于std.parallelism,因此,OpenMP解决方案可能“只适用于”您运行事物的方式。