2014 dxdy logo

Научный форум dxdy

Математика, Физика, Computer Science, Machine Learning, LaTeX, Механика и Техника, Химия,
Биология и Медицина, Экономика и Финансовая Математика, Гуманитарные науки




Начать новую тему Ответить на тему На страницу Пред.  1, 2, 3
 
 Re: Еще раз о пользе от ассемблера
Сообщение23.11.2024, 16:15 
Заслуженный участник


20/08/14
11780
Россия, Москва
bondkim137
Понятно, да атомарность необходима, согласен.
Извините, я С на компе не пользуюсь, ни под винду, ни под другие ОС (которых и нет). В основном асм, для оболочек старую добрую дельфи5 (она зараза только x86, но дельфи7 и новее меня пугают, как и многогигабайтные дистрибутивы С) или вообще PARI/GP (страшно медленный интерпретатор, но удобный встроенными математическими функциями и длинными числами). У меня просто нет задач под комп кроме чисто вычислительных, которые пишу на асм.

Решение с функцией выглядит нормальным, ведь её тоже можно объявить inline и компилятор вполне будет оптимизировать вызов до одной команды тела функции. Правда без гарантии.
С другой стороны, насколько знаю есть функции atomicXXX(), в том числе и inc/dec, они может и не из одной команды, но делают ровно что нужно. И если компилятор умный (как меня пытаются убедить), то он их оптимизирует до одной-двух команд (с префиксом lock).
Ещё вариант: разделить/раздублировать счётчики по потокам (сделать локальными), а объединять лишь в конце, при выводе, там легко можно сделать с семафорами и блокировкой (я предпочитаю критические секции), хотя для чтения аппаратных типов блокировки и не нужны вообще. Даже и не только в конце, в процессе работы тоже можно смело читать текущие состояния счётчиков и складывать одинаковые из всех потоков. Немного муторно, но не так уж сложно. Только не стоит объединять счётчики разных потоков (даже одного типа) в один массив, когерентность кэшей работает строго по строкам (обычно 64 байта), не по байтам или словам, будет много конфликтов/перезапросов, лучше счётчики каждого потока объединить в массив, а для разных потоков массивы объявить отдельно разными. Ну или по 128 байт на каждый счётчик если в одном массиве, чтобы строки кэша разных потоков гарантированно не перекрывались.

Про С лучше позвать worm2 или mihaild. Асм они может и не очень, но зато по самому С много лучше меня, может есть хороший неизвестный мне готовый механизм в языке.

 Профиль  
                  
 
 Re: Еще раз о пользе от ассемблера
Сообщение23.11.2024, 18:00 


14/01/11
3040
bondkim137 в сообщении #1662530 писал(а):
Переходите на C, выбросьте уже этого динозавра :D

Или на fasm. 8-)

 Профиль  
                  
 
 Re: Еще раз о пользе от ассемблера
Сообщение23.11.2024, 18:10 
Заслуженный участник
Аватара пользователя


16/07/14
9151
Цюрих
bondkim137 в сообщении #1662298 писал(а):
это как попросить компилятор гарантированно сделать
Чисто средствами С - никак. Это нужна добрая воля компилятора. Стандарт не накладывает никаких ограничений на итоговый бинарный код, только на наблюдаемое поведение.
На практике - есть _Atomic, который гарантированно наблюдаемо атомарный, и atomic_is_lock_free. clang справляется сгенерировать dec https://godbolt.org/z/1xvfhe1a5 (правда добавляет lock, но без него, насколько я понимаю, нельзя). А гарантию, увы, даст только страховой полис.

 Профиль  
                  
 
 Re: Еще раз о пользе от ассемблера
Сообщение23.11.2024, 18:19 
Аватара пользователя


07/02/12
1438
Питер
Dmitriy40 в сообщении #1662542 писал(а):
Решение с функцией выглядит нормальным, ведь её тоже можно объявить inline и компилятор вполне будет оптимизировать вызов до одной команды тела функции. Правда без гарантии
Компилятор C не может, он не знает тела функции, которое собрано внешним компилятором YASM, например.

Dmitriy40 в сообщении #1662542 писал(а):
С другой стороны, насколько знаю есть функции atomicXXX(), в том числе и inc/dec, они может и не из одной команды, но делают ровно что нужно. И если компилятор умный (как меня пытаются убедить), то он их оптимизирует до одной-двух команд (с префиксом lock)
Эти интринсики используют lock-префикс, и в моем случае это замедляет общую производительность не много не мало, а почти вдвое. И это по сравнению с вызовом функции с одной командой, но без lock.

