VS 7.1 Release compile и несколько потоков - PullRequest
0 голосов
/ 12 июня 2009

VS версия выпуска, кажется, не правильно распараллеливает потоки, в то время как режим отладки делает. Вот краткое изложение того, что происходит.

Во-первых, для чего это стоит, вот основной кусок кода, который распараллеливается, но я не думаю, что это проблема:

       // parallelize the search

       CWinThread* thread[THREADS];
       for ( i = 0; i < THREADS; i++ ) {
           thread[i] = AfxBeginThread( game_search, &parallel_params[i],
                                       THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED );
           thread[i]->m_bAutoDelete = FALSE;
           thread[i]->ResumeThread();
       }
       for ( i = 0; i < THREADS; i++ ) {
           WaitForSingleObject(thread[i]->m_hThread, INFINITE);
           delete(thread[i]);
       }

THREADS - это глобальная переменная, которую я устанавливаю, и я перекомпилирую, если я хочу изменить количество потоков. Чтобы дать немного контекста, это игровая программа, которая ищет игровые позиции.

Вот что происходит, что не имеет смысла для меня.

Во-первых, компиляция в режиме отладки. Если я установлю THREADS на 1, то одному потоку удастся найти около 13 000 позиций. Если я установлю THREADS на 2, каждый поток будет искать около 13 000 позиций. Отлично!

