Ядра ЦП не используются должным образом с использованием QThreads

Использование: C++ (MinGW), Qt4.7.4, Vista (OS), intel core2vPro

Мне нужно обработать 2 огромных файла точно так же. Итак, я хотел бы вызвать процедуру обработки из 2 отдельных потоков для 2 отдельных файлов. Поток GUI не делает ничего сложного; просто отображает метку и запускает цикл событий для проверки условий завершения потока и соответственно закрывает основное приложение. Я ожидал, что это будет использовать два ядра (intel core2) примерно одинаково, но, наоборот, я вижу из диспетчера задач, что одно из ядер активно используется, а другое — нет (хотя и не каждый раз, когда я запускаю код); также время, необходимое для обработки 2 файлов, намного больше, чем время, необходимое для обработки одного файла (я думал, что оно должно быть равно или немного больше, но это почти равно обработке 2 файлов один за другим в непоточном заявление). Могу ли я каким-то образом заставить потоки использовать указанные мной ядра?

QThread* ptrThread1=new QThread;
QThread* ptrThread2=new QThread;
ProcessTimeConsuming* ptrPTC1=new ProcessTimeConsuming();
ProcessTimeConsuming* ptrPTC2=new ProcessTimeConsuming();

ptrPTC1->moveToThread(ptrThread1);
ptrPTC2->moveToThread(ptrThread2);

//make connections to specify what to do when processing ends, threads terminate etc
//display some label to give an idea that the code is in execution

ptrThread1->start();
ptrThread2->start(); //i want this thread to be executed in the core other than the one used above

ptrQApplication->exec(); //GUI event loop for label display and signal-slot monitoring

person ustulation    schedule 26.03.2012    source источник
comment
Файлы находятся на отдельных физических жестких дисках? Если вы пытаетесь раскрутить rust для одновременного чтения двух файлов, вам придется искать между ними каждый раз, когда назначается другой поток, и эта часть заглушит все, что вы можете получить от процессора.   -  person Pete Kirkham    schedule 26.03.2012
comment
Файлы примерно одинакового размера?   -  person Tudor    schedule 26.03.2012
comment
@PeteKirkham: просто имейте 1 жесткий диск   -  person ustulation    schedule 26.03.2012


Ответы (3)


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

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