Dmitriy40 в сообщении #1662542 писал(а):
щё вариант: разделить/раздублировать счётчики по потокам (сделать локальными)
Это затруднительно в моих реалиях. Для этого архитектуру процесса нужно переделывать. К тому же у него есть честный multi-core вариант сборки, с интерлоками. Оба нужны.

Dmitriy40 в сообщении #1662542 писал(а):
хотя для чтения аппаратных типов блокировки и не нужны вообще
Нужны, если команда использует чтение+запись, даже если она одна. не нужны, только если вы гарантированно одним и тем же ядром будете к ячейке доступ осуществлять, например, выставив affinity mask.

Расскажу в общих чертах:
Был процесс (сервис), работал с использованием CPU.
Затем он был доработан и стал использовать GPU. Производительность значительно выросла.
Было замечено, что в среднем один такой процесс на обвязку использует менее одного ядра CPU. Таких процессов на сервере выполняется много параллельно (более сотни), и они друг с другом общаются довольно мало.
Но тем не менее, при использовании сразу нескольких GPU, узким местом все-таки снова становится CPU (хочется сделать компактный сбалансированный продукт - скажем, 2U-сервер с максимальной производительностью для решения конкретной задачи).
Было решено жестко зафиксировать процессы на разных ядрах с помощью affinity-маски с одним битом. Что бы из-за когерентной синхронизации кешей последние минимально бы сбрасывались. Это дало почти +50% производительности.
Затем выставили критическим сессиям spin-count в ноль, ибо мультизадачность все равно стала single-core, как было в нулевые годы на однопроцессорных системах.
Затем, собственно, решили избавиться от interlocked-операций, ибо они тоже стали не нужны. А мешают они сильно - каждый раз блокируют память по вертикали до самого нижнего уровня. И это дало еще невероятные +50-80%. Но т.к. вытесняющая многозадачность все-таки в рамках процесса осталась, сами атомарные функции должны оставаться атомарными - вот тут проблема и возникла, ибо компилятору об этом не скажешь - пришлось использовать ассемблер. Из-за отсутствия inline - внешний. И вызывать целую функцию из-за одной команды. Скорее всего, переход на inline тут уже не сильно ускорит все, но попробовать бы хотелось.

mihaild в сообщении #1662554 писал(а):
правда добавляет lock, но без него, насколько я понимаю, нельзя
Да, к сожалению, добавляет. Собственно, без него как раз и нужно.

 Профиль  
                  
 
 Re: Еще раз о пользе от ассемблера
Сообщение23.11.2024, 18:49 
Заслуженный участник
Аватара пользователя


16/07/14
9151
Цюрих
bondkim137 в сообщении #1662556 писал(а):
Да, к сожалению, добавляет. Собственно, без него как раз и нужно.
Тут уже вопрос к Dmitriy40, что тут делает lock. Скорее всего он правда нужен для корректности.
Можно сказать --reinterpret_cast<int&>(x), тогда будет просто [tt]dec[/tt. Но скорее всего лучше не надо (надо читать стандарт, но я очень сильно подозреваю, что это UB).

 Профиль  
                  
 
 Re: Еще раз о пользе от ассемблера
Сообщение23.11.2024, 18:55 
Аватара пользователя


07/02/12
1438
Питер
mihaild в сообщении #1662562 писал(а):
Скорее всего он правда нужен для корректности
Он нужен для корректной работы read-write инструкций на multi-core системах, т.к. они сложные и разбиваются внутри процессора на более простые операции - без префикса есть вероятность возникновения ошибок в случае конфликтов.

mihaild в сообщении #1662562 писал(а):
Можно сказать --reinterpret_cast<int&>(x)
Это я тоже пробовал. Как и комбинации с volatile. Все равно компилятор часто разбивает операцию, если мы несколько раз подряд обращаемся к одной и той же переменной - пытается уменьшить кол-во обращений к памяти.

UPD: clang похоже как раз нормально работает, если объявить volatile, в отличие от MS-компилятора: https://godbolt.org/z/5no8f7Tqx

 Профиль  
                  
 
 Re: Еще раз о пользе от ассемблера
Сообщение23.11.2024, 22:53 
Заслуженный участник


20/08/14
11780
Россия, Москва
Sender в сообщении #1662553 писал(а):
bondkim137 в сообщении #1662530 писал(а):
Переходите на C, выбросьте уже этого динозавра :D
Или на fasm. 8-)
Так уже, им и пользуюсь. Практически доволен.

