Параллелизм: ускорение (15.07.2026). Печать
2026 - Июль
15.07.2026 14:41
Save & Share
Изучение особенностей параллелизма - похоже на сны при температуре 40. Если обычная многопоточность позволяет создать поток и заставить его что-нибудь отдельное лёгкое сделать, то параллелизм - раскидать одну огромную задачу на все логические процессоры ЦП, загрузить ЦП на ~100% - и при этом всё равно умереть со скуки в ожидании окончания работы. При этом, требуется не просто сверхточная работа - но и ускорение идеально написанных исходников.


Если описать вкратце все предыдущие серии (ссылки в каждом более раннем материале подтягивают ещё более ранние части), в контексте скоростей:
- нестабильность скорости работы потока. Нестабильность скорости работы группы потоков;
- влияние родительского потока на дочерние. Влияние дочерних потоков на родительский. Влияние дочерних потоков друг на друга. Даже если в потоке нет нагрузки, и он давно остановлен. Вплоть до подвешивания ОС;
- влияние исходного кода родительского потока на дочерние;
- возможное влияние настроек ОС и BIOS на дочерние потоки.

Предыдущая попытка оптимизировать исходники закончилась на сюре: время на сильных процессорах стало больше, чем на слабых. Сюр убрался сюрным решением: понижение приоритета родительского потока до минимума - через понижение приоритета всего приложения - через системную функцию SetPriorityClass: IDLE_PRIORITY_CLASS. Это же привело к другому положительному результату: влияние родительского потока на дочерние стало минимальным - и, например, теперь можно и Sleep() с любым числом вызывать, и ProcessMessages() сколь угодно раз (но убирать их из родительской функции всё равно нельзя). При этом, пониженным приложением прекрасно создаются дочерние потоки с приоритетом tpTimeCritical - неправильным распределением которых можно намертво подвесить ОС (если браузер открыт - есть вероятность его падения из-за нехватки процессорного времени). Всё это - дало возможность расширения аналитического функционала для дочерних потоков, без понижения приоритета-скорости их работы.

Программное отключение высокоточного таймера HPET и переход на программный TSC (с сохранением аппаратного HPET в материнской плате) - дало невнятный результат: возможно, есть прирост ~1%. Но если учесть хаотичность времён отдельных потоков и суммарного времени работы группы потоков - этот 1% может быть погрешностью, и риски отключения программного HPET выше предполагаемого получаемого результата (хотя его отключение повышает FPS в играх - см. "Как правильно отключить HPET в Windows: пошаговый гайд по оптимизации таймеров" на IXBT).


Удивительно сильное увеличение скорости на 5.4% получилось при передаче указателей, а не значений, в самую частовызываемую и ресурсоёмкую функцию расчёта погрешности вычислений при работе с long double (4Б вместо 10Б, минус операция копирования). Дополнительное удивление в том, что итоговая погрешность была изначально известна и составляла 0.00046362 (это преднамеренный триггер: что на результат никакие эксперименты не влияют). Но, при изменении способа перемещения данных, стала 0.00044790. Это - полная задница: либо при копировании long double именно иногда порождается дополнительная погрешность (и она сейчас была убрана, и это является ошибкой при поиске погрешностей), либо какая-то ещё неточность где-то появилась.


Далее пошёл полный сюр (собственно, на скрине выше и видно). Время выполнения одного и того же кода - начало резко расти в процессе разработки, пока не достигло минимального значения 300+с: +25% от самого быстрого полученного результата. Выяснилось, что количество элементов на форме напрямую влияет на скорость дочерних потоков. При этом, имеет значение именно наличие элементов: ни Visible, ни Enabled не меняли ситуацию. Удаляешь 2 кнопки и текстовое поле справа сверху - 240с, запускаешь предыдущий вариант EXE-шника с ними - 300с. Перезагрузка - не помогает. На следующий день - проблема так же внезапно исчезла, как и появилась. Проблема не завязана на ЦП или ОС: 2 EXE-шника показывали совершенно разные результаты на разных ПК - как будто линковщик среды программирования единично засунул баг именно в исполняемый файл.

