|
Asm/C++/Delphi/Py/PHP/VBA
|
|||||||||||
FASM Parallel.For, хочу посоветоваться13.08.2018, 23:54. Показов 2122. Ответов 18
Метки нет (Все метки)
Всем привет!
Решил написать небольшой include для fasm под Windows, который будет позволять выполнять параллельные вычисления для for-циклов по аналогии с TParallel.For в Delphi или Parallel.For в C#, но без таких заморочек, как там, а что-то простенькое (строк на 500 асм-кода, ну может, чуть больше). Результат потом выложу. Кто не в теме – схема такая. Запускается функция инициализации, которая создаёт ждущие потоки. Далее (сразу или в нужный момент) вызывается функция, которой передаётся адрес процедуры цикла (ProcAddr) диапазон цикла for (либо кол-во итераций). Эта функция будит потоки (SetEvent) и либо завершает свою работу, либо ждёт окончания цикла. Потоки, в свою очередь, инициализируют цикл по определённым правилам (см. ниже вопросы) и вызывают на каждом цикле процедуру цикла (ProcAddr), передавая ей номер цикла и порядковый номер потока (чтобы было удобнее группировать результаты вычислений без использования lock и критических секций). В конце программист вызывает функцию завершения, которая даёт потокам команду завершить работу. В ходе реализации возникло несколько моментов, о которых я решил посоветоваться с местными спецами ![]() Итак, вопрос №1: какое кол-во потоков оптимально? Тут два варианта:
Кроме того, хочу по умолчанию умножать это число на 2 (потому что функция цикла может грузить процессор не на все 100%, а так всё же надёжнее будет). Ну и давать программисту возможность ограничивать общее число потоков сверху. Вопрос №1А (туда же): есть ли смысл назначать affinity mask или хотя бы номер идеального процессора для каждого потока? Мне кажется, что нет (система сама разберётся как лучше). Вопрос №2: как лучше распределять итерации циклов между потоками? Представим, что у нас 4 потока и мы делаем цикл i=0...999.
Вопрос №3: достаточно ли указания кол-ва итераций (причём, знаковым значением, т.е. 0x7FFFFFFF для 32-х бит) или обязательно нужно указывать и начальное, и конечное значение счётчика? Вариант с указанием обоих границ имеет некоторые сложности в реализации при кол-ве итераций, приближённых к максимально возможным. Но всё решаемо, конечно, при необходимости. Другой вопрос: есть ли такая необходимость? Что думаете обо всём этом? Повторюсь, что это не претензия на какую-то грандиозную задумку, а довольно простой, но всё же полезный инструмент для ускорения циклических вычислений.
2
|
|||||||||||
| 13.08.2018, 23:54 | |
|
Ответы с готовыми решениями:
18
Хочу посоветоваться с Java EE гуру Домашняя сеть, хочу посоветоваться. Решил обновить комп.Хочу посоветоваться. |
|
1624 / 806 / 146
Регистрация: 13.06.2015
Сообщений: 3,266
|
||||||
| 14.08.2018, 04:35 | ||||||
|
2
|
||||||
|
6773 / 2741 / 385
Регистрация: 17.02.2013
Сообщений: 4,048
|
||
| 14.08.2018, 07:53 | ||
|
2
|
||
|
2739 / 1665 / 267
Регистрация: 19.02.2010
Сообщений: 4,406
|
|||
| 14.08.2018, 10:44 | |||
|
А если поток будет прыгать по ядрам - ему придётся ждать, пока модифицируемые им данные (результаты расчётов) через общий и более медленный L3-кэш переберутся на каждое новое ядро.
2
|
|||
|
Asm/C++/Delphi/Py/PHP/VBA
|
||||||
| 14.08.2018, 13:04 [ТС] | ||||||
|
А надо ли основному потоку (который может тоже исполнять что-то параллельно, т.к. будет предусмотрена асинхронная работа for'а) тоже назначать affinity mask из 1 бита на время работы for'а? Я миллиард хочу ![]() 6.8 сек против 2.2 сек без lock'а (разница в 3+ раза). Но так тоже неинтересно. Запустим 10 потоков, которые делают "lock inc" в цикле (по 100 млн итераций каждая). Получим 26.5 сек против 0.7 сек (разница почти в 40 раз!). Понятное дело, что на практике потоки не будут делать в цикле lock inc, но эксперимент вполне показательный.Тут на другом форуме предложили ещё одну идею, где lock'ов будет ещё меньше, чуть позже опишу технологию. Добавлено через 1 минуту Добавлено через 2 минуты Ethereal, VTsaregorodtsev (и другие), что думаете по поводу кол-ва потоков (вопрос 1) и границ цикла (вопрос 3). Ещё я думаю, что нужно при запуске for'а задавать (опционально) процедуру обработки результата, которая будет запускаться после for'а (в нитке, выполнившей последнюю итерацию). Полезно при асинхронных запусках for'а. Зачем запускать for асинхронно? К примеру, для отображения % выполнения в основном потоке или для ожидания ввода юзером какого-то значения (которое понадобится позже) и пр. Добавлено через 2 минуты Меня удивляет, почему в Embarcadero не додумались передавать в процедуру цикла номер потока. Ведь так можно выделить заранее массив из N элементов (где N - кол-во потоков) и сбрасывать туда результаты работы итерации безо всяких lock'ов, если результаты, например, суммируются. Добавлено через 34 минуты После суммирования делается inc отдельной переменной (в одном случае с lock'ом, в другом - без).Результат: без lock'а - 1.2 сек, с lock'ом - 32.5 сек (в 27 раз)! Если делать этот inc/lock inc не каждый раз, а 1 раз в 4 итерации, то получим 8.6 сек против 1.35 сек (6+ раз).Процедуры не очень типичные, но вполне реалистичные ![]() Добавлено через 12 минут Тот "загадочный ещё один метод с другого форума" больше похож на первый предложенный мной вариант, поэтому ещё такой момент интересует... Т.к. итерации всё равно будут идти не совсем последовательно (скажем, 1,3,2,5,4,6,7,9,8,11,10,13,12...), может, вообще стоит делить весь цикл на равные части и отдавать потокам целиковые куски? Т.е. не так: thread1=0,4,8,12... thread2=1,5,9,13... thread3=2,6,10,14... thread4=3,7,11,15... А вот так: thread1=0..249 thread2=250..499 thread3=500..749 thread4=750..999 Для упрощения реализации (и ускорения). В итоге итерации будут происходить примерно в таком виде: (0,250,750,1,1000,251,751,1001,252,2,752 ,253,1002,3,753,503,1003...). Или не стоит, т.к. иногда этот вариант может быть более напряжным? Хотя и там, и там цикл не последовательный... Добавлено через 6 минут Если делать совсем по-нормальному, тогда нужно и шаг приращения указывать (включая отрицательные значения), чего я делать не буду. Если сравнить сишный и паскалевский for, то возможности второго тоже сильно урезаны. Так что... реально ли нужно это начальное значение или всё же 0 достаточно? ![]() p.s. Наверное, я всё же буду исходить из сложности реализации, но сдаётся мне, что проблем с этим быть не должно.
1
|
||||||
|
6773 / 2741 / 385
Регистрация: 17.02.2013
Сообщений: 4,048
|
||
| 14.08.2018, 21:14 | ||
|
2
|
||
|
Asm/C++/Delphi/Py/PHP/VBA
|
|
| 15.08.2018, 07:26 [ТС] | |
|
В общем, есть ещё вот такая идея...
Для каждого потока задано кол-во оставшихся итераций (обозначим его как IC). К примеру, если у нас цикл 0...999 и 4 потока, то на старте для всех нитей IC=250 (и при запуске итерации это число будет уменьшаться без блокировки). Допустим, thread1 закончил раньше. При этом у thread2:IC=30, thread3:IC=20, thread4:IC=10. 1. Ищем максимальное IC. Если это значение = 0, завершаем свою работу. Иначе мы нашли нить, у которой будем отбирать работу (это thread2, у него IC=30, обозначим его как ICx). 2. Вычисляем NewIC = ICx/2 = 15. 3. Делаем xchg ICx,NewIC (тут срабатывает блокировка). 4. Смотрим какое значение мы получили через этот обмен. Если оно <= NewIC, значит goto 1 (такое возможно, когда ICx=1 или около того, либо ещё какой-то поток тоже закончил работу и влез раньше нас). 5. Устанавливаем у себя IC = ICx-NewIC, выполняем оставшиеся итерации. Нормальная схема? Добавлено через 44 минуты Ещё и начальное значение нужно хранить, чтобы "отбирающий" поток знал, с какого места ему продолжать (т.к. "отбираемый" мог тоже у кого-то отобрать)
2
|
|
|
6773 / 2741 / 385
Регистрация: 17.02.2013
Сообщений: 4,048
|
|
| 15.08.2018, 07:36 | |
|
Это уже алгоритм драки между потоками за тарелку с пирожками
1
|
|
|
Asm/C++/Delphi/Py/PHP/VBA
|
||
| 15.08.2018, 11:45 [ТС] | ||
|
Ethereal, я пока не очень понял (читал вчера ночью) твой алгоритм. Сейчас попробую ещё раз осмыслить на свежую голову
![]() Добавлено через 8 минут Более того, тут уже попахивает cmpxchg8b, ибо между нашим xchg ICx и чтением номера следующей итерации отбираемый поток может закончить свою работу и отобрать у кого-то другого, поменяв оба значения (когда мы одно обновили/получили, а второе только собираемся читать). Уф...Добавлено через 1 минуту Не по теме: Пойду читать про
1
|
||
|
Asm/C++/Delphi/Py/PHP/VBA
|
|||||||
| 15.08.2018, 11:45 [ТС] | |||||||
|
Я тут ещё прикинул, что лучше хранить номер последней итерации и кол-во оставшихся итераций (для каждого потока).
Рабочий поток будет на каждом цикле уменьшать кол-во оставшихся (без lock'а), а номер текущей итерации будет храниться просто в регистре (другим он особо не нужен). Обирающий поток будет проверять кол-во оставшихся итераций и просто читать номер последней итерации, чтобы вычислить откуда ему продолжить (это всё без lock'а), а lock cmpxchg8b здесь будет работать эффективнее, т.к. поток меняет только одно из значений, и меньше вероятности "несрабатывания" (сравнения с ложным результатом). При этом решается ещё одна проблема: если хранить номер следующей (а не последней) итерации и кол-во оставшихся, рабочему потоку нужно как-то менять эти 2 значения одновременно. А тут только одно. Кстати, кто мне скажет: может ли поток, выполняющий операцию с lock'ом вклиниться между микрооперациями, скажем, инструкции dec? Т.е.:
Или lock всегда ждёт завершения инструкции, выполняемой другими ядрами? (Хотя, сдаётся мне, что не ждёт... блин... тогда вариант уменьшения кол-ва оставшихся итераций из рабочего потока без lock'а не канает ).Ещё такой момент интересует: т.к. итерации всё равно будут идти не совсем последовательно (скажем, 1,3,2,5,4,6,7,9,8,11,10,13,12...), может, вообще стоит делить весь цикл на равные части и отдавать потокам целиковые куски? Т.е. не так: thread1=0,4,8,12... thread2=1,5,9,13... thread3=2,6,10,14... thread4=3,7,11,15... А вот так: thread1=0..249 thread2=250..499 thread3=500..749 thread4=750..999 Для упрощения реализации (и ускорения). В итоге итерации будут происходить примерно в таком виде: (0,250,750,1,1000,251,751,1001,252,2,752 ,253,1002,3,753,503,1003...). Или не стоит, т.к. иногда этот вариант может быть более напряжным? Хотя и там, и там цикл не последовательный...
1
|
|||||||
|
6773 / 2741 / 385
Регистрация: 17.02.2013
Сообщений: 4,048
|
|||||||||||
| 16.08.2018, 06:13 | |||||||||||
|
Пусть надо обсчитать диапазон 0..0xFFF порциями по 0x10.
I_start = 0 лежит глобально. Пусть также I_end = 0x1000 ; и Step = 0x10 ; Первый поток сделал I_start = 0x10 и считает 0..0xF Второй поток сделал I_start = 0x20 и считает 0x10..0x1F Третий поток сделал I_start = 0x30 и считает 0x20..0x2F Второй поток досчитал свой диапазон, сделал I_start = 0x40 и считает новый диапазон 0x30..0x3F Первый поток досчитал свой диапазон, сделал I_start = 0x50 и считает новый диапазон 0x40..0x4F И так далее пока I_start не будет поднята до I_end и считать будет уже нечего. Занял и освободил переменную I_start в монопольное пользование это например так :
---------------------------------------------------------------------------------------------- Тут чутка не хватает знаний, но вот если вот так без обращений к ОС и всех этих мьютексов-шмутексов :
2
|
|||||||||||
|
Asm/C++/Delphi/Py/PHP/VBA
|
|||||||||||||
| 16.08.2018, 16:50 [ТС] | |||||||||||||
|
В общем, я придумал, как сделать без локов!!! Локи будут только у перехватчиков (это для алгоритма с перехватами, описанного здесь (#7), там я его немного модифицировал, но суть та же). Теперь у каждого потока есть счётчик текущей и последней выполняемой для данного потока итерации (Cur и Last). В массиве ThreadCounters они расположены друг за другом (Cur0,Last0, Cur1,Last1, Cur2,Last2...). Код работающего потока:
1
|
|||||||||||||
|
6773 / 2741 / 385
Регистрация: 17.02.2013
Сообщений: 4,048
|
|||
| 17.08.2018, 00:53 | |||
|
1
|
|||
|
Asm/C++/Delphi/Py/PHP/VBA
|
|||
| 17.08.2018, 07:39 [ТС] | |||
|
Добавлено через 27 секунд
1
|
|||
|
6773 / 2741 / 385
Регистрация: 17.02.2013
Сообщений: 4,048
|
|||
| 17.08.2018, 08:07 | |||
|
Добавлено через 12 минут
2
|
|||
|
Asm/C++/Delphi/Py/PHP/VBA
|
|||
| 17.08.2018, 15:39 [ТС] | |||
lock cmpxchg [ThreadCounters+edi*8],ecx там, а lock cmpxchg8b [ThreadCounters+edi*8] конечно же!Сначала хотел сделать без 8b, потом всё переделал, а тут забыл поменять ![]() Но комменты верные: if (edx:eax == Last:Cur) { Last = ecx; Cur = ebx; }. И там в любом случае нет проверки Cur на Last-(Last-Cur)). Инструкция cmpxchg сравнивает память с eax (а не со 2-м операндом), а второй операнд записывает на место памяти в случае совпадения. А cmpxchg8b - см. комменты. У меня соответственно eax и edx не меняются нигде, весь расчёт идёт в ecx/ebx.И ещё я там по запаре вместо esi использовал ebx в паре мест (в адресации). Добавлено через 14 минут cmpxchg, чтобы поменять Last на новое значение. Наверное. Надо покумекать...Но идея интересная, спасибо! Добавлено через 2 часа 55 минут [cut]
1
|
|||
|
Asm/C++/Delphi/Py/PHP/VBA
|
|||||||||||
| 17.08.2018, 22:35 [ТС] | |||||||||||
|
Итак, новый вариант (реализация накладывает свои коррективы на теорию).
Имеем структуру ThreadData, которая содержит 4 поля dword для каждого потока: Handle, Lock (флаг запрета обработки итераций), Current (номер текущей итерации), Last (номер последней итерации). Обозначу смещения до них как @Handle, @Lock, @Current и @Last (0, 4, 8 и 12). Работающий поток: Код работающего потока:
Добавлено через 2 часа 37 минут Кажется, у меня есть идея, как усовершенствовать мой последний код
1
|
|||||||||||
|
6773 / 2741 / 385
Регистрация: 17.02.2013
Сообщений: 4,048
|
||
| 18.08.2018, 00:51 | ||
Сообщение было отмечено Mikl___ как решение
РешениеНо все равно работа со значениями, которые возможно уже протухли в твоем старом варианте ... надо обдумать может ли она в принципе кончиться чем-то хорошим. И сейчас обдумаю твой новый вариант. Добавлено через 28 минут З.Ы. А ... понял наконец идею (только идею, реализацию уже не смотрю) твоего старого алго, если в нем поставить lock cmpxchg8b. А идея-то была абсолютно верная. Ты сначала готовишь данные для перехвата (которые возможно уже и подтухают, пока ты их обсчитываешь), а потом по lock cmpxchg8b перехватываешь, но только в том случае, если протухания не было. А если данные таки подтухли, то загружаешь данные посвежее и предпринимаешь еще одну попытку. И так до тех пор, пока между подргузкой свежачка и lock cmpxchg8b вклинения другого потока не было. Браво !!! Идея просто монстр !
2
|
||
|
Asm/C++/Delphi/Py/PHP/VBA
|
|||||||||||
| 18.08.2018, 09:49 [ТС] | |||||||||||
|
Спасибо, Артур! Но это вполне стандартный вариант использования
lock cmpxchg8b.Итак, новый вариант... ![]() Суть в том, что мы будем записывать в Lock не просто 1 и 0, а изначально хранить там 0x7FFFFFFF, а потом заносить новый Last. А работающий в цикле поток будет останавливаться не при любом ненулевом значении, а только когда Current доходит до Lock. Т.е. по факту он почти никогда не будет подвисать. Работающий поток:
Осталось теперь проверить на практике
3
|
|||||||||||
| 18.08.2018, 09:49 | |
|
Помогаю со студенческими работами здесь
19
хочу посоветоваться системник около 25 000 игровой Eвклидова норма вектора с помощью метода Parallel.For и Parallel.Invoke Хотел бы посоветоваться Просто посоветоваться, насчет оперативки посоветоваться о эффективном алгоритме резервирования данных Искать еще темы с ответами Или воспользуйтесь поиском по форуму: |
|
Новые блоги и статьи
|
|||
|
Сумматор с применением элементов трёх состояний.
Hrethgir 26.03.2026
Тут.
https:/ / fips. ru/ EGD/ ab3c85c8-836d-4866-871b-c2f0c5d77fbc
Первый документ красиво выглядит, но без схемы.
Это конечно не даёт никаких плюсов автору, но тем не менее. . . всё может быть. . .
|
Автозаполнение реквизитов при создании документа
Maks 26.03.2026
Код из решения ниже размещается в модуле объекта документа, в процедуре "ПриСозданииНаСервере".
Алгоритм проверки заполнения реализован для исключения перезаписи значения реквизита, которое может. . .
|
Команды "Заполнить" и "Очистить" на форме документа
Maks 26.03.2026
1. Команда формы "ЗаполнитьЗапчасти".
На примере нетипового документа разработанного в конфигурации КА2.
В качестве источника данных указан регистр накопления, в который записываются данные о. . .
|
Кому нужен AOT?
DevAlt 26.03.2026
Решил сделать простой ланчер
Написал заготовку:
dotnet new console --aot -o UrlHandler
var items = args. Split(":");
var tag = items;
var id = items;
var executable = args;. . .
|
|
Отправка уведомления на почту при изменении наименования справочника
Maks 24.03.2026
Программная отправка письма электронной почты на примере изменения наименования типового справочника "Склады" в конфигурации БП3. Перед реализацией необходимо выполнить настройку системной учетной. . .
|
модель ЗдравоСохранения 5. Меньше увольнений- больше дохода!
anaschu 24.03.2026
Теперь система здравосохранения уменьшает количество увольнений.
9TO2GP2bpX4
a42b81fb172ffc12ca589c7898261ccb/
https:/ / rutube. ru/ video/ a42b81fb172ffc12ca589c7898261ccb/
Слева синяя линия -. . .
|
Midnight Chicago Blues
kumehtar 24.03.2026
Такой Midnight Chicago Blues, знаешь?. .
Когда вечерние улицы становятся ночными, а ты не можешь уснуть. Ты идёшь в любимый старый бар, и бармен наливает тебе виски. Ты смотришь на пролетающие. . .
|
SDL3 для Desktop (MinGW): Вывод текста со шрифтом TTF с помощью библиотеки SDL3_ttf на Си и C++
8Observer8 24.03.2026
Содержание блога
Финальные проекты на Си и на C++:
finish-text-sdl3-c. zip
finish-text-sdl3-cpp. zip
|