【发布时间】:2020-12-09 22:53:02
【问题描述】:
目标
目标是计算一定数量正方形的所有可能的多边形形状。由于对于大量计算来说这是非常繁重的计算,我想利用我的计算机拥有的多个内核。
问题
我通过创建以下场景使问题更易于解释和测试:
1) for each value of 2, 3, 5, and 7:
2) find all multiples (up to a certain value) and add them to the same List
3) remove all duplicates from said list
在我的最后一个程序中,第 2 步更加庞大且计算量更大,因此我更愿意根据第 1 步的值将任务 2 拆分为我想检查的许多值。
我尝试了什么
我用 C# Core 制作了一个带有 5 个按钮的 winforms 应用程序,尝试了我在 Stackoverflow 和 Internet 上的其他地方找到的不同的并行性变体:
这是代码(看起来很多,但它只是同一想法的 5 种变体),它们都给出了一个计数来检查它们是否产生了相同的结果以及花费了多长时间:
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Parallelism
{
public partial class Form1 : Form
{
private readonly int Repeat = 10000000;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
List<int> output = new List<int>();
foreach (int x in new int[] { 2, 3, 5, 7 })
{
for (int i = 0; i < Repeat; i++)
{
output.Add(x * i);
}
}
output = output.Distinct().ToList();
watch.Stop();
(sender as Button).Text += $", c:{output.Count} - {watch.ElapsedMilliseconds}ms";
}
private void button2_Click(object sender, EventArgs e)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
ConcurrentBag<int> output = new ConcurrentBag<int>();
Task task = Task.WhenAll(
Task.Run(() => button2_Calculation(2, output)),
Task.Run(() => button2_Calculation(3, output)),
Task.Run(() => button2_Calculation(5, output)),
Task.Run(() => button2_Calculation(7, output))
);
task.Wait();
HashSet<int> output2 = new HashSet<int>(output);
watch.Stop();
(sender as Button).Text += $", c:{output2.Count} - {watch.ElapsedMilliseconds}ms";
}
private void button2_Calculation(int x, ConcurrentBag<int> output)
{
for (int i = 0; i < Repeat; i++)
{
output.Add(x * i);
}
}
private void button3_Click(object sender, EventArgs e)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
List<int> output = new List<int>();
foreach (int x in (new int[] { 2, 3, 5, 7 }).AsParallel())
{
for (int i = 0; i < Repeat; i++)
{
output.Add(x * i);
}
}
output = output.Distinct().ToList();
watch.Stop();
(sender as Button).Text += $", c:{output.Count} - {watch.ElapsedMilliseconds}ms";
}
private void button4_Click(object sender, EventArgs e)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
ConcurrentBag<int> output = new ConcurrentBag<int>();
Dictionary<int, Task> runningTasks = new Dictionary<int, Task>();
foreach (int x in new int[] { 2, 3, 5, 7 })
{
int value = x;
runningTasks.Add(x, Task.Factory.StartNew(() => button2_Calculation(value, output)));
}
foreach (Task t in runningTasks.Select(c => c.Value))
t.Wait();
HashSet<int> output2 = new HashSet<int>(output);
watch.Stop();
(sender as Button).Text += $", c:{output2.Count} - {watch.ElapsedMilliseconds}ms";
}
private void button5_Click(object sender, EventArgs e)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
ConcurrentBag<int> output = new ConcurrentBag<int>();
Parallel.ForEach(new int[] { 2, 3, 5, 7 }, x => button5_Calculation(x, output));
HashSet<int> output2 = new HashSet<int>(output);
watch.Stop();
(sender as Button).Text += $", c:{output2.Count} - {watch.ElapsedMilliseconds}ms";
}
private void button5_Calculation(int x, ConcurrentBag<int> output)
{
for (int i = 0; i < Repeat; i++)
output.Add(x * i);
}
}
}
目前的结果
到目前为止,上述所有方法都产生了 1 秒到 1.5 秒之间的相似持续时间。 实际上,有时正常的串行执行似乎要快得多。 这怎么可能?我希望使用 8 个内核(16 个虚拟内核)拆分任务会导致更快的整体速度?
非常感谢任何帮助!
未来
在了解有关如何正确实现并行性的更多信息后,我希望还在另一个线程/异步上运行整个计算,以使 GUI 保持响应。
编辑:
回复@Pac0: 这是我对您的建议的实施。它似乎没有太大区别:
private void button6_Click(object sender, EventArgs e)
{
var watch = System.Diagnostics.Stopwatch.StartNew();
ConcurrentBag<HashSet<int>> bag = new ConcurrentBag<HashSet<int>>();
var output = Parallel.ForEach(new int[] { 2, 3, 5, 7 }, x =>
{
HashSet<int> temp = new HashSet<int>();
for (int i = 0; i < Repeat; i++)
temp.Add(x * i);
bag.Add(temp);
});
HashSet<int> output2 = new HashSet<int>();
foreach (var hash in bag)
output2.UnionWith(hash);
watch.Stop();
(sender as Button).Text += $", c:{output2.Count} - {watch.ElapsedMilliseconds}ms";
}
【问题讨论】:
-
你可以对
Repeat = 100000000尝试相同的方法并查看结果吗? -
我的猜测是,由于您总是使用与并发包相同的方法来“即时”存储结果,因此会导致大量锁定,从而失去使用多线程的好处。但我可能弄错了,因为我不使用并发包进行大存储。我会更多地采用“Map / Reduce”方法:独立计算 2、3、5 和 7,然后“合并”所有结果。
-
所以基本上,为每个并行计算创建一个
HashSet,然后在所有完成后,对哈希集进行联合(这将处理重复)。 -
您似乎在假设瓶颈是
for循环:for (int i = 0; i < Repeat; i++)的情况下进行操作。您如何确认问题存在,而不是使用删除重复项的代码? -
@JoshuaRobinson,你是对的。在此示例中,最大的瓶颈是删除重复项。然而,这在我的代码的实际实现中有所不同。但我会接受有关提高重复删除效率的建议。
标签: c# visual-studio winforms asynchronous parallel-processing