Цель
Цель состоит в том, чтобы вычислить все возможные полиформы определенного количества квадратов. Поскольку это очень тяжелые вычисления для большего числа, я хотел использовать несколько ядер, которые есть на моем компьютере.
Проблема
Я упростил объяснение и тестирование проблемы, создав следующий сценарий:
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.
Что я пробовал
Я сделал приложение winforms с C # Core с 5 кнопками, пробуя различные варианты параллелизма, которые я нашел здесь, в Stackoverflow, и в других местах в Интернете:
Вот код (кажется, что его много, но это всего лишь 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 виртуальных ядер) разделение задач приведет к более высокой общей скорости?
Любая помощь очень ценится!
Будущее
Узнав больше о том, как правильно реализовать параллелизм, я рассчитываю также запустить все вычисления в другом потоке/асинхронном режиме, чтобы графический интерфейс оставался отзывчивым.
РЕДАКТИРОВАТЬ:
Ответ @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
и посмотреть результаты? - person Sowmyadhar Gourishetty   schedule 20.08.2020HashSet
для параллельного вычисления, затем, как только все будет сделано, выполните объединение хэш-наборов (это позаботится о дубликатах). - person Pac0   schedule 20.08.2020for
:for (int i = 0; i < Repeat; i++)
. Как вы подтвердили, что проблема именно там, а не в коде, удаляющем дубликаты? - person Joshua Robinson   schedule 20.08.2020