У меня есть программа со многими независимыми вычислениями, таким образом, я решил параллелизировать ее.
Я использую Параллель. Для/Каждый.
Результаты были хорошо для двухъядерной машины - загрузка ЦП приблизительно 80%-90% большую часть времени. Однако с двойной машиной Xeon (т.е. 8 ядер) я получаю только приблизительно 30%-40%-ю загрузку ЦП, хотя программа проводит довольно много времени (иногда больше чем 10 секунд) на параллельных разделах, и я вижу, что она использует еще приблизительно 20-30 потоков в тех разделах по сравнению с последовательными разделами. Каждый поток занимает больше чем 1 секунду для завершения, таким образом, я не вижу оснований для них для не работы параллельно - если нет проблема синхронизации.
Я использовал встроенного профилировщика VS2010, и результаты являются странными. Даже при том, что я использую блокировки только в одном месте, профилировщик сообщает, что приблизительно 85% времени программы потрачены на синхронизацию (также 5-7%-й сон, 5-7%-е выполнение, менее чем 1%-й IO).
Заблокированный код является только кэшем (словарь) получите/добавьте:
bool esn_found;
lock (lock_load_esn)
esn_found = cache.TryGetValue(st, out esn);
if(!esn_found)
{
esn = pData.esa_inv_idx.esa[term_idx];
esn.populate(pData.esa_inv_idx.datafile);
lock (lock_load_esn)
{
if (!cache.ContainsKey(st))
cache.Add(st, esn);
}
}
lock_load_esn
статический член класса текстового объекта.
esn.populate
чтения из файла с помощью отдельного StreamReader для каждого потока.
Однако, когда я нажимаю кнопку Synchronization для наблюдения то, что вызывает большую часть задержки, я вижу, что профилировщик сообщает о строках, которые являются функциональными строками входа, и не сообщает о самих заблокированных разделах.
Это даже не сообщает о функции, которая содержит вышеупомянутый код (напоминание - единственные привязывают программу) как часть блокирующегося профиля с уровнем шума 2%. С уровнем шума в 0% это сообщает обо всех функциях программы, которую я не понимаю, почему они рассчитывают как блокирующиеся синхронизации.
Таким образом, мой вопрос - что продолжается здесь?
Как может случиться так, что 85% времени потрачены на синхронизацию?
Как я узнаю то, что действительно проблема с параллельными разделами моей программы?
Спасибо.
Обновление: После развертки в потоки (использующий чрезвычайно полезный visualizer) я узнал, что большая часть времени синхронизации была проведена на ожидании потока GC для завершения выделений памяти, и что частые выделения были необходимы из-за универсальных структур данных, изменяют размер операций.
Я должен буду видеть, как инициализировать мои структуры данных так, чтобы они выделили достаточно памяти на инициализации, возможно избежав этой борьбы за поток GC.
Я сообщу о результатах позже сегодня.
Обновление: кажется, что выделения памяти были действительно причиной проблемы. Когда я использовал начальные мощности ко всем Словарям, и Списки в параллели выполнили класс, проблема синхронизации были меньшими. У меня теперь было только приблизительно 80%-е время Синхронизации, со скачками 70%-й загрузки ЦП (предыдущие скачки составляли только приблизительно 40%).
Я развернул еще больше в каждый поток и обнаружил, что теперь много вызовов к GC выделяют, были сделаны для выделения маленьких объектов, которые не были частью больших словарей.
Я решил эту проблему путем предоставления каждому потоку пул предварительно выделенных такие объекты, которые я использую вместо того, чтобы вызвать "новую" функцию.
Таким образом, я по существу реализовал отдельный пул памяти для каждого потока, но очень сырым способом, который является очень трудоемким и на самом деле не очень хорошим - я все еще должен использовать много новых для инициализации этих объектов, только теперь я делаю это однажды глобально и существует меньше конкуренции на потоке GC, имея необходимость увеличить размер пула.
Но это - определенно не решение, которое я люблю, поскольку оно не обобщено легко, и я не хотел бы писать своего собственного диспетчера памяти.
Существует ли способ сказать.NET выделять предопределенный объем памяти для каждого потока и затем брать все выделения памяти от локального пула?
Можете ли вы выделить меньше?
У меня было несколько подобных случаев, когда я смотрел на плохую производительность и обнаруживал, что в основе проблемы лежит сборщик мусора. Однако в каждом случае я обнаруживал, что случайно теряю память в каком-то внутреннем цикле, без надобности выделяя тонны временных объектов. Я бы внимательно посмотрел на код и посмотрел, есть ли выделения, которые вы можете удалить. Я думаю, что программам редко «нужно» выделять много памяти во внутренних циклах.