为何Grasshopper中C#多线程点距离计算代码比单线程慢?
多线程计算点对距离反而更慢的原因及优化方案
你的代码之所以比单线程慢,核心问题不在于多线程本身,而是代码里的几个低效设计拖垮了性能,具体分析如下:
1. x.IndexOf(pt)带来的O(n²)级开销
在Parallel.ForEach的循环体里,你通过x.IndexOf(pt)获取当前点的索引,而List.IndexOf是线性查找(O(n)复杂度)。10000个点的情况下,总操作量变成了10000×10000=1亿次,这直接把计算复杂度从单线程的O(n)拉到了O(n²),这是性能暴跌的主要原因——和多线程无关,哪怕单线程这么写也会慢得离谱。
2. ConcurrentDictionary的不必要锁开销
你用ConcurrentDictionary存储结果,但实际上这里根本不需要线程安全的字典:因为两组点是一一对应的,每个计算任务只需要操作对应索引的位置,完全没有线程竞争。ConcurrentDictionary内部的锁机制会带来额外的性能损耗,完全是冗余的。
3. 轻量计算的多线程调度开销抵消收益
Point3d.DistanceTo是非常简单的数学运算(平方差求和开根号),属于超轻量任务。多线程的线程调度、上下文切换本身有一定开销,如果每个任务的计算量太小,这些开销会超过多线程带来的并行收益,导致整体速度比单线程还慢。
优化后的代码
针对以上问题,优化后的代码如下:
private void RunScript(List<Point3d> x, List<Point3d> y, ref object A) { // 先校验两组点数量是否一致 if (x.Count != y.Count) { A = null; return; } // 用数组存储结果,每个索引位置独立,无线程竞争 double[] results = new double[x.Count]; // 按索引并行循环,避免线性查找 Parallel.For(0, x.Count, i => { results[i] = x[i].DistanceTo(y[i]); }); A = new List<double>(results); }
优化说明
- 改用索引循环:直接通过
i访问对应位置的点,彻底消除IndexOf的O(n)开销,把复杂度拉回O(n) - 用普通数组代替
ConcurrentDictionary:数组的每个索引位置由单个线程操作,不需要锁,消除线程安全容器的额外开销 - 保留并行逻辑的同时,最大化降低额外损耗:如果后续你的计算任务变得更复杂(比如每个点需要做更多几何运算),这个并行方案会开始体现性能优势;但如果只是简单的距离计算,单线程可能依然是最优选择——因为轻量任务的并行收益抵不上调度开销。
内容的提问来源于stack exchange,提问作者Charles Boman
相关产品推荐
相关产品推荐

