AsParallel() or async/await

3

假设我们要创建一个方法CountString,给定一个字符串数组和一个整数,返回长度大于该整数的字符串数量。 如果我要尽可能地利用多核硬件,只需这样做:

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

我是否需要使用任务或混合任务和PLinq来实现?

请注意,这只是一个简单的例子,我知道这种方法不会对硬件性能产生太大影响。

我在思考这样做是否更好,使用AsParallel(),还是声明方法为async并在方法体中使用await(即使我不知道如何使用)。


2
如果你没有非阻塞操作(也没有UI线程),那么你不需要使用Task。请参见:http://blog.slaks.net/2014-12-23/parallelism-async-threading-explained/ - SLaks
2
请注意,您的工作太简单了,无法从并行化中受益。 - SLaks
是的,我知道,那只是一个纯粹的例子,它必须主要考虑大量需要检查的字符串... 顺便说一下,我会去阅读那篇文章,谢谢! - Sanci
2个回答

7

编辑:

我看到你实际上的问题有点误导人,我会尝试回答你想问的问题。在这里,选择使用AsParallel是一个好的方法,因为实际上没有什么需要await的。由于你正在处理集合,PLINQ或Paralle.ForEach是一个不错的选择。当你有自然异步I/O绑定操作时,考虑使用async-await。建议不要用async包装同步方法。


如果你实际测试你的代码,你甚至会惊讶地发现,对这段代码进行并行处理实际上对你的方法执行有负面影响,这取决于你正在迭代的数组的大小。

很多时候人们忘记了使用线程实际上有开销,即使是在使用线程池之外的线程时也是如此。你必须有最少量的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

你可以看到,在处理不超过10,000个字符串时,同步方法实际上比并行方法更快。


2
这当然取决于s的长度。即使对于如此微不足道的工作量,肯定会有一个截止点超过该点并行化会产生好处。 - spender
你为什么认为OP的代码会有负面性能影响(假设s数组很大而不是非常小)? - Sriram Sakthivel
1
@YuvalItzchakov 那个不同的解决方案会是什么?你能具体说明一下吗?因为我想不出来。 - Sriram Sakthivel
1
@YuvalItzchakov 我猜你忽略了原帖中的代码。原帖中检查了字符串数组中每个字符串的“Length”属性,而不是集合的计数。如果数组很大,那么没有比原帖中所做的更好的方法了。 - Sriram Sakthivel
1
@Sanci 这真的取决于不同的情况。当您正在处理管道或生产者-消费者模式时,考虑使用TPL Dataflow可能会很有帮助。当您需要IO绑定操作时,可以选择 async-await。当您处理集合时,可以根据自己的感觉选择,您可以选择 PLINQParallel.ForEach - Yuval Itzchakov
显示剩余3条评论

1
只要你不使用async-awaitAsParallel就足够了。没有理由直接使用任务,因为AsParallel在后台已经为你完成了这个任务。
重要的是要记住,并行性具有开销,在您的情况下,这种开销可能比并行性带来的增益更大。要实际提高性能,您应该处理许多项目,并且工作本身应该是非平凡的。
然而,如果您确实需要使用async-awaitAsParallel(和PLinq的其余部分)不适用,因为它先于TAP。您需要使用Task.Run来并行处理,Task.WhenAllawait所有内容。与此类似:
var tasks = items.Select(item => Task.Run(() => Process(item));
var results = await Task.WhenAll(tasks);
// process results

我的问题可能写得不好。我想问的是,将方法声明为async并在方法内部使用(我不知道如何使用)'await'是否更好,还是像我所做的那样,使用AsParallel()? - Sanci
2
@Sanci 只要其中没有真正的异步操作,通常是 I/O 操作,就没有使用 async-await 的意义。 - i3arnon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接