Может ли код, выполняющийся в фоновом потоке, быть быстрее, чем в основном потоке VCL в Delphi? - PullRequest
8 голосов
/ 19 июля 2011

Если у кого-то был большой опыт работы с временным кодом в основном потоке VCL против фонового потока, я бы хотел узнать мнение. У меня есть код, который выполняет тяжелую обработку строк в моем приложении Delphi 6 в главном потоке. Каждый раз, когда я запускаю операцию, время для каждой операции колеблется около 50 мс в одном потоке на моем ядре i5 Quad. Что меня действительно подозревает, так это то, что тот же код, который работает на моем старом Pentium 4, показывает то же время для операции, когда обычно я вижу, что код работает примерно в 4 раза медленнее на Pentium 4, чем Quad Core. Я начинаю задумываться, может ли код потреблять значительно меньше времени, чем 50 мс, но что-то есть в основном потоке VCL, возможно, обработке сообщений Windows или выполнении вызовов Windows API, что создает искусственный «пол» для операции. Обратите внимание, что операция запускается входящим запросом на сокете, если это имеет значение, но измерение времени не происходит до тех пор, пока данные не будут получены полностью.

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

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

- Рошлер

Ответы [ 5 ]

12 голосов
/ 19 июля 2011

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

  • Компиляторгенерирует точно такой же код, ему все равно, будет ли код выполняться в основном потоке или в фоновом потоке.Фактически, вы можете поместить весь код в процедуру и вызвать его как из Execute() вашего рабочего потока, так и из основного потока VCL.

  • Для ЦП все ядра и все потоки, равны.Если это на самом деле не процессор Hyper Threading, где не все ядра являются действительными, но затем посмотрите следующую таблицу.

  • Даже если не все ядра процессора равны, ваш поток будет работать очень редко на том же ядре , операционная система может свободно перемещать ее по своему желанию (и на самом деле запланирует запуск потока на разных ядрах в разное время).

  • Затраты на обмен сообщениями не имеют значения для основного потока VCL, потому что если вы не вызываете Application.ProcessMessages() вручную, насос сообщений просто останавливается, пока ваша процедура выполняет свою работу.Насос сообщений пассивен, ваш поток должен запрашивать сообщения из очереди, но поскольку поток занят выполнением вашей работы, он не запрашивает никаких сообщений, поэтому никаких накладных расходов нет.

Там простоодно место, где потоки не равны, и это может изменить воспринимаемую скорость выполнения: это операционная система, которая планирует потоки для исполнительных блоков (ядер), и для потоков операционной системы имеют разные приоритеты.Вы можете указать ОС, что определенный поток необходимо обрабатывать по-разному, используя API SetThreadPriority() (который используется свойством TThread.Priority).

10 голосов
/ 19 июля 2011

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

Звучит однозначно как:

  • Проблема архитектуры - как определяются ваши темы?
  • Проблема измерения - как вы рассчитываете свои темы?
  • A типичная проблема масштабирования как менеджера памяти, так и реализации, связанной со строкой RTL.

По поводу последней точки, учтите это:

  • Текущий менеджер памяти (FastMM4) плохо масштабируется на многоядерных процессорах; попробуйте использовать диспетчер памяти для каждого потока, например наш экспериментальный SynScaleMM - обратите внимание, например. что команда Free Pascal Compiler недавно написала новый масштабирующий MM с нуля, чтобы избежать такой проблемы;
  • Попробуйте изменить реализацию строкового процесса, чтобы избежать выделения памяти (используйте статические буферы) и подсчета ссылок на строки (при каждом доступе к подсчету строк получается LOCK DEC/INC, который не так хорошо масштабируется на многокодовом ЦП - используйте обработка на уровне символов, например, PChar в статических буферах вместо string).

Я уверен, что без string операций вы обнаружите, что все потоки эквивалентны.

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

6 голосов
/ 19 июля 2011

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

Никаких накладных расходов нет, так как вы «владеете» всей вычислительной мощностью потока, когда находитесь в своем собственном коде.

Я бы предложил использовать инструмент профилированиячтобы найти фактическое узкое место.

3 голосов
/ 19 июля 2011

Производительность нельзя оценивать статически. Для этого вам нужно получить AQTime или какой-либо другой профилировщик производительности для Delphi. Я использую AQtime, и мне это нравится, но я знаю, что это считается дорогим.

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

Если, однако, вы могли бы выполнять части вашего алгоритма параллельно, то есть разделить свою работу так, чтобы у вас было 2 или более рабочих потоков, обрабатывающих ваши данные, и у вас был четырехъядерный процессор, то ваше общее время выполнения фиксированной нагрузка на работу, может уменьшиться. Это не означает, что код будет работать быстрее, но в зависимости от множества факторов, вы можете получить небольшую выгоду от многопоточности, вплоть до количества ядер на вашем компьютере. Это никогда не приведет к увеличению производительности в 2 раза, если использовать два потока вместо одного, но вы можете повысить производительность на 20-40% в параллельных решениях с несколькими потоками, в зависимости от масштабируемости кучи. под многопоточными нагрузками, и как IO / память / кеш ограничены вашей рабочей нагрузкой.

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

Ваш лучший выбор для оптимизации скорости строковых операций - сначала проверить его и выяснить, где именно он использует большую часть своего времени. Это куча операций? Операции копирования и перемещения в память? Без профилировщика, даже с советами других людей, вы все равно будете совершать кардинальный грех программирования; преждевременная оптимизация. Будьте ориентированы на результаты. Быть научно обоснованным. Мера. Понимаю. Тогда решите.

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

Вот патологический (экстремальный) случай, который, к сожалению, довольно часто встречается в дикой природе:

   procedure TMyThread.Execute;
   begin
       while not Terminated do 
         Synchronize(DoWork);
   end;

Проблема здесь заключается в том, что 100% работы действительно выполняется на переднем плане, кроме проверки «если завершено», которая выполняется в контексте потока. Чтобы сделать приведенный выше код еще хуже, добавьте непрерывный сон.

Для быстрого фонового кода потока, используйте Synchronize экономно или не используйте его вообще, и убедитесь, что код, который он вызывает, прост и выполняется быстро, или, что еще лучше, используйте TThread.Queue или PostMessage, если вы действительно можете жить с работой основного потока очереди .

1 голос
/ 19 июля 2011

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

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

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

...