最近,我回答了一个关于优化一种可能可并行化的方法以生成任意基数数字的排列的问题。我发表了类似于并行化,低效实现代码块列表的答案,并有人几乎立即指出:
这几乎肯定会给你带来虚假共享,并且可能会慢很多倍。(归功于gjvdkamp)
他们说得对,它运行得非常慢。尽管如此,我研究了这个话题,并找到了一些有趣的材料和建议(仅存档的 MSDN 杂志,.NET Matters: False Sharing),可以用于解决这个问题。如果我理解正确,当线程访问连续内存时(比如,可能支持那个ConcurrentStack
的数组),虚假共享可能会发生。
对于水平线以下的代码,Bytes
是:
struct Bytes {
public byte A; public byte B; public byte C; public byte D;
public byte E; public byte F; public byte G; public byte H;
}
为了进行自己的测试,我希望能够运行一个并行版本,并且确实更快,因此我基于原始代码创建了一个简单的示例。 6
作为 limits[0]
是我的懒惰选择——我的电脑有6个核心。
单线程块 平均运行时间:10s0059ms
var data = new List<Bytes>();
var limits = new byte[] { 6, 16, 16, 16, 32, 8, 8, 8 };
for (byte a = 0; a < limits[0]; a++)
for (byte b = 0; b < limits[1]; b++)
for (byte c = 0; c < limits[2]; c++)
for (byte d = 0; d < limits[3]; d++)
for (byte e = 0; e < limits[4]; e++)
for (byte f = 0; f < limits[5]; f++)
for (byte g = 0; g < limits[6]; g++)
for (byte h = 0; h < limits[7]; h++)
data.Add(new Bytes {
A = a, B = b, C = c, D = d,
E = e, F = f, G = g, H = h
});
并行化的,糟糕的实现 平均运行时间:81秒729毫秒,~8700个争用
var data = new ConcurrentStack<Bytes>();
var limits = new byte[] { 6, 16, 16, 16, 32, 8, 8, 8 };
Parallel.For(0, limits[0], (a) => {
for (byte b = 0; b < limits[1]; b++)
for (byte c = 0; c < limits[2]; c++)
for (byte d = 0; d < limits[3]; d++)
for (byte e = 0; e < limits[4]; e++)
for (byte f = 0; f < limits[5]; f++)
for (byte g = 0; g < limits[6]; g++)
for (byte h = 0; h < limits[7]; h++)
data.Push(new Bytes {
A = (byte)a,B = b,C = c,D = d,
E = e,F = f,G = g,H = h
});
});
并行化,?? 实现 平均运行时间:5秒833毫秒,92次竞争
var data = new ConcurrentStack<List<Bytes>>();
var limits = new byte[] { 6, 16, 16, 16, 32, 8, 8, 8 };
Parallel.For (0, limits[0], () => new List<Bytes>(),
(a, loop, localList) => {
for (byte b = 0; b < limits[1]; b++)
for (byte c = 0; c < limits[2]; c++)
for (byte d = 0; d < limits[3]; d++)
for (byte e = 0; e < limits[4]; e++)
for (byte f = 0; f < limits[5]; f++)
for (byte g = 0; g < limits[6]; g++)
for (byte h = 0; h < limits[7]; h++)
localList.Add(new Bytes {
A = (byte)a, B = b, C = c, D = d,
E = e, F = f, G = g, H = h
});
return localList;
}, x => {
data.Push(x);
});
很高兴我已经得到一个比单线程版本更快的实现。我原本希望结果能够接近10秒/6或1.6秒左右,但这可能是一个天真的期望。
我的问题是对于实际比单线程版本更快的并行化实现,是否存在可以应用于该操作的进一步优化?我想了解与并行化相关的优化,而不是用于计算值的算法改进。具体而言:
- 我知道将其存储为
struct
并进行填充的优化,但它与并行化无关(还是有关系的?) - 我知道使用Ripple-Carry加法器可以进行惰性求值,但与
struct
优化相同。