Проблемы производительности при запуске приложения на параллельной машине - PullRequest
0 голосов
/ 13 июля 2009

У меня действительно странная проблема:

У меня есть приложение, которое запускает несколько рабочих параллельно:

for (it = jobList.begin(); it != jobList.end(); it++) {
    DWORD threadId;
    Job job = *it;
    Worker *worker = new Worker(job);
    workers[i] = worker;
    threads[i++] = CreateThread((LPSECURITY_ATTRIBUTES)NULL, (DWORD)0, &launchThread, worker, (DWORD)0, &threadId);
}
WaitForMultipleObjects((DWORD)jobList.size(), threads, (BOOL)true, (DWORD)INFINITE);

Они распределяют кучу вещей, поэтому я предполагаю, что они синхронизируются по новому, но это единственное место, где они в конечном итоге синхронизируют друг друга.

Когда я запускал приложение на одноядерном компьютере, все было в порядке; когда я запускаю приложение на многоядерной машине, производительность становится намного хуже , хуже чем:

for (it = jobList.begin(); it != jobList.end(); it++) {
    DWORD threadId;
    Job job = *it;
    Worker *worker = new Worker(job);
    workers[i] = worker;
    threads[i++] = CreateThread((LPSECURITY_ATTRIBUTES)NULL, (DWORD)0, &launchThread, worker, (DWORD)0, &threadId);
    WaitForSingleObject(threads[i-1], (DWORD)INFINITE);
}

Кто-нибудь имеет разумное предположение, чтобы дать мне?

EDIT

Я провел несколько тестов и обнаружил, что:

  1. Изменение распределителя в соответствии с состоянием параллельного распределителя не помогает
  2. Результаты многопоточного приложения лучше на машине с ядром 2 дуэта (два ядра с общим кешем L2), чем с двойным Xeon (два процессора с разными кешами).

Я думаю, что у меня в руках есть приложение с узким местом доступа к памяти, но ... Как я могу проверить, действительно ли это проблема, или мне стоит поискать в других местах?

Ответы [ 4 ]

1 голос
/ 14 июля 2009

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

1 голос
/ 13 июля 2009

Ваш индивидуальный Job s также звонит new? new почти всегда потокобезопасен, но обычно эта безопасность приводит к огромному снижению производительности (да, в этой статье говорится о malloc, но (1) те же проблемы чумы new, и ( 2) new часто реализуется с использованием malloc).

У вас есть потенциальная утечка памяти, если вызов new Worker(job) завершится неудачно, код очистки отсутствует. Конечно, вы, возможно, удалили этот код, чтобы опубликовать пример, или это может быть целая программа, и вы полагаетесь на операционную систему для очистки. Вы можете рассмотреть возможность использования Scope Guard -подобного решения для этого.

В целом, я бы порекомендовал взглянуть на что-то вроде Intel Threading Building Blocks или Windows пул потоков . Они должны обрабатывать множество других хитрых деталей (сколько потоков создавать, как правильно планировать, какие данные им давать, чтобы избежать ошибок кэша и т. Д.).

1 голос
/ 14 июля 2009

Функции Interlocked * могут иногда выходить за рамки радара при поиске синхронизации. Они довольно быстрые, но они вызывают некоторую синхронизацию и обновление кэша процессора, что замедляет работу. При этом, возможно, придется использовать эти функции много , чтобы получить влияние, которое вы описываете.

Не имея дополнительных подробностей, я бы предложил профилировать рабочие потоки, углубиться в длинные части кода и в конечном итоге определить узкое место. Учитывая значительный эффект, узкое место должно быть заметным. Если вы нашли место, но не указали причину, обновите свой вопрос, указав фактическую часть кода, вызвавшую проблему.

0 голосов
/ 13 июля 2009

сколько потоков вы создаете и сколько у вас ядер? Все, что превышает 2 потока на ядро, значительно снижает производительность.

Обычно при решении такой проблемы вы настраиваете пул потоков (то есть максимум 4 потока для двухъядерного компьютера), а затем назначаете каждому потоку «задание». Когда это задание завершается, вы выталкиваете следующее задание из списка заданий, и поток продолжает обрабатывать новое задание. Вы делаете это, пока каждая работа не будет обработана. Имейте в виду, что поток в пуле может ПРЯМО извлечь следующее задание из списка заданий, вместо того, чтобы рабочий поток назначил ему следующее задание.

Рабочий процесс выглядит следующим образом.

1) Главный поток создает несколько рабочих потоков.
2) Основной поток заполняет список заданий.
3) Рабочий поток проверяет, есть ли еще задания, и выбирает следующую из списка.
4) Перейдите к 3, если остались рабочие места.
5) Завершить (или вернуться к 2 в зависимости от ваших требований).

Существуют и другие подходы к проблеме, но приведенный выше, вероятно, наиболее эффективен и прост в реализации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...