使用 asParallel 与 async function() 迭代集合
Iterating on collection using asParallel vs async function()
在尝试了解如何使用并行性和并发性时
在对这些方法进行基准测试时,我正在比较使用三种不同方法花费大量时间的列表登录
但是这三种方法现在让我很困惑:
am asking what is going on with this code?
- 第一种方法是同步
- 第二种方法是同步用作并行
- 第三种方法是异步使用带有 yeild 的正常循环
public class LookUpCollections
{
private const int N = 3;
private readonly List<int> _list;
public LookUpCollections()
{
_list = new List<int>();
for (int i = 0; i < N; i++)
{
_list.Add(i);
}
}
[Benchmark]
public void ListLookup() => _list.ForEach(x => Thread.Sleep(TimeSpan.FromSeconds(2)));
[Benchmark]
public void ListLookupAsParallel() => _list.AsParallel<int>().ForAll((x) =>
Thread.Sleep(TimeSpan.FromSeconds(2)));
[Benchmark]
public async IAsyncEnumerable<int> ListLookupAsync()
{
foreach (var item in _list)
{
Task.Delay(TimeSpan.FromSeconds(2)); // the method will complete before 2sec delay
await Task.Delay(TimeSpan.FromSeconds(2)); // some asynchronous work
yield return item;
}
}
}
这里是benchmark results
或者这里是控制台的副本和过去,以防图像出现问题:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450
.NET Core SDK=3.1.401
[Host] : .NET Core 2.1.21
DefaultJob : .NET Core 2.1.21
Method Mean Error StdDev
|--------------------- |--------------------:|-----------------:|-----------------:|
| ListLookup | 6,027,116,440.00 ns | 6,926,407.532 ns | 6,478,965.903 ns |
| ListLookupAsParallel | 2,008,655,313.33 ns | 5,275,609.028 ns | 4,934,807.958 ns |
| ListLookupAsync | 27.47 ns | 0.611 ns | 1.632 ns |
LookUpCollections.ListLookupAsync: Default -> 6 outliers were removed (37.39 ns..40.15 ns)
// * Legends *
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
1 ns : 1 Nanosecond (0.000000001 sec)
鉴于您的评论,您对第三种方法的结果有一些疑问。我已经问过您如何调用该方法,因为如果像这样正确调用它确实需要大约 6 秒:
List<int> _list;
async Task Main()
{
const int N = 3;
_list = new List<int>();
for (int i = 0; i < N; i++)
{
_list.Add(i);
}
await foreach(var i in ListLookupAsync())
{
Console.WriteLine(i);
}
}
public async IAsyncEnumerable<int> ListLookupAsync()
{
Console.WriteLine($"{DateTime.Now} - Entering ListLookupAsync");
foreach (var item in _list)
{
await Task.Delay(TimeSpan.FromSeconds(2)); // some asynchronous work
yield return item;
}
Console.WriteLine($"{DateTime.Now} - Exiting ListLookupAsync");
}
我真的怀疑你测量错了。基本上使用 IAsyncEnumerable
类型确实与使用不可等待的可枚举具有相同的效果:iterations/yield returns 按顺序 完成。由于 _list
包含 3 个项目使用上面的代码消耗枚举需要 3 次两秒延迟。
如果你想执行基于任务的方法 并行你可以利用Task.WhenAll
:
List<int> _list;
async Task Main()
{
const int N = 3;
_list = new List<int>();
for (int i = 0; i < N; i++)
{
_list.Add(i);
}
Console.WriteLine($"{DateTime.Now} - Before WhenAll");
await Task.WhenAll(_list.Select(TaskBased));
Console.WriteLine($"{DateTime.Now} - After WhenAll");
}
public async Task TaskBased(int index)
{
await Task.Delay(TimeSpan.FromSeconds(2)); // some asynchronous work
Console.WriteLine(index);
}
这大约需要 2 秒。
但是,无论如何:在质疑基准测试的结果之前,请绝对确保您了解代码的作用,否则您可能会测量错误的东西并得出错误的结论。
在尝试了解如何使用并行性和并发性时 在对这些方法进行基准测试时,我正在比较使用三种不同方法花费大量时间的列表登录 但是这三种方法现在让我很困惑:
am asking what is going on with this code?
- 第一种方法是同步
- 第二种方法是同步用作并行
- 第三种方法是异步使用带有 yeild 的正常循环
public class LookUpCollections
{
private const int N = 3;
private readonly List<int> _list;
public LookUpCollections()
{
_list = new List<int>();
for (int i = 0; i < N; i++)
{
_list.Add(i);
}
}
[Benchmark]
public void ListLookup() => _list.ForEach(x => Thread.Sleep(TimeSpan.FromSeconds(2)));
[Benchmark]
public void ListLookupAsParallel() => _list.AsParallel<int>().ForAll((x) =>
Thread.Sleep(TimeSpan.FromSeconds(2)));
[Benchmark]
public async IAsyncEnumerable<int> ListLookupAsync()
{
foreach (var item in _list)
{
Task.Delay(TimeSpan.FromSeconds(2)); // the method will complete before 2sec delay
await Task.Delay(TimeSpan.FromSeconds(2)); // some asynchronous work
yield return item;
}
}
}
这里是benchmark results
或者这里是控制台的副本和过去,以防图像出现问题:
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.450
.NET Core SDK=3.1.401
[Host] : .NET Core 2.1.21
DefaultJob : .NET Core 2.1.21
Method Mean Error StdDev
|--------------------- |--------------------:|-----------------:|-----------------:|
| ListLookup | 6,027,116,440.00 ns | 6,926,407.532 ns | 6,478,965.903 ns |
| ListLookupAsParallel | 2,008,655,313.33 ns | 5,275,609.028 ns | 4,934,807.958 ns |
| ListLookupAsync | 27.47 ns | 0.611 ns | 1.632 ns |
LookUpCollections.ListLookupAsync: Default -> 6 outliers were removed (37.39 ns..40.15 ns)
// * Legends *
Mean : Arithmetic mean of all measurements
Error : Half of 99.9% confidence interval
StdDev : Standard deviation of all measurements
1 ns : 1 Nanosecond (0.000000001 sec)
鉴于您的评论,您对第三种方法的结果有一些疑问。我已经问过您如何调用该方法,因为如果像这样正确调用它确实需要大约 6 秒:
List<int> _list;
async Task Main()
{
const int N = 3;
_list = new List<int>();
for (int i = 0; i < N; i++)
{
_list.Add(i);
}
await foreach(var i in ListLookupAsync())
{
Console.WriteLine(i);
}
}
public async IAsyncEnumerable<int> ListLookupAsync()
{
Console.WriteLine($"{DateTime.Now} - Entering ListLookupAsync");
foreach (var item in _list)
{
await Task.Delay(TimeSpan.FromSeconds(2)); // some asynchronous work
yield return item;
}
Console.WriteLine($"{DateTime.Now} - Exiting ListLookupAsync");
}
我真的怀疑你测量错了。基本上使用 IAsyncEnumerable
类型确实与使用不可等待的可枚举具有相同的效果:iterations/yield returns 按顺序 完成。由于 _list
包含 3 个项目使用上面的代码消耗枚举需要 3 次两秒延迟。
如果你想执行基于任务的方法 并行你可以利用Task.WhenAll
:
List<int> _list;
async Task Main()
{
const int N = 3;
_list = new List<int>();
for (int i = 0; i < N; i++)
{
_list.Add(i);
}
Console.WriteLine($"{DateTime.Now} - Before WhenAll");
await Task.WhenAll(_list.Select(TaskBased));
Console.WriteLine($"{DateTime.Now} - After WhenAll");
}
public async Task TaskBased(int index)
{
await Task.Delay(TimeSpan.FromSeconds(2)); // some asynchronous work
Console.WriteLine(index);
}
这大约需要 2 秒。
但是,无论如何:在质疑基准测试的结果之前,请绝对确保您了解代码的作用,否则您可能会测量错误的东西并得出错误的结论。