Параллельное проектирование является одним из методов, используемых в приложениях с многоядерными процессорами. Вот некоторые результаты эксперимента поделиться с вами.

В чем разница между параллельным проектированием и обычным последовательным проектированием в C#.

Последовательный тип

for (int i = 0; i < 500; i++)
{
   DoSomeStuff();
}

Параллельный тип

Parallel.For(0, 500, i =>
{
   DoSomeStuff();
});

давайте проведем несколько экспериментов, чтобы увидеть, работает это или нет.

  • Проверим время обработки

в DoSomeStuff я делаю что-то, чтобы система была загружена

double DoStuffs(int cycle)
{
    var ret = .0;
    for (int i = 0; i < cycle; i++)
    {
        ret += Math.PI;
    }
    return ret;
}

Последовательный код будет состоять из 2 циклов и подсчета прошедшего времени.

var stopwatch = new Stopwatch();
stopwatch.Start();
double ret = 0;
for (int i = 0; i < 500; i++)
{
    for (int j = 0; j < 500; j++)
    {
        ret += DoStuffs(cycle);
    }
}
stopwatch.Stop();
Console.WriteLine($"{ret}, {stopwatch.ElapsedMilliseconds}");

То же, что и параллельный код, он просто заменяет первый цикл параллельным методом.

var stopwatch = new Stopwatch();
stopwatch.Start();
double ret = 0;
Parallel.For(0, 500, i =>
{
    for (int j = 0; j < 500; j++)
    {
        ret += DoStuffs(cycle);
    }
});
stopwatch.Stop();
Console.WriteLine($"{ret}, {stopwatch.ElapsedMilliseconds}");

Прошедшее время результатов, как показано ниже

╔═══════════╦════════════════════╦═══════════════╗
║ Cycling   ║  Sequential (ms)   ║ Parallel(ms)  ║
╠═══════════╬════════════════════╬═══════════════╣
║ 100       ║     98             ║     245       ║
║ 500       ║     432            ║     827       ║
║ 1000      ║     1131           ║     425       ║
║ 5000      ║     4214           ║     1133      ║
╚═══════════╩════════════════════╩═══════════════╝

При времени цикла ‹ 500 последовательное время даже лучше, чем параллельное. причина в том, что DoSomeStuff только получает данные Math.PI и суммирует их. Параллельному методу даже нужны ресурсы для подготовки потока к обработке. Давайте попробуем еще что-нибудь, сделаем DoSomeStuff более загруженным и проверим еще раз.

double DoStuffs(int cycle)
{
    var ret = .0;
    for (int i = 0; i < cycle; i++)
    {
        ret += Math.Sin(i) + Math.Cos(i);
    }
    return ret;
}
╔═══════════╦════════════════════╦═══════════════╗
║ Cycling   ║  Sequential (ms)   ║ Parallel(ms)  ║
╠═══════════╬════════════════════╬═══════════════╣
║ 100       ║     792            ║     655       ║
║ 500       ║     3486           ║     1116      ║
║ 1000      ║     6097           ║     1886      ║
║ 5000      ║     24915          ║     10674     ║
╚═══════════╩════════════════════╩═══════════════╝

Параллельный метод за 100 циклов уже ниже, чем последовательный.

Давайте проверим состояние процессора, сначала я делаю последовательный запуск. После завершения метода система подождет 5 секунд, а затем выполнит Parallel one.

Что касается использования ЦП, первые два пика связаны с последовательным, а другой пик - с параллельным. Оба метода вызывают высокий пик, однако параллельный метод может иметь более короткий временной эффект.

Вы должны подумать, что также нельзя изменить второй цикл как параллельный тип, как показано ниже:

Parallel.For(0, 500, i =>
{
    Parallel.For(0, 500, j=>
    {
        ret += DoStuffs(cycle);
    });
});

В принципе, он уже не слишком сильно отличается от одного типа Parallel. Система уже оптимизирована в вашем первом параллельном типе.

Там есть точка требует очень тщательно. Если вы проведете больше тестов, вы можете найти «возврат», который не совпадает между последовательным и параллельным.

Давайте попробуем «DoStuffs» и попробуем. В результате я получаю тот же результат только при первом запуске.

double DoStuffs(int cycle)
{
    var ret = .0;
    for (int i = 0; i < cycle; i++)
    {
        ret = 1;
    }
    return ret;
}
╔═══════════╦════════════════════╦═══════════════╗
║ Cycling   ║  Sequential (ret)  ║ Parallel(ret) ║
╠═══════════╬════════════════════╬═══════════════╣
║ 2         ║     250000         ║    250000     ║
║ 2         ║     250000         ║    104705     ║
║ 2         ║     250000         ║    108740     ║
║ 2         ║     250000         ║    89855      ║
╚═══════════╩════════════════════╩═══════════════╝

Такое поведение связано с тем, что цикл имеет зависимость от данных. Когда разные потоки работают параллельно, это может вызвать «состояние гонки» для «рет».

Чтобы решить эту проблему, вы можете добавить «блокировку» для защиты данных, как показано ниже:

Parallel.For(0, 500, i =>
{
    for (int j = 0; j < 500; j++)
    {
        var data = DoStuffs(cycle);
        lock(_lock)
        {
            ret += data;
        }
    }
});

Надеюсь, эта информация полезна для вас, наслаждайтесь ~