Подтвердить что ты не робот

Понимание результатов параллельного профилирования VS2010 С#

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

Я использую Parallel.For/Every.

Результаты были хороши для использования двухъядерной машины - загрузка процессора примерно в 80% -90% в большинстве случаев. Однако с двойной машиной Xeon (т.е. с 8 ядрами) я получаю только около 30% -40% загрузки процессора, хотя программа тратит довольно много времени (иногда более 10 секунд) на параллельные разделы, и я вижу, что она использует около 20-30 больше потоков в этих разделах по сравнению с последовательными разделами. Каждый поток занимает более 1 секунды для завершения, поэтому я не вижу причин, чтобы они не работали параллельно - если нет проблемы с синхронизацией.

Я использовал встроенный профайлер VS2010, и результаты странные. Несмотря на то, что я использую блокировки только в одном месте, профилировщик сообщает, что около 85% времени программы тратится на синхронизацию (также 5-7% спящего режима, 5-7% выполнения, менее 1% IO).

Заблокированный код - это только кеш (словарь) get/add:

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 является статическим членом класса типа Object.
esn.populate читает файл из отдельного StreamReader для каждого потока.

Однако, когда я нажимаю кнопку "Синхронизация", чтобы узнать, что вызывает наибольшую задержку, я вижу, что профилировщики строят строки, которые являются входными строками функций, и не сообщают о самих заблокированных секциях.
Он даже не сообщает о функции, которая содержит вышеуказанный код (напоминание - единственная блокировка в программе) как часть профиля блокировки с уровнем шума 2%. С уровнем шума 0% он сообщает о всех функциях программы, которые я не понимаю, почему они считают блокировкой синхронизации.

Итак, мой вопрос - что здесь происходит?
Как может быть, что 85% времени тратится на синхронизацию?
Как узнать, в чем проблема с параллельными разделами моей программы?

Спасибо.

Обновление. После сверления в потоки (используя чрезвычайно полезный визуализатор) я узнал, что большая часть времени синхронизации была потрачена на то, что поток GC завершит выделение памяти, и что частые распределения были необходимы из-за операций изменения размера общих структур данных.

Мне нужно посмотреть, как инициализировать мои структуры данных, чтобы они выделяли достаточно памяти при инициализации, возможно, избегая этой гонки для потока GC.

Я сообщу результаты позже сегодня.

Обновить. Похоже, что выделение памяти действительно стало причиной проблемы. Когда я использовал начальные возможности для всех словарей и списков в параллельном исполняемом классе, проблема синхронизации была меньше. У меня теперь было только около 80% времени синхронизации, с пиками 70% загрузки процессора (предыдущие спайки были только около 40%).

Я просверлил еще больше в каждом потоке и обнаружил, что теперь многие вызовы выделения GC были выделены для выделения небольших объектов, которые не были частью больших словарей.

Я решил эту проблему, предоставив каждому потоку пул preallocated таких объектов, который я использую вместо вызова "новой" функции.

Итак, я по существу реализовал отдельный пул памяти для каждого потока, но очень грубым способом, который очень трудоемкий и на самом деле не очень хороший - мне все еще нужно использовать много нового для инициализации этих объектов, только теперь я делаю это один раз в глобальном масштабе, и в GC поток меньше, даже когда приходится увеличивать размер пула.

Но это определенно не решение, которое мне нравится, поскольку оно не обобщается легко, и я бы не хотел писать свой собственный менеджер памяти.
Есть ли способ сказать .NET распределить предопределенный объем памяти для каждого потока, а затем взять все распределения памяти из локального пула?

4b9b3361

Ответ 1

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

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