Нашли или выдавили из себя код, который нельзя назвать нормальным,
на который без улыбки не взглянешь?
Не торопитесь его удалять или рефакторить, — запостите его на
говнокод.ру, посмеёмся вместе!
// читаем структуру из файла
Header hdr;
// ... тут вызов read ...
// и фиксим endian во всех полях с числами длиннее байта
hdr.field16 = htons(hdr.field16);
hdr.field32 = htonl(hdr.field32);
// теперь с ними можно спокойно работать
// биты и арифметика ведут себя как положено
Мы на работе сериализуем в protobuf. Это позволяет безболезненно добавлять и удалять поля. Нахуй ещё задумываться о такой хуйне типа внутреннего строения и индейцах.
// ... убедились, что в буфере есть sizeof(T) байт ...
// и теперь читаем их из буфера как big-endian
T res = 0;
for (size_t i = 0; i < sizeof(T); ++i)
res = (res << 8) | (uint8_t)buf[i];
// теперь с res можно спокойно работать
// биты и арифметика ведут себя как положено
Да, но если есть стык, то например, если тебе нужен отрезок 7-12,
то окажется, что не инвертировав байты ты достанешь не оттуда.
там вообще в этот отрезок войдет еще один отрезок, который ему не должен принадлежать.
xor-обмен никто в здравом уме не юзает, разве что выебнуться. Впрочем, компиляторы его умеют оптимизировать в один xchg. Как и классическую перестановку через временную переменную, которая юзается в дефолтном std::swap.
https://www.agner.org/optimize/instruction_tables.pdf
> Instructions with a LOCK prefix have a long latency that depends on cache organization and possibly RAM speed. If there are multiple processors or cores or direct memory access (DMA) devices, then all locked instructions will lock a cache line for exclusive access, which may involve RAM ac-cess. A LOCK prefix typically costs more than a hundred clock cycles, even on single-processor systems. This also applies to the XCHG instruction with a memory operand.
Зачем? Зачем? Даже при обычном xchg, а не lock xchg у которого гарантии?
З.Ы. Бля, и правда. If a memory operand is referenced, the processor’s locking protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL.
То есть раньше было одно ядро, и великой разницы между lock xchg и xchg не было, и тупые лалки понаписали без лока, а потом интел завез SMP, и побоялся сломать старые программы?
SMP нет смысла поддерживать в окамле: SMP никогда не выстрелит
Скорее уж у людей будут 64х битные компьюетеры с 16-ю гигами памяти, и серьезный софт начнут писать, например, на джаваскрипте, ахаха
>почему
если уж вы с циферками не знаете, то я и подавно не знаю. Можно спросить на форуме у интела. Я там как-то чото спрашивал, там крутые челы сидят
Лол, в доке по оригинальному 8086 уже есть глава про Multiprocessing Features! И там xchg ещё не брал lock автоматически.
The 8086 and 8088 are designed for the multiprocessing environment. They have built-in features that help solve the coordination problems that have discouraged multiprocessing system development in the past.
LOCK may be used in multiprocessing systems to coordinate access to a common resource.
It is possible for another processor to obtain the bus between these two cycles and to gain access to the partially-updated semaphore. This can be prevented by preceding the XCHG instruction with a LOCK prefix.
Х.з., возможно тогда LOCK ещё на любых инструкциях работал, включая всякую строковую фигню? Я не вижу в доке от двойки ограничения на конкретный набор инструкций, которое в свежих доках есть.
Короче да, в доке по 80386 есть глава Restricted Semantics of LOCK. И замечание, что при неправильном использовании LOCK теперь может вылететь invalid opcode.
The 80286 processor implements the bus lock function differently than the 80386. Programs that use forms of memory locking specific to the 80286 may not execute properly when transported to a specific application of the 80386.
Т.е. совместимость - это святое, но иногда можно и забить )))
Despite the fact that Pentium 4, Intel Xeon, and P6 family processors support processor ordering, Intel does not guarantee that future processors will support this model.
Какой дисклеймер )))
Т.е. интел внезапно может дропнуть гарантии про реордеринг. Но что-то мне намекает, что юзеры после этого дропнут интел.
Они гарантируют (пока?) что даже без фенсов, лочек и т.п. у тебя чтения и записи почти не реордерятся.
Насколько я помню, на x86 только read вперёд write может проскочить (если они в разные места, само собой). Всё остальное идёт строго как написано в коде. Ну смёржиться разве что могут если WB или WC режим.
В инструкции к 80386 указана только инструкция XCHG.
11.2.2 Automatic Locking
In several instances, the processor itself initiates activity on the data
bus. To help ensure that such activities function correctly in
multiprocessor configurations, the processor automatically asserts the LOCK#
signal. These instances include:
• Acknowledging interrupts.
• Setting busy bit of TSS descriptor.
• Loading of descriptors.
• Updating page-table A and D bits.
• Executing XCHG instruction.
Я уже забыл про такой багор:
14.7 Differences From 8086
...
4. Value written by PUSH SP.
The 80386 pushes a different value on the stack for PUSH SP than the
8086/8088. The 80386 pushes the value of SP before SP is incremented
as part of the push operation; the 8086/8088 pushes the value of SP after it is incremented. If the value pushed is important, replace
PUSH SP instructions with the following three instructions:
PUSH BP
MOV BP, SP
XCHG BP, [BP]
This code functions as the 8086/8088 PUSH SP instruction on the 80386.
Видимо, поэтому инструкцию PUSH SP почти нигде не встретишь: SP сначала кладут в какой-нибудь регистр, потом пушат.
CPUID can be executed at any privilege level to serialize instruction execution. Serializing instruction execution guarantees that any modifications to flags, registers, and memory for previous instructions are completed before the next instruction is fetched and executed.
если она не лезет, то кто вообще узнает о том, что ее реордернули? или можно писнуть в какой-то регистр, и идущий за тобой этого не заметит? Это было бы странно
Ведь нельзя же читать чужие регистры из соседнегоядра?
Могло быть так, что какой-то софт типа аиды начинал тест с CPUID (чтобы знать с чем имеет дело), потом Intel оптимизнул что-то, и сломал тест, пересрался, и сериализнул в том месте?
Нормальные проги CPUID не юзают, и по пиформансу не просядут
Вроде на каких-то 80486 она появилась, но поскольку в более старых процессорах её не было, её не использовали, чтобы не ловить исключение 6, а вместо неё применяли другие методы.
напихали локи в какие-то места что что-то работало
Почему нельзя было сказать так
* все инструкции никогда явно не берут лок
* бери лок сам
* хочет пирформанса -- вот тебе спец атомарная инструкция, которую проц оптимизирует
Напоминает код в одном проекте, где чуваки в случайные места вставили синхронизацию к комментом "без этого чото падает иногда". без царя вголове
/*****************************************************************************\
FUNCTION: CFtpDrop::CopyStorage
DESCRIPTION:
Copy a file contents provided as an IStorage. Gack.
We have to do this only because Exchange is a moron.
Since there is no way to tell OLE to create a .doc file
into an existing stream, we need to create the .doc file
on disk, and then copy the file into the stream, then delete
the .doc file.
Note that CDropOperation::DoOperation() (_CopyOneHdrop) will do the ConfirmCopy
and the FtpDropNotifyCreate(), too! However, we want to fake
it out and fool it into thinking we are doing a DROPEFFECT_COPY,
so that it doesn't delete the "source" file. *We* will delete
the source file, because we created it. (No need to tell the
shell about disk size changes that don't affect it.)
\*****************************************************************************/
Я тоже соседние отделы обкладываю, но у нас нельзя шеймить в комментах, потому приходится писать: "in spite of the documentation, this API doesn't support our case"
Да иногда просто не везёт. На той версии мастера, которая была перед мержом твоих изменений и запуском тестов всё было норм. Но за это время кто-то уже пролез в мастер. И всё сломалось.
Полную сериализацию коммитов никто в здравом уме делать не будет. Поэтому иногда shit happens.
Ну вопрос в том, насколько полная сериализация коммитов ловит больше багов, чем более слабая но оптимистичная. И насколько она дороже обходится по железу и усилиям программистов.
Если была пара поломок мастера в месяц, то может и хуй с ними?
З.Ы. Я не против прекоммит тестов, я против их полной сериализации.
сериализация не серилизует работу: пока оно там крутится, ты занят своими делами.
поломка мастера может быть очень болезненной: я как-то собирал openjdk, и попал на несобираемый мастер, и пытался понять где я обосрался, а потом приехал следующий коммит и все починилось
у нас правда ботик срет в чатик когда компилция падает на CI, так что обычно все знают
З.Ы. Беру свои слова обратно. gcc -O2 не осилило xor-swap оптимизнуть. А вот с std::swap весь твой extract_bits<uint32_t, uint32_t>(n, 0, 31) свернулся в одну ассемблерную инструкцию bswap.
Не работает для типов, которые нельзя поксорить. По-моему этого уже достаточно.
Ну и с наивным конпелятором, который не умеет в эту идиому, превращается в сраное говнище, которое не даёт процу распараллелить выполнение (в обычном swap'е по сути просто регистры заренеймятся).
>Не работает для типов, которые нельзя поксорить.
логично) Я говорил про целочисленные типы.
> обычном swap'е по сути просто регистры заренеймятся
логично, там же под капотом 100500 внутренних регистров, которыv микрокот просто дает имена типа EAX
> Не работает для типов, которые нельзя поксорить.
Скастовать в тип подходящего размера который ксорить можно, и потом поксорить (точнее, сделать указатель на такой-то тип с поддержкой xor, и присвоить в такие указатели адреса на переменные с неподходящим для перексориванием типы, и потом уже поксорить). Обменять float-ы так вполне можно.
Если переменная с этим may_alias будет встречаться только в какой-то особой специальной функции обмена, разве это всюду сломает оптимизации?
Впрочем, xor swap вообще говоря говно, https://godbolt.org/z/9rT6ax оптимизируется он плохо. Компилятор не может в принципе породить точно такой же код 1 в 1 как при обмене переменных через третью, т.к. если мы меняем переменную саму с собой, xor-swap ее занулит
> если мы меняем переменную саму с собой, xor-swap ее занулит
Ну вот это и есть самое большое говнище в xor-swap. Всё прекрасно работает, а потом кто-нибудь передаёт туда уко-ко-козатели на одинаковые пельменные — и пиздец.
Еще есть нестандартные функции типа:
uint16_t __builtin_bswap16 (uint16_t x)
uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_t x)
uint128_t __builtin_bswap128 (uint128_t x)
из GCC, но Clang их вроде тоже умеет.
А в стандартах нету скорее всего из-за того, что представление числа стандартами не описывается. Хотя было бы неплохо иметь какие-нибудь шаблонные std::to_network_endian() и std::from_network_endian().
С другой стороны, функции которые ты привёл - это такая же чистая математика как и функции бесселя. Можно и добавить, даже если на какой-нибудь платформе в духе PDP они не имеют никакого смысла.
З.Ы. А потом захочется вращения чисел и поиска старшего бита.
Кстати я так подозреваю, что two's complement для signed чисел сделали чтоб в constexpr поведение было более однозначным (не зависело от платформы). Оно-то всё еще зависит в какой-то мере, из-за implementation defined размеров всяких типов и хуйни с плавучими питухами (хотя может я тут ошибаюсь, и плавучепитуховые вычисления в коконстэкспрах всегда дают известно какой результат?).
Вполне возможно. А может быть чтобы убрать UB при касте из uint32_t в int32_t для больших чисел. Всё-таки очень много скверно написанных программ на это полагается.
З.Ы. invert_bytes аля htons/htonl/bswap_64 обычно делают сразу после чтения, до любой работы с числом, а не посреди неё.
P.S. ты отредактировал комментарий, там был read
будет какое-то такое описание:
Можно потом из этого XML генерить протобуфную питушню какую-нибудь
Дык у протобуфа парсер в принципе не пригоден для кастомных форматов. Ибо там что-то в духе тегированных полей. Или что-то изменилось?
Ну что-то своё можно сгенерить. На крестах в общем-то просто имён полей хватает для генерации, остальное в компайлтайме доступно.
read() я убрал потому что я обосрался с проверкой его результата (signed-unsigned mismatch).
то окажется, что не инвертировав байты ты достанешь не оттуда.
там вообще в этот отрезок войдет еще один отрезок, который ему не должен принадлежать.
15 14 13 [12] 11 10 9 8 | [7] 6 5 4 3 2 1 0 | LE
~~~~~~~~ -------------------------- ~~~~~~~~~~~
[7] 6 5 4 3 2 1 0 | 15 14 13 [12] 11 10 9 8 | BE
--- ~~~~~~~~~~~~~~~~~~~~ --------------------
a ^= b ^= a ^= b;
Сейас прилетит питух, и скажет, что это UB
Или не скажет, а потом у тебя это в продакшене навернётся.
На самом деле я хз как устроен std::swap для стандартных типов.
Используется ли там xor-обмен.
xor-обмен никто в здравом уме не юзает, разве что выебнуться. Впрочем, компиляторы его умеют оптимизировать в один xchg. Как и классическую перестановку через временную переменную, которая юзается в дефолтном std::swap.
> Instructions with a LOCK prefix have a long latency that depends on cache organization and possibly RAM speed. If there are multiple processors or cores or direct memory access (DMA) devices, then all locked instructions will lock a cache line for exclusive access, which may involve RAM ac-cess. A LOCK prefix typically costs more than a hundred clock cycles, even on single-processor systems. This also applies to the XCHG instruction with a memory operand.
я не думал, что он вот прямо обязан писать в память. Не все ведь инструкции обязаны, слава богу
Ну это уже неправда вроде. Если нет реальной драки за кешлайн, то атомики относительно дешёвые.
Зачем? Зачем? Даже при обычном xchg, а не lock xchg у которого гарантии?
З.Ы. Бля, и правда. If a memory operand is referenced, the processor’s locking protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL.
Скорее уж у людей будут 64х битные компьюетеры с 16-ю гигами памяти, и серьезный софт начнут писать, например, на джаваскрипте, ахаха
Я могу написать на си функцию doAll которая делает форк на 16 ядров, потом джойн, и возвращает результат ожидающему потоку в JS
И какой-нибудь флажок smp-aware в eflags, который отключает все автоблокировки.
если уж вы с циферками не знаете, то я и подавно не знаю. Можно спросить на форуме у интела. Я там как-то чото спрашивал, там крутые челы сидят
https://community.intel.com/
Лол, в доке по оригинальному 8086 уже есть глава про Multiprocessing Features! И там xchg ещё не брал lock автоматически.
The 8086 and 8088 are designed for the multiprocessing environment. They have built-in features that help solve the coordination problems that have discouraged multiprocessing system development in the past.
LOCK may be used in multiprocessing systems to coordinate access to a common resource.
It is possible for another processor to obtain the bus between these two cycles and to gain access to the partially-updated semaphore. This can be prevented by preceding the XCHG instruction with a LOCK prefix.
Видимо какие-то лалки уже успели обосраться за 4 года.
разве что там был хитрый DMA, который сам читал с шины данные и писал в память
LOCK на двойке был привилегированной инструкцией! А мутексов в юзермоде то хотелось. Вот и прикрутили автолочку к XCHG.
80386 перестал проверять привилегии на LOCK. Но уже было поздно.
Такой вот очередной плевок в вечность.
раньше я мог писать lock к каждой инструкции, и течь?
The 80386 defines new exceptions that can occur even in systems designed for the 80286.
Exception #6 - invalid opcode
This exception can result from improper use of the LOCK instruction.
Т.е. совместимость - это святое, но иногда можно и забить )))
Despite the fact that Pentium 4, Intel Xeon, and P6 family processors support processor ordering, Intel does not guarantee that future processors will support this model.
Какой дисклеймер )))
Т.е. интел внезапно может дропнуть гарантии про реордеринг. Но что-то мне намекает, что юзеры после этого дропнут интел.
они гарантировали реордеринг или его отсутсвие при фенсах?
Насколько я помню, на x86 только read вперёд write может проскочить (если они в разные места, само собой). Всё остальное идёт строго как написано в коде. Ну смёржиться разве что могут если WB или WC режим.
звучит как остыль конечно
11.2.2 Automatic Locking
In several instances, the processor itself initiates activity on the data
bus. To help ensure that such activities function correctly in
multiprocessor configurations, the processor automatically asserts the LOCK#
signal. These instances include:
• Acknowledging interrupts.
• Setting busy bit of TSS descriptor.
• Loading of descriptors.
• Updating page-table A and D bits.
• Executing XCHG instruction.
в Executing XCHG instruction был LOCK#
блядь, как всё сложно
именно по этому я за 5150
14.7 Differences From 8086
...
4. Value written by PUSH SP.
The 80386 pushes a different value on the stack for PUSH SP than the
8086/8088. The 80386 pushes the value of SP before SP is incremented
as part of the push operation; the 8086/8088 pushes the value of SP
after it is incremented. If the value pushed is important, replace
PUSH SP instructions with the following three instructions:
This code functions as the 8086/8088 PUSH SP instruction on the 80386.
Видимо, поэтому инструкцию PUSH SP почти нигде не встретишь: SP сначала кладут в какой-нибудь регистр, потом пушат.
Точнее, какой имплементейш дефанд
Ну и пойомка эти ваши процессоры. Понятно теперь, почему пуш не трогают. Кому было охота иметь разное поведние
Хак для определения процессора (когда-то не было CPUID):
Правда кстати, что CPUID ставит барьер почему-то?
CPUID can be executed at any privilege level to serialize instruction execution. Serializing instruction execution guarantees that any modifications to flags, registers, and memory for previous instructions are completed before the next instruction is fetched and executed.
Х.з., на самом деле это единственная инструкция, которая сериализует исполнение в юзермоде.
Формально ещё можно iret дёрнуть, но ты же не будешь это делать посреди кода.
cpuid - самый сильный из заборов, доступных юзермоду. Остальные заборы что-то да разрешают переставлять.
надо про них почитать, я опять забыл чем они все отличаются
Ведь нельзя же читать чужие регистры из соседнегоядра?
По-моему это только для замера пирфоманса может пригодиться.
Нормальные проги CPUID не юзают, и по пиформансу не просядут
Программирование это костыли
Я сейчас открыл доку по первому пню, где она изначально появилась. И она там уже сериализует исполнение.
The CPUID instruction can be executed at any privilege level to serialize instruction execution.
ради кого?
Пользователя спрашивали какой у вас проц: 286, 386 или 486.
В зависимости от этого считали сколько времени занимает инструкция, чтобы делать delay
486 мог быть от 25 МГц до 80(2×40) МГц или 100(3×33) МГц.
386 мог быть от 16 МГц до 40 МГц.
Т. е. можно было нарваться и на 40-мегагерцовую «трёшку», и на 25-мегагерцовую «четвёрку».
на всех трёх
в 1987-м году
mov ss загружает сегмент стека? зачем запрещает? для какой-то дыры в безопансоти?
mov ss, ...
mov sp, ...
И наебнуться от прерывания между ними.
напихали локи в какие-то места что что-то работало
Почему нельзя было сказать так
* все инструкции никогда явно не берут лок
* бери лок сам
* хочет пирформанса -- вот тебе спец атомарная инструкция, которую проц оптимизирует
Напоминает код в одном проекте, где чуваки в случайные места вставили синхронизацию к комментом "без этого чото падает иногда". без царя вголове
процы оптимизируют под хуевый код
а потом код оптимизируют под хуевые процы
и так по кругу
Это расширение «Проводника», чтобы файл можно было мышкой кинуть на окошко, в котором отображается FTP. Почему они упомянули «Exchange», я не понял.
DDE это dyn data Exchange, лол)
Где-то там были и патчи ОС под кривые прикладные программы.
Это значит, какой-то пидарас сломал компиляцию, и ушел домой
У нас есть safe push плагин, чтобы сначала код гонялся на TC, а потом попадал в мастер, но некоторые забивают
Да иногда просто не везёт. На той версии мастера, которая была перед мержом твоих изменений и запуском тестов всё было норм. Но за это время кто-то уже пролез в мастер. И всё сломалось.
Полную сериализацию коммитов никто в здравом уме делать не будет. Поэтому иногда shit happens.
Придётся тесты очень сильно поджимать по времени и вбрасывать кучу железа на их распараллеливание.
По-моему это не особо пригодно для большой команды.
И 100% багов это всё равно не поймает. Ну не сможешь ты всё-всё-всё протестировать в такой сериализованной модели.
* компилировать инкрементально на агентах
* гонять только самые основные тесты
но железа нужно много, это правда.
Если у тебя в кодовой базе более ста питухов тусит за раз, то может пора разбиваться на модули, и собирать их отдельно?
Правда, придется думать про апи и аби: нельзя будет больше одним ловким рефакторингом переименовать метод
>И 100% багов это всё равно не поймает.
никакие тесты не ловят 100 процентов, иначе багов бы не было
ну хотяб прошла компиляция и смок тесты
Ну вопрос в том, насколько полная сериализация коммитов ловит больше багов, чем более слабая но оптимистичная. И насколько она дороже обходится по железу и усилиям программистов.
Если была пара поломок мастера в месяц, то может и хуй с ними?
З.Ы. Я не против прекоммит тестов, я против их полной сериализации.
поломка мастера может быть очень болезненной: я как-то собирал openjdk, и попал на несобираемый мастер, и пытался понять где я обосрался, а потом приехал следующий коммит и все починилось
у нас правда ботик срет в чатик когда компилция падает на CI, так что обычно все знают
З.Ы. Беру свои слова обратно. gcc -O2 не осилило xor-swap оптимизнуть. А вот с std::swap весь твой extract_bits<uint32_t, uint32_t>(n, 0, 31) свернулся в одну ассемблерную инструкцию bswap.
Ну и с наивным конпелятором, который не умеет в эту идиому, превращается в сраное говнище, которое не даёт процу распараллелить выполнение (в обычном swap'е по сути просто регистры заренеймятся).
логично) Я говорил про целочисленные типы.
> обычном swap'е по сути просто регистры заренеймятся
логично, там же под капотом 100500 внутренних регистров, которыv микрокот просто дает имена типа EAX
Скастовать в тип подходящего размера который ксорить можно, и потом поксорить (точнее, сделать указатель на такой-то тип с поддержкой xor, и присвоить в такие указатели адреса на переменные с неподходящим для перексориванием типы, и потом уже поксорить). Обменять float-ы так вполне можно.
Сможешь это написать без ub'ов и короче наивного свапа?
Прощайте, оптимизации, мне будет вас не хватать. Хотя с char * в общем-то так же.
Впрочем, xor swap вообще говоря говно, https://godbolt.org/z/9rT6ax оптимизируется он плохо. Компилятор не может в принципе породить точно такой же код 1 в 1 как при обмене переменных через третью, т.к. если мы меняем переменную саму с собой, xor-swap ее занулит
Ну вот это и есть самое большое говнище в xor-swap. Всё прекрасно работает, а потом кто-нибудь передаёт туда уко-ко-козатели на одинаковые пельменные — и пиздец.
Ну можно, только strict aliasing не забудь отключить.
Я прпвильно понимаю, что все ваши may_alias и откелючение strict aliasing по пизде пойдут?
Это ты про Эльбрус?
uint16_t __builtin_bswap16 (uint16_t x)
uint32_t __builtin_bswap32 (uint32_t x)
uint64_t __builtin_bswap64 (uint64_t x)
uint128_t __builtin_bswap128 (uint128_t x)
из GCC, но Clang их вроде тоже умеет.
У MSVC есть под это своя елда:
Еще есть интелевые интринсики:
https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_bswap
И более-менее стандартный htonl() ntohl() (В стандарте Си нет, но в POSIX есть).
В крестоговно почему-то не завезли никакой специальной хуйни для разворота байтиков, но зато там можно функцию Бесселя считать.
Ну это не совсем так, в последнем стандарте C++ описывается, что представление signed чисел может быть только в "two's complement" (дополнительный код). http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p0907r4.html
И если это не описываеся, почему б это не описать тогда?
С другой стороны, функции которые ты привёл - это такая же чистая математика как и функции бесселя. Можно и добавить, даже если на какой-нибудь платформе в духе PDP они не имеют никакого смысла.
З.Ы. А потом захочется вращения чисел и поиска старшего бита.
Нет, пожалуй это ближе к области байтоебства, чем к математике. Зачем математикам разворачивать байтики?