Сколько потоков нужно, чтобы сделать их плохим выбором? - PullRequest
12 голосов
/ 17 сентября 2009

Я должен написать небольшую программу на C ++, используя boost :: thread.

Проблема в том, чтобы обработать большое (возможно, тысячи или десятки тысяч. Возможны также сотни и миллионы) большое количество (возможно) больших файлов. Каждый файл независим от другого, и все они находятся в одном каталоге. Я думаю об использовании многопоточного подхода, но вопрос в том, сколько потоков я должен использовать? Я имею в виду, какой порядок? 10, 500, 12400?

Существуют некоторые проблемы синхронизации, каждый поток должен возвращать структуру значений (которые накапливаются для каждого файла), и они добавляются в «глобальную» структуру для получения общих данных. Я понимаю, что некоторые потоки могут «проголодаться» из-за синхронизации, но если это только операция добавления, имеет ли это значение?

Я думал о

for(each file f in directory){

    if (N < max_threads)//N is a static variable controlling amount of threads
         thread_process(f)
    else
       sleep()
}

Это в HP - UX, но я не смогу его часто тестировать, так как это удаленный и довольно недоступный сервер.

Ответы [ 15 ]

2 голосов
/ 21 сентября 2009

Больше потоков не обязательно даст вам большую пропускную способность. Потоки имеют нетривиальную стоимость, как для создания (с точки зрения времени процессора и ресурсов ОС), так и для запуска (с точки зрения памяти и планирования). И чем больше у вас потоков, тем больше вероятность конфликта с другими потоками. Добавление потоков может иногда даже замедлять выполнение. Каждая проблема немного отличается, и вам лучше написать хорошее, гибкое решение и поэкспериментировать с параметрами, чтобы увидеть, что работает лучше.

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

Некоторые факторы, которые влияют на вашу пропускную способность:

  • Количество ядер процессора
  • Количество каналов диска (шпинделей, RAID-устройств и т. Д.)
  • Алгоритм обработки и проблема, связанная с ЦП или с вводом / выводом
  • Конфликт за основную статистическую структуру

В прошлом году я написал приложение, которое по сути делает то же, что вы описываете. В итоге я использовал Python и библиотеку pprocess . Он использовал многопроцессную модель с пулом рабочих процессов, взаимодействующих через каналы (а не потоки). Главный процесс будет читать рабочую очередь, разбивать входные данные на куски и отправлять рабочим информацию о чанках. Рабочий собирал данные, собирал статистику и по окончании отправлял результаты мастеру. Мастер будет объединять результаты с глобальными итогами и отправлять другой кусок работнику. Я обнаружил, что он масштабируется почти линейно до 8 рабочих потоков (на 8-ядерном корпусе, что довольно неплохо), и помимо этого он ухудшается.

Некоторые вещи, которые следует учитывать:

  • Используйте пул потоков с рабочей очередью, где число потоков, вероятно, равно количеству ядер в вашей системе
  • В качестве альтернативы, используйте многопроцессную настройку, которая обменивается данными по каналам
  • Оцените, используя mmap() (или эквивалентный), для сопоставления памяти входных файлов, но только после того, как вы профилировали базовый случай
  • Считать данные кратными размеру блока (например, 4 КБ) и разбить их на строки в памяти
  • Встроенное детальное ведение журнала с самого начала, чтобы облегчить отладку
  • Следите за конфликтами при обновлении основной статистики, хотя она, вероятно, будет затоплена из-за времени обработки и чтения данных
  • Не делайте предположений - тестируйте и измеряйте
  • Установите локальную среду разработки, максимально приближенную к системе развертывания
  • Использовать базу данных (например, SQLite ) для данных о состоянии, результатов обработки и т. Д.
  • База данных может отслеживать, какие файлы были обработаны, какие строки имели ошибки, предупреждения и т. Д.
  • Предоставляйте вашему приложению доступ только для чтения к исходному каталогу и файлам и сохраняйте результаты в других местах
  • Будьте осторожны, не пытайтесь обрабатывать файлы, открытые другим процессом (здесь есть несколько хитростей)
  • Осторожно, вы не достигнете пределов ОС количества файлов в каталоге
  • Профилируйте все, но не забудьте изменить только одну вещь за один раз и вести подробные записи. Оптимизация производительности составляет жесткий .
  • Настройте сценарии, чтобы вы могли последовательно перезапускать тесты. Здесь помогает наличие БД, поскольку вы можете удалить записи, которые помечают файл как обработанный, и повторно выполнить те же данные.

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

Еще одно слово в профилировании производительности: будьте осторожны при экстраполяции производительности из небольших наборов тестовых данных в супер огромные наборы данных. Ты не можешь Я нашел трудный путь, которым вы можете достичь определенной точки, когда обычные предположения о ресурсах, которые мы делаем каждый день в программировании, просто больше не выполняются. Например, я обнаружил, что буфер операторов в MySQL составляет 16 МБ, когда мое приложение прошло через него! А занятость 8 ядер может занять много памяти, но вы можете легко потратить 2 ГБ ОЗУ, если не будете осторожны! В какой-то момент вам придется тестировать реальные данные в производственной системе, но вы можете сами выбрать безопасную тестовую среду для запуска, чтобы вы не взламывали производственные данные или файлы.

С этим обсуждением напрямую связана серия статей в блоге Тима Брея, названная "Wide Finder" . Проблема заключалась в том, чтобы просто проанализировать файлы журналов и сгенерировать простую статистику, но максимально быстрым способом в многоядерной системе. Многие люди предлагали решения на разных языках. Это определенно стоит прочитать.

2 голосов
/ 17 сентября 2009

Я согласен со всеми, кто предлагает пул потоков: вы планируете задачи с пулом, а пул назначает потоки для выполнения задач.

Если вы привязаны к процессору, просто продолжайте добавлять потоки, пока загрузка процессора ниже 100%. Когда вы ограничены I / O , сбои диска могут в какой-то момент помешать большему количеству потоков повысить скорость. Это вам придется выяснить самим.

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

1 голос
/ 17 сентября 2009

В качестве приблизительного числа, вы, вероятно, должны поддерживать количество потоков от 10 до 100, чтобы минимизировать конфликты блокировки и издержки переключения контекста.

1 голос
/ 17 сентября 2009

Сколько стоит самый простой поток, зависит от ОС (вам также может понадобиться настроить некоторые параметры ОС, чтобы обойти определенное количество потоков). Как минимум, каждый имеет свое собственное состояние ЦП (регистры / флаги с плавающей запятой) и стек, а также любое хранилище динамической памяти.

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

В предельном случае вам может понадобиться использовать механизм совместной работы не в ОС или даже мультиплексировать события, используя крошечные объекты "контекста выполнения".

Просто начните с темы и позаботьтесь об этом позже:)

0 голосов
/ 03 января 2011

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

Давайте начнем со второй проблемы, для начала я бы не распараллеливал для каждого файла, но я бы распараллеливал обработку, выполняемую для одного файла за раз. Это может значительно помочь в нескольких частях вашей среды: - Жесткий диск, поскольку его не нужно искать из одного файла в n - 1 других - Файловый кеш операционной системы будет нагрет с данными, которые вам понадобятся во всех ваших потоках, и вы не будете испытывать такого большого количества кеша.

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

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

Конечная точка, конечно, заключается в том, что при использовании этого типа обработки ваше приложение будет более уважительно относиться к вашей системе, поскольку одновременный доступ к n файлам может сделать вашу ОС не отвечающей на запросы.

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