bondkim137 в сообщении #1662556 писал(а):
Нужны, если команда использует чтение+запись, даже если она одна.
Тут я говорил про чтение счётчиков для вывода статистики, не для их изменения. Читать типы поддерживаемые аппаратно (те .укладывающиеся в одну команду записи) можно без блокировок. Всё равно всё сведётся к обмену строками кэша, а операции записи в строку кэша командами делаются атомарно (внутри процессора), даже когда записи от нескольких команд объединяются в пакеты. Правда если записываемые без lock данные пересекают границу строк кэша уже не уверен что будет ... Скорее всего глюк.
Компилятор добавляет lock так как данные глобальные и выполняется операция записи, тут без него никак. Чтение всегда можно делать без блокировки (ну, пока работает MOESI протокол кэшей конечно). Правда знает ли об этом компилятор я не в курсе.
Ваше желание убрать lock понятно, хотя иногда может привести к глюкам. Именно из-за того что inc/dec mem транслируются в 4 микрокоманды (на моём проце), не 1. И не вклинится ли чужая микрокоманда чтения между родной чтения и родной же записи - неизвестно. Но для 8/16/32/64 битов это не слишком принципиально (если данные не пересекают границу линии кэша!), ведь будет всего лишь прочитано старое значение счётчика и потеряется одно увеличение ...
Возможностей для глюка я вижу две:
а) если слово пересекает 64 байт границу, тогда части слова могут в какой-то момент оказаться от старого и нового значения счётчика, что эквивалентно временному выбросу вверх/вниз на 256/65536/16777216 единиц счётчика, очень редко, но возможно, если в этот момент влезет команда модификации из другого потока будет совсем каша;
б) нарушится последовательность микрокоманд чтения и записи в разных потоках, будет не чтение-запись-чтение-запись, а чтение-чтение-запись-запись, тут сбой всего на 1.
И они могут произойти и одновременно ...
Потому всё что пишет в глобальную память из нескольких потоков (оба выделенных слова критичны) - должно выполняться либо атомарно (lock), либо через арбитраж (семафоры, критические секции, чего там ещё). А так как арбитраж это не атомарная запись, то под него приходится убирать и чтения тоже.
Подозреваю что Вы это всё знаете, но пусть будет для остальных.

И опять же, для локальных для процесса данных lock не нужен и добавляться не должен (если не встроен в команду типа xchg, но вы их не используете), так что решение с локальными счётчиками должно быть рабочим. Не слишком удобным, да.

Замедление вдвое от счётчиков производительности это конечно нонсенс. Может стоит выкинуть 90% из них и профилировать частями, запуская много раз, для разных кусков кода? Или речь про онлайн (на лету) профилирование, прямо в процессе? Можно попытаться выделить самым нагруженным счётчикам по аппаратному регистру (компиляторы C вроде можно заставить не использовать регистры по списку, хотя не уверен). Или разместить счётчики или в отдельной области памяти (4к страницам) или наоборот поближе к данным что обрабатываются вокруг них, в те же строки кэша, усложнив структуру данных (внеся счётчики прямо в неё).

Извините, но я больше ничем помочь не могу.
Судя по рассказу, Вы знаете побольше моего. Даже захотелось вопросики по GPU позадавать ... ;-)

-- 23.11.2024, 22:57 --

bondkim137 в сообщении #1662563 писал(а):
Это я тоже пробовал. Как и комбинации с volatile. Все равно компилятор часто разбивает операцию, если мы несколько раз подряд обращаемся к одной и той же переменной - пытается уменьшить кол-во обращений к памяти.
С volatile насколько знаю не имеет права, они всегда должны писаться в память и только оттуда читаться, для каждой операции, никаких кэширований в регистрах. Именно ради многопоточности (и модификаций в прерываниях или вообще аппаратно).

-- 23.11.2024, 23:09 --

bondkim137 в сообщении #1662556 писал(а):
Что бы из-за когерентной синхронизации кешей последние минимально бы сбрасывались. Это дало почти +50% производительности.
Ещё это меня поразило, я сколько ни прикидывал (до прямых опытов не доходило), но время чтения всего L2 даже из памяти составляло доли процента от тика переключения задач (18мс или пусть даже 1мс), порядка десятка мкс. Может у вас очень много данных и в L2 не влезают? Или много обращений к ОС и L2 перегружается много-много (более сотни) раз за тик? Странно. Нельзя ли обойтись без вызовов ОС подольше ...

 Профиль  
                  
 
 Re: Еще раз о пользе от ассемблера
