让我们考虑一个简单的编程挑战: 对大数组中的所有元素求和. 现在可以通过使用并行性来轻松优化这一点, 特别是对于具有数千或数百万个元素的巨大阵列, 还有理由认为, 并行处理时间应该与常规时间除以 CPU 核心数一样多. 事实证明, 这一壮举并不容易实现. 我将向您展示几种并行执行此操作的方法, 它们如何改善或降低性能以及以某种方式影响性能的所有细节.
简单的循环方法
- private const int ITEMS = 500000;
- private int[] arr = null;
- public ArrayC()
- {
- arr = new int[ITEMS];
- var rnd = new Random();
- for (int i = 0; i <ITEMS; i++)
- {
- arr[i] = rnd.Next(1000);
- }
- }
- public long ForLocalArr()
- {
- long total = 0;
- for (int i = 0; i < ITEMS; i++)
- {
- total += int.Parse(arr[i].ToString());
- }
- return total;
- }
- public long ForeachLocalArr()
- {
- long total = 0;
- foreach (var item in arr)
- {
- total += int.Parse(item.ToString());
- }
- return total;
- }
只需要迭代循环就可以计算出结果, 超级简单, 这里没有用直接相加求出结果, 原因是直接求出结果, 发现每次基本的运行都比并行快, 但是实际上, 并行处理没有那么简单, 所以这里的加法就简单的处理下 total += int.Parse(arr[i].ToString()). 现在, 让我们尝试用并行性来打败数组迭代吧.
首次尝试
- private object _lock = new object();
- public long ThreadPoolWithLock()
- {
- long total = 0;
- int threads = 8;
- var partSize = ITEMS / threads;
- Task[] tasks = new Task[threads];
- for (int iThread = 0; iThread < threads; iThread++)
- {
- var localThread = iThread;
- tasks[localThread] = Task.Run(() =>
- {
- for (int j = localThread * partSize; j <(localThread + 1) * partSize; j++)
- {
- lock (_lock)
- {
- total += arr[j];
- }
- }
- });
- }
- Task.WaitAll(tasks);
- return total;
- }
请注意, 您必须使用 localThread 变量来 "保存" 该 iThread 时间点的值. 否则, 它将是一个随着 for 循环前进而变化的捕获变量. 当数据最后打的时候并行已经比普通的快了, 但是发现快的不多, 说明还可以优化
再次优化
- public long ThreadPoolWithLock2()
- {
- long total = 0;
- int threads = 8;
- var partSize = ITEMS / threads;
- Task[] tasks = new Task[threads];
- for (int iThread = 0; iThread < threads; iThread++)
- {
- var localThread = iThread;
- tasks[localThread] = Task.Run(() =>
- {
- long temp = 0;
- for (int j = localThread * partSize; j <(localThread + 1) * partSize; j++)
- {
- temp += int.Parse(arr[j].ToString());
- }
- lock (_lock)
- {
- total += temp;
- }
- });
- }
- Task.WaitAll(tasks);
- return total;
- }
增加设置临时变量, 减少 lock 次数, 发现运行效果已经有质的提高, 提高了几倍. 忽然想起, 有个 Parallel.For 的方法, 研究性能是否可以更快.
Parallel.For 优化
- public long ParallelForWithLock()
- {
- long total = 0;
- int parts = 8;
- int partSize = ITEMS / parts;
- var parallel = Parallel.For(0, parts, new ParallelOptions(), (iter) =>
- {
- long temp = 0;
- for (int j = iter * partSize; j <(iter + 1) * partSize; j++)
- {
- temp += int.Parse(arr[j].ToString());
- }
- lock (_lock)
- {
- total += temp;
- }
- });
- return total;
- }
运行结果比普通迭代快, 但是没有 ThreadPool 快, 但是觉得 Parallel.For 还可以继续优化, 也许可以更快
Parallel.For 继续优化
- public long ParallelForWithLock2()
- {
- long total = 0;
- int parts = 8;
- int partSize = ITEMS / parts;
- var parallel = Parallel.For(0, parts,
- localInit: () => 0L, // Initializes the "localTotal"
- body: (iter, state, localTotal) =>
- {
- for (int j = iter * partSize; j <(iter + 1) * partSize; j++)
- {
- localTotal += int.Parse(arr[j].ToString());
- }
- return localTotal;
- },
- localFinally: (localTotal) => { total += localTotal; });
- return total;
- }
运行效果已经很快, 和 ThreadPool 优化过的差不多, 有些时候更快
结论和总结
并行化优化肯定可以提高性能, 但是这取决于很多因素, 每个案例都应该进行测量和检查.
当各种线程需要通过某种锁定机制相互依赖时, 性能会显着降低.
50 万数据运行结果
来源: https://www.cnblogs.com/zhao123/p/11064107.html