person Tudor    schedule 26.03.2012
comment
я вижу .. можете ли вы однако сказать мне, как форсировать нить на ядре выбора? - person ustulation; 26.03.2012
comment
@ustulation: qthread не предоставляет такой API сходства. Кроме того, вам почти никогда не нужно устанавливать привязку к отдельным ядрам, потому что планировщик установит сопоставление потоков и ЦП как можно лучше. В любом случае, если вам это действительно нужно, посмотрите этот пост об использовании вызовов библиотеки pthread для достижения этой цели: qt-project.org/faq/answer/ - person Tudor; 26.03.2012
comment
спасибо .. я думаю, мне нужно придерживаться последовательной обработки, потому что размер файла непомерно высок для чтения в ОЗУ с последующей обработкой. функция (основной Affinity API), чтобы я мог вникать в нее, когда она мне нужна? - person ustulation; 26.03.2012
comment
@ustulation: я думаю, вы не поняли ответа. Вам не нужны потоки. Следовательно, вам также не нужна привязка потоков. Если вам нужно больше производительности, купите SSD. - person MSalters; 26.03.2012
comment
@ustulation: Насколько я знаю, boost также не предлагает эту функцию. На самом деле, высокоуровневые библиотеки многопоточности (boost, tbb, qthread) стараются избегать такого рода низкоуровневых механизмов, потому что это создает проблемы с переносимостью. Если вам нужна явная близость к Linux, просто используйте классические pthreads. - person Tudor; 26.03.2012
comment
@MSalters: ты тоже меня не понял... я сказал отдельно - person ustulation; 26.03.2012
comment
-1: Точно нельзя сказать. См. en.wikipedia.org/wiki/I/O_scheduling и, например, en.wikipedia.org/wiki/Elevator_algorithm . См. также en.wikipedia.org/wiki/ . Таким образом, наличие нескольких потоков для чтения огромных файлов (sic) может на самом деле увеличить производительность, неважно, используете ли вы многоядерный процессор или нет. - person Sebastian Mach; 26.03.2012
comment
@phresnel: вау .. интересно читать .. но я действительно не испытываю этого с точки зрения прироста скорости (интересно, действительно ли моя ОС делает это) - person ustulation; 26.03.2012
comment
@phresnel: я бы не стал так быстро минусовать. Связанное вами чтение не предполагает, что можно добиться большего успеха, чем последовательное время. Что делает алгоритм Elevator, так это группирует чтения в направлении движения головы, что приближает вас к шаблону последовательного чтения и, следовательно, к скорости последовательного чтения. По сути, он пытается уменьшить деградацию из-за одновременного чтения, но не может добиться большего, чем одно последовательное чтение. - person Tudor; 26.03.2012
comment
@phresnel: предположим, что два потока выдают чтение, каждый для половины данных, которые выдал бы один поток. Эти чтения будут запланированы в соответствии с алгоритмом Elevator, чтобы оптимизировать время поиска на диске, но вы можете легко увидеть, что они не могут превзойти одиночное последовательное чтение, которое по определению является оптимальным. - person Tudor; 26.03.2012
comment
На самом деле, если бы каждый поток читал с отдельного участка диска, то, если диск начнет обслуживать один из них, он не вернется на другую сторону, так как другие входящие чтения будут обслуживаться только в прямом направлении до тех пор, пока голова достигает края. В основном это приводит к тому, что оба потока обслуживают свои операции чтения один за другим. - person Tudor; 26.03.2012
comment
@Tudor: Вы не ошибаетесь сами по себе, и я не прав сам по себе (хотя я написал «не могу сказать наверняка»). Представьте, что у вас есть огромные файлы A и B. Они разбросаны по всему жесткому диску. Процесс 1 читает A, процесс 2 читает B. Теперь процесс 1 читает фрагмент A, который находится недалеко от B-блока, следующего за считываемым процессом 2. Следующий A-блок находится далеко. Строго однопоточный означает, что процесс 1 теперь должен ждать несколько секунд, пока диск не будет готов для следующего фрагмента. И после окончания этого процесс 2 может читать B, с тем же диском-ждет... - person Sebastian Mach; 26.03.2012
comment
@phresnel: это, вероятно, зависит от уровня фрагментации диска. Если файлы в основном непрерывны на диске, последовательное чтение лучше всего, но если файл очень фрагментирован, я согласен, что все не так просто оценить. - person Tudor; 26.03.2012
comment
@Tudor: ... но многопоточный, хотя процесс 1 все равно должен ждать, процесс 2 теперь может прочитать соседний фрагмент, в то же время продвигая позицию чтения дальше. Конечно, вы совершенно правы в том, что если диск физически может быть прочитан последовательно, производительность будет страдать, но часто большие файлы фрагментируются. Хотя вы можете не получить полного использования нескольких ядер, вы можете получить еще меньше использования с одним потоком. Но, однако, это все очень расплывчато и зависит от звезд и магнитных полей и других счастливых обстоятельств. - person Sebastian Mach; 26.03.2012
comment
@Tudor: В качестве последнего примечания: я на самом деле не голосую за ваше объяснение, а скорее за то, что самая первая фраза стерта;) И, конечно же, если между чтением фрагментов возникает тяжелая нагрузка на процессор, многопоточность снова выигрывает несколько метров. (Относительно последнего: полезно использовать для сборки больше процессов компилятора, чем у вас есть физических ядер; для некоторых программ это make -j8, даже если я использую четырехъядерный процессор) - person Sebastian Mach; 26.03.2012
comment
@phresnel: На самом деле, когда я писал это, я думал, что, вероятно, я был слишком резок, говоря, что это определенно не принесет никакой пользы. Вероятно, мне следует смягчить его до чего-то вроде, вероятно, не будет, чтобы учесть исключения, подобные тому, что вы упомянули. В отсутствие кода метода OP, вероятно, это то, что все сразу предполагают. :) - person Tudor; 26.03.2012
comment
@phresnel: Спасибо. Хотя хорошо, что вы подняли этот вопрос. Всегда стоит рассмотреть несколько возможностей. - person Tudor; 26.03.2012
comment
@Tudor: после обсуждения с вами у меня тоже было несколько просветлений; и хотя (в ретроспективе) я иногда звучу немного резко, я никогда не имел в виду это так :) - person Sebastian Mach; 27.03.2012
comment
@phresnel: Все в порядке, я не переживал. Я тоже кое-чему научился. :) - person Tudor; 27.03.2012
comment
Конечно, вы можете добиться большего успеха в смысле использования меньшего количества памяти, чем последовательное чтение всего, если файлы содержат ошибки. Вы можете и должны явно контролировать накладные расходы на поиск между файлами - реализовать свой собственный циклический считыватель. Это не магия, физические ограничения довольно просты. - person Kuba hasn't forgotten Monica; 22.08.2013