Сообщение24.11.2024, 02:02 
Аватара пользователя


07/02/12
1438
Питер
Dmitriy40 в сообщении #1662600 писал(а):
Замедление вдвое от счётчиков производительности это конечно нонсенс. Может стоит выкинуть 90% из них и профилировать частями, запуская много раз, для разных кусков кода?
Это не счетчики производительности, это счетчики ссылок, reference-counters. Они классическим способом у меня используются в обвязке в первую очередь для автоматического удаления или возвращения в пул освобожденных объектов. И там их ооочень много. Ну и понятно, что ошибки +- 1 являются критичными.

Dmitriy40 в сообщении #1662600 писал(а):
Ещё это меня поразило, я сколько ни прикидывал (до прямых опытов не доходило), но время чтения всего L2 даже из памяти составляло доли процента от тика переключения задач (18мс или пусть даже 1мс), порядка десятка мкс. Может у вас очень много данных и в L2 не влезают? Или много обращений к ОС
Очень много обращений к ОС. Практически - там сплошные обращения к GPU + относительно архитектурно-сложная работа по структурированию полученных данных + получение/отправка по сети. Длительных внутренних вычислений на CPU порядка кванта и близко нет.

Dmitriy40 в сообщении #1662600 писал(а):
С volatile насколько знаю не имеет права, они всегда должны писаться в память и только оттуда читаться, для каждой операции, никаких кэширований в регистрах. Именно ради многопоточности (и модификаций в прерываниях или вообще аппаратно)
Только не для многопоточности, а для возможности неявного модифицирования переменных во внешнем коде (например, вызываемой функции) или когда в качестве переменной вы взяли ссылку на элемент массива, а потом работаете с этим массивом. Для многопоточности они как раз не годятся, даже в случае single-core. Мне удавалось добиться, что б MS-компилятор ставил инкремент/декремент одной командой (вот пример https://godbolt.org/z/8ThEob5d9), но иногда он ее все-таки разбивал, вот так: https://godbolt.org/z/vjG1Tf7M4

Dmitriy40 в сообщении #1662600 писал(а):
И не вклинится ли чужая микрокоманда чтения между родной чтения и родной же записи - неизвестно
Гарантированно никто не вклинится, если все обращения к конкретной области памяти происходят на одном фиксированном ядре (например, вследствие выставления affinity-mask процессу). Вытесняющая многозадачность не может прервать код на половине инструкции, в контексте замороженного потока в ОС хранится адрес следующей инструкции, без каких-либо дробных ее частей. Если же физически два ядра работают одновременно, то одно ядро может таки вклиниться между двумя обращениями к памяти сложной CISC-инструкции. Для защиты от этого lock-префикс и используется. Для простого чтения/записи его применение бессмысленно, при условии, что блок данных проходит atomic_is_lock_free-тест (как вы их назвали, поддерживаемые аппаратно) и блок выровнен в памяти, как вы успели заметить. Правда, скорее не достаточно, что бы блок находился в границах линейки кеша - я бы не стал рассчитывать, что вся линейка загрузится в кеши атомарно, исходя из информации, что она вся будет загружена (в DDR5 вообще уменьшили транзакцию с 64 до 32 бит и сделали каждую планку двухканальной, но atomic_is_lock_free ессно там проходят). Но зато достаточно что б блок был выровнен по адресу, кратному ближайшей вмещающей степени двойки своего размера.

Почти offtop, но может пригодится: если вам придется программировать под RISC, например ARM, столкнетесь с тем, что там внешне сложных инструкций нет. Либо чтение, либо запись в память - никаких read+write. И атомарные интерлоки в ассемблере описываются сложно: https://godbolt.org/z/6vq3sGKdq (ручная блокировка, ручная разблокировка). Но с другой стороны, между блокировкой и разблокировкой можно делать более сложные вещи, чем inc/dec/exchange и т.д. Мини-критическая-секция такая, но с очень дорогим внутренним временем.

Dmitriy40 в сообщении #1662600 писал(а):
Даже захотелось вопросики по GPU позадавать
Это всегда пожалуйста, спрашивайте :-)

 Профиль  
                  
Показать сообщения за:  Поле сортировки  
Начать новую тему Ответить на тему  [ Сообщений: 38 ]  На страницу Пред.  1, 2, 3

Модераторы: Karan, Toucan, PAV, maxal, Супермодераторы



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей


Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете добавлять вложения

Найти:
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group