Понимание результатов параллельного профилирования VS2010 C # - PullRequest
15 голосов
/ 25 мая 2010

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

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

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

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

Заблокированный код - это всего лишь кеш (словарь). 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 для выделения небольших объектов, которые не были частью больших словарей.

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

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

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

1 Ответ

4 голосов
/ 25 мая 2010

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

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

...