Понимание параллели VS2010 C# профильные результаты

У меня есть программа со многими независимыми вычислениями, таким образом, я решил параллелизировать ее.

Я использую Параллель. Для/Каждый.

Результаты были хорошо для двухъядерной машины - загрузка ЦП приблизительно 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 выделять предопределенный объем памяти для каждого потока и затем брать все выделения памяти от локального пула?

15
задан Joel Coehoorn 25 May 2010 в 16:14
поделиться

1 ответ

Можете ли вы выделить меньше?

У меня было несколько подобных случаев, когда я смотрел на плохую производительность и обнаруживал, что в основе проблемы лежит сборщик мусора. Однако в каждом случае я обнаруживал, что случайно теряю память в каком-то внутреннем цикле, без надобности выделяя тонны временных объектов. Я бы внимательно посмотрел на код и посмотрел, есть ли выделения, которые вы можете удалить. Я думаю, что программам редко «нужно» выделять много памяти во внутренних циклах.

4
ответ дан 1 December 2019 в 05:22
поделиться
Другие вопросы по тегам:

Похожие вопросы: