【问题标题】:AsParallel() or async/awaitAsParallel() 或异步/等待
【发布时间】:2015-02-05 15:29:17
【问题描述】:

假设执行一个方法CountString,给定一个字符串数组和一个int,返回长度大于该int 的字符串的数量。 如果我必须尽可能利用多核硬件,这样做是否足够:

public int CountString(string[] s, int i) 
{
  return s.AsParallel().Count( res => res.length > i);
}

或者我必须以某种方式使用任务,甚至混合任务和 PLinq??

这只是一个简单的例子,我知道这种方法对硬件性能影响不大。

我想知道这样做是否更好,使用AsParallel(),或者在方法体。

【问题讨论】:

  • blog.slaks.net/2014-12-23/parallelism-async-threading-explained 如果没有非阻塞操作(也没有 UI 线程),则不需要Task。
  • 请注意,您的工作太过琐碎,无法从并行化中受益。
  • 是的,我知道,这只是一个纯粹的例子,它必须主要考虑大量的字符串来检查......顺便说一句,我要去读那篇文章,谢谢!跨度>

标签: c# .net asynchronous task-parallel-library async-await


【解决方案1】:

编辑:

我看到您的实际问题有点误导,我会尝试回答故意的问题。具体来说,使用AsParallel 将是一个好方法,因为您实际上不需要await。由于您正在处理集合,因此 PLINQ 或 Paralle.ForEach 是一个不错的选择。当您有自然的异步 I/O 绑定操作时,请考虑使用 async-await。建议不要wrap synchronous methods with async wrappers.


如果您实际测试您的代码,您甚至可能会惊讶地发现并行这段代码实际上会对您的方法执行产生负性能成本,具体取决于大小 em> 您正在迭代的数组。

很多时候人们忘记了使用线程实际上是有开销的,即使使用线程池之外的线程也是如此。您必须进行最少的 CPU 密集型工作,才能让并行化的性能受到影响。

如果你的数组足够长,那么使用AsParallel 就足够了。没有理由添加 Task,因为 PLINQ 会很好地处理并行化。

好的,让我们实际测试一下这段代码。我将迭代一个充满 GUID 的string[]。这是代码:

主要方法:

void Main()
{
    //JIT
    Test(0);

    Test(100);
    Test(1000);
    Test(10000);
    Test(1000000);
    Test(10000000);
}

public void Test(int itemAmount)
{
    string[] strings = Enumerable.Range(0, itemAmount).Select(i => Guid.NewGuid()
                                                      .ToString()).ToArray();

    var stopWatch = Stopwatch.StartNew();
    CountStringInParallel(strings, itemAmount);
    stopWatch.Stop();
    Console.WriteLine("Parallel Call: String amount: {0}, Time: {1}", 
                                                        itemAmount, stopWatch.Elapsed);
    
    stopWatch.Restart();
    CountStringSync(strings, itemAmount);
    stopWatch.Stop();
    Console.WriteLine("Synchronous Call: String amount: {0}, Time: {1}", 
                                                        itemAmount, stopWatch.Elapsed);
}

并行和同步:

public int CountStringInParallel(string[] s, int i) 
{
    return s.AsParallel().Count( res => res.Length > i);
}

public int CountStringSync(string[] s, int i) 
{
    return s.Count(res => res.Length > i);
}

结果:

并行调用:字符串数量:100,时间:00:00:00.0000197

同步调用:字符串数量:100,时间:00:00:00.0000026


并行调用:字符串数量:1000,时间:00:00:00.0000266

同步调用:字符串数量:1000,时间:00:00:00.0000201


并行调用:字符串数量:10000,时间:00:00:00.0002060

同步调用:字符串数量:10000,时间:00:00:00.0002003


并行调用:字符串数量:1000000,时间:00:00:00.0080492

同步调用:字符串数量:1000000,时间:00:00:00.0135279


并行调用:字符串数量:10000000,时间:00:00:00.0744104

同步调用:字符串数量:10000000,时间:00:00:00.1402474

可以看到最多10000个字符串,同步的方式其实比并行的快。

【讨论】:

  • 当然这取决于s 的长度。即使对于如此微不足道的工作负载,肯定会有一个截止点,超过该截止点并行化会产生好处。
  • 为什么你认为OP的代码会有负面的表现(假设s数组很大不是很小)?
  • @YuvalItzchakov 那不同的解决方案是什么?你能具体点吗?因为我想不出一个
  • @YuvalItzchakov 我猜你忽略了 OP 的代码。 OP 正在检查字符串数组中每个字符串的 Length 属性,而不是集合的计数。如果数组恰好很大,没有比 OP 更好的方法了。
  • @Sanci 这真的取决于情况。当您在管道或生产者-消费者模式中进行处理时,查看 TPL 数据流会很有帮助。当您有 IO 绑定操作时,async-await 适合。当你有收藏时,你可以选择感觉更好的,你可以选择PLINQ或Parallel.ForEach。
【解决方案2】:

只要你不使用async-await AsParallel 就是你所需要的。没有理由直接使用任务,因为 AsParallel 会在后台为您执行此操作。

请务必记住,并行性的开销在您的情况下可能会大于并行性的附加值。为了真正提高性能的并行性,您应该处理许多项目,并且工作本身应该是不平凡的。

但是,如果您这样做,则需要使用 async-await AsParallel(以及 PLinq 的其余部分)不适合,因为它早于 TAP。您将需要使用Task.Run 来并行化处理,并使用Task.WhenAll 到await 来实现这一切。类似这样:

var tasks = items.Select(item => Task.Run(() => Process(item));
var results = await Task.WhenAll(tasks);
// process results

【讨论】:

  • 我的问题可能写得不好。我确实想问是否最好将方法声明为异步并在方法内部使用(我不知道如何)“等待”,或者像我一样使用 AsParallel()。
  • @Sanci 只要那里没有真正异步的东西,通常是 I/O,那么使用 async-await 是没有意义的。
猜你喜欢
  • 1970-01-01
  • 2023-03-12
  • 2015-09-28
  • 1970-01-01
  • 2016-07-07
  • 2016-03-25
  • 2017-10-09
相关资源
最近更新 更多