C# Parallel.For的使用

2019-09-28  本文已影响0人  TauJiong

1 缘起

实验室接了一个新的项目,需要在一块3d地形上基于速度绘上颜色,而且要动态改变。由于地形比较大,mesh网格的顶点一共有一千三百多万个,即使在逻辑上优化之后也达到了一百多万个。使用传统的for循环为每个顶点赋颜色值,效率太低,最终的fps只能达到8左右,完全达不到项目要求。在老师的指导下,开始考虑多线程的方法提高效率。

2 Parallel.For

在查阅了大量网络资源之后,确定了最简单易行的方法是C#本身提供的Parallel并行计算方法。

关于Parallel.For的语法就不细说了,直接通过下面的代码见识一下并行的威力吧。

using System.Diagnostics;
using System.Threading.Tasks;
using UnityEngine;

public class testParallel : Mono Behaviour
{
    Stopwatch stopWatch = new Stopwatch();
    void Start()
    {
        stopWatch.Start();
        for (int i = 0; i < 10000; i++)
        {
            for (int j = 0; j < 60000; j++)
            {
                int sum = 0;
                sum += i;
            }
        }
        stopWatch.Stop();
        print("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms.");

        stopWatch.Reset();
        stopWatch.Start();
        Parallel.For(0, 10000, item =>
        {
            for (int j = 0; j < 60000; j++)
            {
                int sum = 0;
                sum += item;
            }
        });
        stopWatch.Stop();
        print("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms.");
    }
}

在unity中运行该代码,其结果如下图所示

可以发现,Parallel.For相较于传统的for循环运行时间加快了1s。而在本次项目中,在使用了Parallel.For的情况下,fps直接达到了23,这是一个相当大的提升了。

3 深入思考

在体验到Parallel.For的巨大优势之后,我不禁思考:

  1. 是不是所有的Parallel.For都比传统的for循环都快呢?
  2. 今后我是不是可以把所有的for循环都替换为Parallel.For呢?

带着这样的问题,我继续搜索,也发现了使用Parallel.For的一些注意事项。

3.1 Parallel.For的快是有前提的

众所周知,在实现多线程时,为了防止多个线程同时处理同一个变量而导致变量处于"薛定谔状态",引入了"锁"的概念,即在每一时刻只有获得"锁"的线程才能操作目标变量。那么如果在Parallel.For中也需要操作一个全局变量,就意味着即使这是并行计算,大家也需要排队操作全局变量,此时Parallel.For可能远远不如传统的for循环来的快。

using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading.Tasks;
using UnityEngine;

public class testParallel : MonoBehaviour
{
    Stopwatch stopWatch = new Stopwatch();
    void Start()
    {
        var obj = new Object();
        long num = 0;
        ConcurrentBag<long> bag = new ConcurrentBag<long>();

        stopWatch.Start();
        for (int i = 0; i < 10000; i++)
        {
            for (int j = 0; j < 60000; j++)
            {
                num++;
            }
        }
        stopWatch.Stop();
        print("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms.");

        stopWatch.Reset();
        stopWatch.Start();
        Parallel.For(0, 10000, item =>
        {
            for (int j = 0; j < 60000; j++)
            {
                lock (obj)
                {
                    num++;
                }
            }
        });
        stopWatch.Stop();
        print("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms.");
    }
}

在unity中运行该代码,其结果如下图所示

可以发现,Parallel.For的运行时间竟然是传统for循环运行时间的100倍!因此可以得出结论:

如果在循环中需要竞争资源,用到线程锁的话,Parallel.For未必优于传统for循环

3.2 Parallel.For的循环是无序的

由于Parallel.For中的各个循环是同时进行的,所以每次循环体执行的时间会有些许差异,这就导致了循环的运行是无序的。

using System.Threading.Tasks;
using UnityEngine;

public class testParallel : MonoBehaviour
{
    void Start()
    {
        Parallel.For(0, 5, item =>
        {
            print(item);
        });
    }
}

在unity中运行该代码,其结果如下图所示

因此可以得出结论:

如果循环的执行顺序需要严格控制的话,则不能使用Parallel.For

4 总结

在计算机多核处理器普及的前提下,合理的使用多线程或者并行能够显著提高软件的运行效率。当然,这一切都是有前提的。滥用多线程往往会适得其反,不仅得不到理想的输出结果,甚至会干扰其他程序的正常运行。在明确了自己的需求的前提下,选择合适的方法才是最重要的。

上一篇下一篇

猜你喜欢

热点阅读