如何用Parallel.ForEach并行遍历多个Span?
C#并行遍历Span的实现方案
任务要求
把同步遍历代码改成Parallel.ForEach并行实现,全程用Span处理,而且这是热点代码(数十亿次迭代),绝对不能有内存分配。
原同步代码
for (int i4 = 0; i4 < ba_result_out.Length; i4 += c.i_key_size) { long k = BitConverter.ToInt64(spn_data.Slice(i4, c.i_key_size)); if (Dictionary.md1.ContainsKey(k)) { // 省略部分业务逻辑 } }
尝试的并行代码(报错)
ParallelOptions po = new ParallelOptions { MaxDegreeOfParallelism = 2 }; var rp = Partitioner.Create(0, ba_result_out.Length / c.i_key_size, po.MaxDegreeOfParallelism); Parallel.ForEach(rp, po, (range, loopState) => { // 编译器直接报错:Lambda里不能用Span for (int i4 = range.Item1; i4 < range.Item2; i++) { long k = BitConverter.ToInt64(spn_data.Slice(i4, c.i_key_size)); if(Dictionary.ContainsKey(k)){ // 省略部分业务逻辑 } } });
问题&疑问
- 问题:编译器不让在Lambda里捕获Span,因为Span是栈分配类型,Lambda会被包装成闭包,容易触发生命周期问题
- 疑问:能不能并行遍历多个独立的Span?
可行方案
当然可以,核心是别让Lambda捕获外部Span,而是传递内存的起始/结束索引,在Lambda内部重新构造子Span,同时保证线程安全:
最终代码
ReadOnlySpan<byte> sourceSpan = spn_data; int keySize = c.i_key_size; // 按键的数量分区,避免拆分到键的中间 var partitioner = Partitioner.Create(0, sourceSpan.Length / keySize); Parallel.ForEach(partitioner, new ParallelOptions { MaxDegreeOfParallelism = 2 }, range => { // 计算当前分区对应的字节范围 int startBytePos = range.Item1 * keySize; int endBytePos = range.Item2 * keySize; // 在Lambda内部构造当前线程要处理的子Span,完全不依赖外部闭包 ReadOnlySpan<byte> currentPartitionSpan = sourceSpan.Slice(startBytePos, endBytePos - startBytePos); // 遍历当前子Span里的每个键 for (int i = 0; i < currentPartitionSpan.Length; i += keySize) { long k = BitConverter.ToInt64(currentPartitionSpan.Slice(i, keySize)); // 注意:如果字典有写入操作,必须用ConcurrentDictionary或者加锁;只读的ContainsKey没问题 if (Dictionary.md1.ContainsKey(k)) { // 省略部分业务逻辑 } } });
关键注意点
- 避开闭包捕获Span:Lambda里直接用外部Span会触发编译错误,因为Span不能被闭包持有。通过传递索引在内部构造Span,彻底解决这个问题。
- 零内存分配:所有Span操作都是栈上的,没有堆内存分配,完全符合热点代码的性能要求。
- 线程安全:如果业务逻辑里要修改字典,必须用线程安全的
ConcurrentDictionary或者加锁;如果只是执行只读的ContainsKey操作,只要并行过程中字典不被其他线程修改,就不用额外处理。 - 分区合理性:按键的数量来分区,而不是字节数,避免把一个完整的键拆到两个分区里,逻辑更顺畅。
补充说明
Span本身支持并行访问,只要每个线程处理的是Span的不同区域,就不会有冲突。上面的方案就是把原Span拆成多个不重叠的子Span,分给不同线程并行处理,完全满足需求。
内容的提问来源于stack exchange,提问作者Yurii Palkovskii
相关产品推荐
相关产品推荐