При работе с механическими жесткими дисками необходимо явно контролировать соотношение времени, затрачиваемого на последовательное чтение, и времени, затрачиваемого на поиск. Канонический способ сделать это с объектами n+m, работающими в m+min(n, QThread::idealThreadCount()) потоках. Здесь m — количество жестких дисков, на которых находятся файлы, а n — количество файлов.

  • Каждый из m объектов считывает файлы с заданного жесткого диска в циклическом режиме. Каждое чтение должно быть достаточно большим. На современных жестких дисках давайте выделять пропускную способность 70 Мбайт/с (вы можете сравнить реальное значение), 5 мс на поиск. Чтобы тратить не более 10% пропускной способности, у вас есть только 100 мс или 100 мс/(5 мс/поиск) = 20 операций поиска в секунду. Таким образом, вы должны прочитать не менее 70 Мбайт/(20 поисковых запросов+1)=3,3 мегабайта из каждого файла перед чтением из следующего файла. Этот поток заполняет буфер данными файла, а затем буфер сигнализирует соответствующему вычислительному объекту, присоединенному к другой стороне буфера. Когда буфер занят, вы просто пропускаете чтение из данного файла до тех пор, пока буфер снова не станет доступным.

  • Другие объекты n являются вычислительными объектами, они выполняют вычисления по сигналу из буфера, который указывает, что буфер заполнен. Как только данные буфера больше не нужны, буфер «сбрасывается», чтобы программа чтения файлов могла его пополнить.

Всем объектам чтения нужны собственные потоки. Вычислительные объекты могут быть распределены между своими потоками в циклическом режиме, так что все потоки имеют объекты в пределах +1, -0 друг от друга.

person Kuba hasn't forgotten Monica    schedule 22.08.2013

Я подумал, что мои эмпирические данные могут оказаться полезными для этой дискуссии. У меня есть каталог с 980 текстовыми файлами, которые я хотел бы прочитать. В среде Qt/C++ и на четырехъядерном процессоре Intel i5 я создал приложение с графическим интерфейсом и добавил рабочий класс для чтения файла по заданному пути. Я поместил рабочего в поток, а затем повторил добавление дополнительного потока при каждом запуске. Я замерил примерно 13 минут с 1 потоком, 9 минут с 2 и 8 минут с 3. Таким образом, в моем случае было некоторое преимущество, но оно быстро ухудшилось.

person Kevin White    schedule 21.02.2013
comment
Любая система, позволяющая одному потоку исчерпать свои возможности чтения/записи, будет нестабильной или, по крайней мере, не будет отвечать на запросы пользователя. Если вы не уйдете со своего пути, потоки эффективно регулируют свои операции ввода-вывода. Что вы сделали, так это потребовали, чтобы ваша программа имела более высокий приоритет, запустив два потока. - person Mikhail; 17.05.2013
comment
Все зависит от размера файлов. Как правило, для механических жестких дисков, если вы хотите, чтобы накладные расходы были ниже 10%, вы должны считывать пару мегабайт за раз. Поэтому, если файлы меньше, скажем, 2 Мбайт, вы читаете их целиком. Если они больше, вы можете циклически перемещаться между файлами, чтобы задействовать больше вычислительных потоков. - person Kuba hasn't forgotten Monica; 22.08.2013