Если я скомпилирую в режиме выпуска и установлю для THREADS значение 1, потоку удастся найти около 30 000 позиций, это обычное ускорение, которое я привык видеть при переходе от отладки к выпуску. Но вот кикер. Когда я компилирую с THREADS = 2, каждый поток ищет только около 15 000 позиций. Очевидно, что половина того, что делает THREADS = 1, так что эффективная компиляция релиза не дает мне никакого эффективного ускорения. (

Наблюдая за диспетчером задач, когда все это работает, при THREADS = 1 я вижу 50% загрузки процессора на моей двухъядерной машине, а когда THREADS = 2, я вижу 100% загрузки процессора. Но компиляция релиза дает мне эффективное использование процессора в размере 50%. Или что-то?!

Есть мысли? Что-то, что я должен установить на страницах свойств?


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


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

При работе в режиме отладки, если я запускаю с 1 потоком, он получает X выполненной работы. Если я запускаю 2 потока, каждый поток получает X проделанной работы. Аналогично с 3 и 4 потоками. Масштабирование идеально.

При работе в режиме выпуска происходит следующее:

С 1 потоком: он получает количество выполненной работы Y, где Y почти вдвое больше X.

С 2 потоками: каждый поток получает Y выполненной работы. Опять же, идеальное масштабирование.

С 3 потоками: 1 поток получает Y объема выполненной работы, остальные 2 потока получают 2/3 Y выполненной работы. Я потерял около 2/3 процессора, хотя один из них, по-видимому, полностью простаивает. Диспетчер задач показывает 75% загрузки процессора.

С 4 потоками: 1 поток получает Y объема выполненной работы. Другие 3 темы получают 1/2 Y объема работы. Теперь я потерял около 1,5 процессора. Диспетчер задач показывает 100% загрузки процессора.

Очевидные вопросы:

(1) Повторяя предыдущий вопрос, был ли режим отладки так хорошо масштабируется, но не релиз?

(2) Почему одно ядро ​​всегда может работать в полную силу, а другие, кажется, отваливаются? Это отсутствие симметрии вызывает беспокойство.

(3) Почему остальные падают? Пропускная способность памяти была предложена ранее, но это похоже на очень высокую цену.

Любые комментарии или идеи приветствуются. И, как всегда, спасибо!

Ответы [ 7 ]

1 голос
/ 12 июня 2009

Я думаю, вы должны использовать WaitForMultipleObjects ().

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

Есть много вещей, которые могут помешать вашей работе.

Одной из проблем может быть ложное совместное использование строк кэша.

Когда у вас есть что-то вроде:

struct data
{
   int cnt_parsed_thread[THREADS];
   // ...
};
static data;

и в самой теме:

threadFunc( int threadNum )
{
   while( !end )
   {
      // ...
      // do something
      ++data.cnt_parsed_thread[num];
   }
}

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

Эту проблему можно обойти, распространив ложно используемые данные в отдельные строки кэша.

например. :

struct data
{
   int cnt_parsed_thread[THREADS*CACHELINESIZE];
   // ...

   int& at( int k ) { return cnt_parsed_thread[k*CACHELINESIZE}; }
};

(размер CACHELINE должен составлять 64 байта (я думаю), возможно, поиграйте с этим.)

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

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

При работе в режиме отладки, если я запускаю с 1 потоком, он получает X выполненной работы. Если я запускаю 2 потока , каждый поток получает X проделанной работы. Аналогично с 3 и 4 потоками. Масштабирование идеально.

При работе в режиме выпуска происходит следующее:

С 1 потоком: он получает количество выполненной работы Y, где Y почти вдвое больше X.

С двумя потоками: Каждый поток получает Y выполненной работы. Опять же, идеальное масштабирование.

С 3 потоками: 1 поток получает Y объема выполненной работы, остальные 2 потока получают 2/3 Y выполненной работы. Я потерял около 2/3 процессора, хотя один из них, по-видимому, полностью простаивает. Диспетчер задач показывает 75% загрузки процессора.

С 4 потоками: 1 поток получает Y выполненной работы. Другие 3 темы получают 1/2 Y объема работы. Теперь я потерял около 1,5 процессора. Диспетчер задач показывает 100% загрузки процессора.

Очевидные вопросы:

(1) Повторяя предыдущий вопрос, был ли режим отладки так хорошо масштабируется, но не выпуск?

(2) Почему одно ядро ​​всегда может работать в полную силу, а остальные, кажется, отваливаются? Это отсутствие симметрии вызывает беспокойство.

(3) Почему остальные падают? Пропускная способность памяти была предложена ранее, но это кажется очень высокой ценой.

Любые комментарии или идеи приветствуются. И, как всегда, спасибо!

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

Мне кажется подозрительным тот факт, что вы получаете 30 тыс. Позиций из 1 и 2 потоков. Может ли этот предел поступить от другого компонента в вашей системе? Вы упоминаете, что каждый поток полностью независим, но случайно ли вы используете какую-либо из функций Interlocked *? Они выглядят невинно, но на самом деле они вызывают синхронизацию всех кэшей ЦП, что может быть болезненно при попытке выжать максимум из ЦП.

Что бы я сделал, чтобы каждый поток выполнял какое-то фиктивное действие (манипулирование строками или около того), просто чтобы потратить некоторое время. Если это хорошо масштабируется, добавьте часть реального кода потока в фиктивное действие и протестируйте снова. Повторяйте, пока производительность не перестанет масштабироваться, что означает, что последнее добавление кода является узким местом.

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

0 голосов
/ 12 июня 2009

Как сказал Чарльз Бейли, из вашего описания кажется, что вы устанавливаете ограничение по времени для потока.

Может случиться так, что используемый вами механизм синхронизации ссылается на время настенных часов в режиме отладки и время ЦП (которое суммируется по всем используемым процессорам / ядрам) в режиме выпуска. Таким образом, когда THREADS = 2 в режиме выпуска, вы используете общее выделение процессорного времени вдвое быстрее, выполняя вдвое меньше работы на каждом ядре.

Просто идея. Можете ли вы дать более подробную информацию о вашем механизме синхронизации?

0 голосов
/ 12 июня 2009

Я не уверен, что понимаю, почему в Debug vs. Release ищется разное количество позиций. Вы ожидаете завершения потоков, так что я бы ожидал, что версия выпуска завершится быстрее, но обе версии приведут к одинаковым результатам.

Вы устанавливаете ограничение по времени для потока? Если да, то каков механизм этого?

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

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

0 голосов
/ 12 июня 2009

Проблема с многопоточностью заключается в том, что она недетерминирована.

Прежде всего, цель DEBUG не оптимизирует код. Он также добавляет дополнительный код для проверки во время выполнения (например, утверждения, трассировки в MFC и т. Д.).

Цель RELEASE оптимизирована. Таким образом, в режиме выпуска двоичный файл может немного отличаться от режима DEBUG.

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

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

Позднее обновление: Вы можете использовать WaitForMultipleObjects, чтобы дождаться завершения всех потоков:

DWORD result = WaitForMultipleObjects( 
  numberOfThreads,  // Number of thread handles in the array
  threadHandleArray,  // the array of thread handles
  true, // true means wait for all the threads to finish
  INFINITE); // wait indefinetly
if( result == WAIT_FAILED)
  // Some error handling here
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...