Я не попробовал это (нашел его через Google). Мог бы работать? http://www.semdesigns.com/Products/Formatters/CSharpFormatter.html . Это довольно дешево на уровне 50 долларов США, но никакая доступная пробная версия.
Итак, я наконец понял, в чем проблема - и думаю было бы полезно поделиться им с сообществом SO.
Вся проблема с нелинейной производительностью была результатом единственной строки внутри метода Evaluate ()
:
var coordMatrix = new long[100];
Так как Evaluate ()
вызывается миллионы раз, это выделение памяти происходило миллионы раз. Как это часто бывает, CLR внутренне выполняет некоторую межпотоковую синхронизацию при выделении памяти - в противном случае выделение нескольких потоков может непреднамеренно перекрываться. Изменение массива с экземпляра локального метода на экземпляр класса, который выделяется только один раз (но затем инициализируется в цикле локального метода), устранило проблему масштабируемости.
Обычно это ' s антипаттерн для создания члена уровня класса для переменной, которая используется (и имеет смысл) только в рамках одного метода. Но в этом случае, поскольку мне нужна максимально возможная масштабируемость, я буду жить (и задокументировать) эту оптимизацию.
Эпилог: После того, как я внес это изменение, параллельный процесс смог выполнить 12,2 миллиона вычислений в секунду. .
PS Престижность Игорю Островскому за его актуальную ссылку на блоги MSDN, которая помогла мне идентифицировать и диагностировать проблему.
Взгляните на эту статью: http://blogs.msdn.com/pfxteam/archive/2008/08/12/8849984.aspx
В частности, ограничьте память выделения в параллельной области и внимательно проверяйте записи, чтобы убедиться, что они не происходят рядом с участками памяти, которые читают или записывают другие потоки.
Я, конечно, не ожидал бы линейной зависимости, но я бы подумал, что вы увидите больший выигрыш, чем это. Я предполагаю, что загрузка ЦП максимальна на всех ядрах. Всего пара мыслей, которые мне не приходят в голову.
Edit: Извините, я только что заметил, что вы уже ответили на оба моих вопроса.
При использовании параллельного алгоритма следует ожидать нелинейного масштабирования по сравнению с последовательным алгоритмом, поскольку существует некоторые накладные расходы при распараллеливании. (В идеале, конечно, вы хотите подойти как можно ближе.)
Кроме того, обычно есть определенные вещи, о которых вам нужно позаботиться в параллельном алгоритме, которые вам не нужны в последовательном алгоритме. Помимо синхронизации (которая действительно может затруднить вашу работу), могут произойти и другие вещи:
Насколько я могу судить, ваш текущий явный подход использует общий итератор между потоками. Это нормальное решение, если обработка сильно различается по всему массиву, но, вероятно, будут накладные расходы на синхронизацию, чтобы предотвратить пропуск элемента (получение текущего элемента и перемещение внутреннего указателя на следующий элемент должны быть атомарными операциями, чтобы предотвратить пропуск элемента).
Следовательно, это может быть Лучшая идея разделить массив, предполагая, что время обработки каждого элемента, как ожидается, будет примерно одинаковым, независимо от положения элемента. Учитывая, что у вас есть 10 миллионов записей, это означает, что поток 1 должен работать с элементами от 0 до 2 499 999, поток 2 работает с элементами от 2 500 000 до 4 999 999 и т. Д. Вы можете назначить каждому потоку идентификатор и использовать его для вычисления фактического диапазона.
Еще одно небольшое улучшение - позволить основному потоку действовать как один из потоков, выполняющих вычисления. Однако, если я правильно помню, это очень второстепенная вещь.