Продолжая добавлять элементы в ПО, время 240с увеличилось до 251с - и думалось, что в этом виноваты элементы интерфейса. Но когда начал добавлять пачками метки на форму - скорость не менялась. Поэтому, вопрос с элементами интерфейса остаётся открытым - но острота проблемы спала.

Неизвестно, как это объяснить: 251с превратилась в 235с - после того, как исходный код из CPP-файла формы перекидал в отдельные CPP-файлы. Мало того, была написана функция оценки состояния потоков - именно в CPP-файле, отвечающем за потоки. Есть предположение, что чем меньше кода пишешь в CPP-файле формы - тем лучше, хоть это и не имеет логики: наоборот, добавились вызовы отдельных функций, тратящих процессорное время. Возможно, это связано с выделением памяти в разных местах стека или основной памяти. В интернете пишут, что для каждого потока нужно делать свою отдельную область памяти и не лезть за пределы функции потока без необходимости. Может, действительно: вынесение операций в CPP-файл именно потоков - ускорило их размещением в другом месте RAM (хоть и используется один и тот же родительский поток).

Увеличение объёма работы для логических процессоров в 16 раз (увеличение точности вычисления погрешностей) - не устранило проблему разной скорости работы потоков - частоты логических процессоров не сильно плавают относительно друг друга. Работа почти 1.5ч - все потоки стали уже по 100%, а 2 плетутся на 90% ещё: ОС приспичило не разгонять их как все остальные (или кастрировать их номинальные частоты - прямо в момент создания).


2 способа работы с параллелизмом. Раздать потокам одинаковые порции задач и ждать, когда завершится последний. Разбить задачу на большое число порций - и кормить каждому освободившемуся потоку новую порцию. Второй, безусловно, быстрее; однако с ним не сможешь понять всех тонкостей работы потоков, не сможешь ускорить исходники - и можно получить на выходе второго способа время большее, чем оптимизированного первого. Уменьшение работы с 340с до 235с - это не шутки.

ПО работает необычно. При одном запуске, приложение сразу порождает 4 программных потока (видно на вкладке "подробности" диспетчера задач). Но это - минимум, может породить и 5, и 6, и 7. Может возникнуть мысль, что именно поэтому от запуска к запуску время работы одного и того же кода в одних и тех же условиях отличаются. Но это не так: даже если 4 потока при текущем запуске приложения вышло - первый запуск обработки данных даст время 235с, но оно никогда не будет достигнуто повторными запусками (всегда будет выше). На листках записаны значения на промежуточных этапах оптимизации: 241-305с с 10 запусков, 252-297 с 10 запусков. Первый запуск обработки данных - всегда самый быстрый; и даже несмотря на корректное уничтожение первой группы потоков (даже если байты занимаемой оперативной памяти просматривать) - что-то тормозит следующую группу потоков. Возможно, чтобы параллелизм работал максимально быстро при частых вызовах, - его нужно оформлять как отдельное приложение, которое жрёт данные/параметры из файла/RAM - и обрабатывает их, с последующей выдачей результатов и собственным уничтожением.

В некоторых новых средах программирования есть очень удобная фича: библиотека параллельных шаблонов PPL + одна лишь функция при её использовании (как будто однопоточный код пишешь - PPL сама раскидывает всё по логическим процессорам).

Вкусняшка E5-2696 V2 опять уделала все прочие ЦП по скорости работы: 189с.


Важный скрин увеличенной точности расчёта погрешностей уравнений прямой: максимальная погрешность сосредоточена и в первом, и в последнем потоке - и при этом одинаковая. Это значит, что она находится между минимальными и максимальными X1-Y1-X2-Y2. А это значит - можно сократить работу в 11-23 раза, обработав только числовую нагрузку первого потока, уже с максимальной точностью. А это значит, время на расчёты 20-40 дней - уменьшается до единиц дней. А там, глядишь, и дискрет анализа 6с на мировой карте можно будет уменьшить до необходимого 1с.


Надо бы эту заготовку по параллелизму закончить - и как отдельную программу выложить...
Обновлено ( 15.07.2026 20:06 )