Как обойти strict aliasing rule?
От: Eeel Россия  
Дата: 16.06.16 22:40
Оценка: 3 (1)
У меня есть некая структурка данных, и к ней надо обращаться то как к двум 32-битным беззнаковым интам, то как к одному 64-битному беззнаковому инту.

По сути, это union. Но, насколько я понимаю, через union по стандарту C++ этого делать нельзя, так как это нарушение strict aliasing и UB (записывать один тип, а читать другой, несовместимый, тип).

struct Foo
{
    union
    {
        std::uint64_t value64;

        struct 
        {
            std::uint32_t value32Lo;

            std::uint32_t value32Hi;           
        };
    };
};


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

Еще у меня была мысль про буфер char'ов: по стандарту любой тип можно алиазить массивом чаров, тогда что если записать этот в массив std::uint64_t, а прочитать два std::uint32_t — будет ли это нарушением strict aliasing rule? Очевидный ответ — да, поскольку читается один тип, а записывается другой. А может и нет. Этот момент мне неясен.

struct Foo
{
    alignas(std::uint64_t) char buffer[sizeof(std::uint64_t)];
};
Отредактировано 27.06.2016 9:56 S. Schlongberg . Предыдущая версия . Еще …
Отредактировано 21.06.2016 16:20 S. Schlongberg . Предыдущая версия .
Отредактировано 21.06.2016 9:55 S. Schlongberg . Предыдущая версия .
Отредактировано 21.06.2016 9:55 S. Schlongberg . Предыдущая версия .
Отредактировано 17.06.2016 21:01 S. Schlongberg . Предыдущая версия .
Re: Как обойти strict aliasing rule?
От: Erop Россия  
Дата: 17.06.16 00:43
Оценка: +1
Здравствуйте, Eeel, Вы писали:

E>У меня есть некая структурка данных, и к ней надо обращаться то как к двум 32-битным беззнаковым интам, то как к одному 64-битному беззнаковому инту.


А зачем? В том смысле, что на разных платформах могут быть разные эндианы, и одно и тоже 64-битное число таким образом будет мапиться на разные пар 32-битных.
Тебе надо физически с битиками памяти работать, или таки нужны логически старшие и младшие биты?

А ещё есть второй вопрос, зачем ты хочешь, обязательно читать/писать память, а не двигать битики? Вдруг двигать битики быстрее? Например, во многих x86 процах смесь 32-битных и 64-битных инструкций дорогая...
Зачем ты хочешь мешать оптимизатору?

В общем, если ты хочешь работать с памятью напрямую, то есть речь о чём-то железячном, то пиши, читай прямо по адресам. Всё равно это никак не переносимо, а если таки речь идёт об операциях с числами, то бери сдвиги/маски и вперёд...
Только не ясно, зачем тебе маски...
struct Foo
{
    std::uint32_t value32Lo;

    std::uint32_t value32Hi;    

    enum { BITS_IN_LO = 32 };
    std::uint64_t value64() const { return value32Lo|(((std::uint64_t)value32Hi)<<BITS_IN_LO); }
    void set( std::uint64_t v )
    {
        value32Lo = (std::uint32_t)v;
        value32Hi = (std::uint32_t)(v>>BITS_IN_LO);
    } 
};


E>Еще у меня была мысль про буфер char'ов: по стандарту любой тип можно алиазить массивом чаров, тогда что если записать этот в массив std::uint64_t, а прочитать два std::uint32_t — будет ли это нарушением strict aliasing rule? Очевидный ответ — да, поскольку читается один тип, а записывается другой. А может и нет. Этот момент мне неясен.



Конечно, потому, что есть разыне endians, кроме того, ещё и с выравниванием могут быть проблемы, при таком подходе...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Отредактировано 17.06.2016 9:28 Erop . Предыдущая версия .
Re: Как обойти strict aliasing rule?
От: placement_new  
Дата: 17.06.16 04:21
Оценка:
Здравствуйте, Eeel, Вы писали:

E>По сути, это union. Но, насколько я понимаю, через union по стандарту C++ этого делать нельзя, так как это нарушение strict aliasing и UB (записывать один тип, а читать другой, несовместимый, тип).


Strictly speaking, reading a member of a union different from the one written to is undefined in ANSI/ISO C99 except in the special case of type-punning to a char*, similar to the example below: Casting to char*. However, it is an extremely common idiom and is well-supported by all major compilers. As a practical matter, reading and writing to any member of a union, in any order, is acceptable practice.

http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
Re: Как обойти strict aliasing rule?
От: Videoman Россия https://hts.tv/
Дата: 17.06.16 08:35
Оценка:
Интересно, вопрос вроде по тойже теме, а тут:
struct Foo
{
    union
    {
        std::uint32_t value[2];

        struct 
        {
            std::uint32_t value32Lo;

            std::uint32_t value32Hi;           
        };
    };
};


тут такая же проблема с выравниванием, или, из-за того что типы одинаковые, гарантий больше ?
Re[2]: Как обойти strict aliasing rule?
От: Eeel Россия  
Дата: 17.06.16 10:10
Оценка:
Здравствуйте, Erop, Вы писали:

E>А зачем? В том смысле, что на разных платформах могут быть разные эндианы, и одно и тоже 64-битное число таким образом будет мапиться на разные пар 32-битных.


Это в теории, а на практике будет только x86/64 — либо i7, либо Xeon. А big endian я просто не буду поддерживать, как как для этого нужно во всем коде специально делать поддержку и тщательно его весь именно для этого тестировать, что совершенно не стоит потраченных усилий. Так же, как я не собираюсь поддерживать системы меньше 64 бит.

Просто мне не хочется писать код, который в явном виде нарушает strict aliasing и, таким образом, зависит от опций компиляции.
E>Тебе надо физически с битиками памяти работать, или таки нужны логически старшие и младшие биты?

Ну мне надо, например, параллельно сложить нижнее и верхнее 32-битные слова (нижнее с нижним, верхнее с верхним), так, чтобы при сложении нижнего не было переноса в верхнее. Но потом использовать это все как одно 64-битное слово.

E>А ещё есть второй вопрос, зачем ты хочешь, обязательно читать/писать память, а не двигать битики? Вдруг двигать битики быстрее? Например, во многих x86 процах смесь 32-битных и 64-битных инструкций дорогая...

E>Зачем ты хочешь мешать оптимизатору?

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

Меня просто еще интересует концептуальная возможность обходить strict aliasing rule, так как оно является очень большим ограничением.

E>В общем, если ты хочешь работать с памятью напрямую, то есть речь о чём-то железячном, то пиши, читай прямо по адресам. Всё равно это никак не переносимо, а если таки речь идёт об операциях с числами, то бери сдвиги/маски и вперёд...


Ну отмена переносимости кода мало помогает, так как сама по себе не отменяет правила strict aliasing в компиляторах. А от опций и версий конкретных компиляторов не хочется зависеть.

На самом деле, вопрос "зачем" — это несколько другой вопрос (как обычно) и слегка оффтопик.

Меня интересует принципиальный вопрос обхода strict aliasing rule и, прежде всего, ответ на следующий вопрос:

Еще у меня была мысль про буфер char'ов: по стандарту любой тип можно алиазить массивом чаров, тогда что если записать этот в массив std::uint64_t, а прочитать два std::uint32_t — будет ли это нарушением strict aliasing rule? Очевидный ответ — да, поскольку читается один тип, а записывается другой. А может и нет. Этот момент мне неясен.

struct Foo
{
    alignas(std::uint64_t) char buffer[sizeof(std::uint64_t)];
};
Отредактировано 27.06.2016 18:05 S. Schlongberg . Предыдущая версия . Еще …
Отредактировано 21.06.2016 16:20 S. Schlongberg . Предыдущая версия .
Отредактировано 21.06.2016 9:53 S. Schlongberg . Предыдущая версия .
Отредактировано 17.06.2016 10:30 S. Schlongberg . Предыдущая версия .
Отредактировано 17.06.2016 10:23 S. Schlongberg . Предыдущая версия .
Re[3]: Как обойти strict aliasing rule?
От: Erop Россия  
Дата: 17.06.16 10:44
Оценка:
Здравствуйте, Eeel, Вы писали:

E>Ну мне надо, например, параллельно сложить нижнее и верхнее 32-битные слова (нижнее с нижним, верхнее с верхним), так, чтобы при сложении нижнего не было переноса в верхнее. Но потом использовать это все как одно 64-битное слово.


Ну, например
a + b - (1<<32)&((a+b)^~(a^b))
И пусть себе там компилятор разбирается, как оптимизировать?
Ещё всякие векторные инструкции для этого рулят...

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

Его надо делать на всех платформах... Стоит ли выигрыш затрат?

E>Меня просто еще интересует концептуальная возможность обходить strict aliasing rule, так как оно является очень большим ограничением.

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

E>Ну отмена переносимости кода мало помогает, так как сама по себе не отменяет правила strict aliasing в компиляторах. А от опций и версий конкретных компиляторов не хочется зависеть.

Если бы тебе нужна была именно работа с битиками памяти, например в конце стояло бы устройство отображённое на память, то как раз от опций компилятора ты бы тогда не зависел. Возможно, твой код в разных случаях интерпретировал бы ОДНИ И ТЕ ЖЕ битики по ОДНИМ И ТЕМ ЖЕ адресам, как разные числа, но когда дело доходило бы до аппаратных битиков, всё было бы правильно.

А тебе таки нужны числа, а не аппаратные битики. Вот с числами и работай. Так ндёжно, а обходить — мешать оптимизатору.
Иногда оптимизаторы лажают, конечно, но редко. И там, под конкретный оптимизатор, если задача очень нагруженная, можно сделать отдельную ветку кода, завязанную на особенности конкретного транслятора в конкретных настройках. Только это никак со strict aliasing rule не связано, оптимизаторы могут лажать сотнями способов...

E>

E>Еще у меня была мысль про буфер char'ов: по стандарту любой тип можно алиазить массивом чаров, тогда что если записать этот в массив std::uint64_t, а прочитать два std::uint32_t — будет ли это нарушением strict aliasing rule? Очевидный ответ — да, поскольку читается один тип, а записывается другой. А может и нет. Этот момент мне неясен.

Очевидный ответ верный. Просто сравни поведение на двух системах, где разный эндиан, и сразу поймёшь, что это так и есть...
Кроме того, при таком подходе ещё и проблемы с выравниванием добавятся...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[2]: Как обойти strict aliasing rule?
От: Erop Россия  
Дата: 17.06.16 10:46
Оценка:
Здравствуйте, placement_new, Вы писали:

_>Strictly speaking, reading a member of a union different from the one written to is undefined in ANSI/ISO C99 except in the special case of type-punning to a char*, similar to the example below: Casting to char*. However, it is an extremely common idiom and is well-supported by all major compilers. As a practical matter, reading and writing to any member of a union, in any order, is acceptable practice.


Только оптимизацию может нагнуть...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: Как обойти strict aliasing rule?
От: T4r4sB Россия  
Дата: 17.06.16 19:17
Оценка: 2 (1) +1
memmove позволяет битово копировать данные разных типов без УБЭ. Но от огребания от индейца не спасёт, как уже сказали.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[2]: Как обойти strict aliasing rule?
От: Erop Россия  
Дата: 18.06.16 07:29
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>memmove позволяет битово копировать данные разных типов без УБЭ. Но от огребания от индейца не спасёт, как уже сказали.


Тока это нагнёт оптимизацию ещё мощнее...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[3]: Как обойти strict aliasing rule?
От: watchmaker  
Дата: 18.06.16 08:10
Оценка: 2 (1)
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, T4r4sB, Вы писали:


TB>>memmove позволяет битово копировать данные разных типов без УБЭ. Но от огребания от индейца не спасёт, как уже сказали.


E>Тока это нагнёт оптимизацию ещё мощнее...


А что за компилятор, который не умеет memmove?
Например, gcc и clang заменяют вызов функции на простое чтение из памяти даже при полностью выключенной оптимизации. Так что каст через memcpy может использоваться для борьбы с алиасингом. https://godbolt.org/g/mtEgIY
Re[4]: Как обойти strict aliasing rule?
От: Erop Россия  
Дата: 18.06.16 08:26
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>А что за компилятор, который не умеет memmove?

W>Например, gcc и clang заменяют вызов функции на простое чтение из памяти даже при полностью выключенной оптимизации. Так что каст через memcpy может использоваться для борьбы с алиасингом. https://godbolt.org/g/mtEgIY

ТС'у нужны операции, вроде сложить два 64-х числа без переноса между 31-м и 32-м битом.

Ты правда думаешь, что реализация через memmove не напряжёт оптимизатор?
Я, например, не в курсе, но как-то сомневаюсь, то оно хорошо соптимизируется...

Хотя вопрос, конечно, интересный.

Я думаю, что самый дружественный к оптимизации подход будет где-то тут:
a + b - ((1ULL<<32)&((a+b)^~(a^b)))

Но может, конечно, и через химию с памятью быстрее, но как-то мало верится. Например, не понятно, что скорее будет векторизовано...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[5]: Как обойти strict aliasing rule?
От: watchmaker  
Дата: 18.06.16 08:56
Оценка: +1
Здравствуйте, Erop, Вы писали:

E>Здравствуйте, watchmaker, Вы писали:


W>>А что за компилятор, который не умеет memmove?

W>>Например, gcc и clang заменяют вызов функции на простое чтение из памяти даже при полностью выключенной оптимизации. Так что каст через memcpy может использоваться для борьбы с алиасингом. https://godbolt.org/g/mtEgIY

E>ТС'у нужны операции, вроде сложить два 64-х числа без переноса между 31-м и 32-м битом.


Извини, но я отвечал только на выпад в сторону memcpy/memmove. Насколько я понял, ТС больше интересуется правилами алиасинга, а не конкретной задачей.

Так то, конечно, для задачи сложения двух чисел, такое использование memcpy выглядит избыточном. Особенно в свете ориентации ТС на архитектуру x86, ибо там уже лет 20 есть инструкция paddd, которая делает ровно то что нужно — складывает у двух 64-х битных чисел нижние и верхние половинки без переноса между ними. Если уж была бы важна скорость, то стоит просто этой инструкцией воспользоваться (например через соответствующий интринсик).
Re[3]: Как обойти strict aliasing rule?
От: T4r4sB Россия  
Дата: 18.06.16 11:12
Оценка:
Здравствуйте, Erop, Вы писали:

E>Тока это нагнёт оптимизацию ещё мощнее...


Нет, нормальные компиляторы нормально это воспринимают, а вот хвалёный ИЦЦ начинает нести бред при виде меммува, это да. Это гениально, оптимизировать только те способы, которые по стандарту работать не обязаны, а на то, что по стандарту — самый честный способ — вообще забить.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[4]: Как обойти strict aliasing rule?
От: Erop Россия  
Дата: 18.06.16 11:23
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Нет, нормальные компиляторы нормально это воспринимают, а вот хвалёный ИЦЦ начинает нести бред при виде меммува, это да. Это гениально, оптимизировать только те способы, которые по стандарту работать не обязаны, а на то, что по стандарту — самый честный способ — вообще забить.


Стоит знать не только стандарт, но и причины заложенных в него ограничений и популярные реализации...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[4]: Как обойти strict aliasing rule?
От: Eeel Россия  
Дата: 18.06.16 11:52
Оценка: 5 (1)
Здравствуйте, Erop, Вы писали:

E>Ну, например a + b — (1<<32)&((a+b)^~(a^b)) И пусть себе там компилятор разбирается, как оптимизировать?


MSVC 2015 Update 2, похоже, не разобрался, а просто дубово выполняет битовые операции.

    mov    rax, QWORD PTR [rcx]
    lea    rcx, QWORD PTR [rax+rdx]
    xor    rax, rdx
    not    rax
    xor    rax, rcx
    and    rax, rcx
    ret    0
Отредактировано 27.06.2016 18:06 S. Schlongberg . Предыдущая версия . Еще …
Отредактировано 21.06.2016 16:20 S. Schlongberg . Предыдущая версия .
Отредактировано 21.06.2016 9:53 S. Schlongberg . Предыдущая версия .
Re[5]: Как обойти strict aliasing rule?
От: Erop Россия  
Дата: 18.06.16 12:24
Оценка:
Здравствуйте, Eeel, Вы писали:

E>>Ну, например
a + b - (1ull<<32)&((a+b)^~(a^b))
И пусть себе там компилятор разбирается, как оптимизировать?


E>MSVC 2015 Update 2, похоже, не разобрался, а просто дубово выполняет битовые операции.


E>
E>    mov    rax, QWORD PTR [rcx]
E>    lea    rcx, QWORD PTR [rax+rdx]
E>    xor    rax, rdx
E>    not    rax
E>    xor    rax, rcx
E>    and    rax, rcx
E>    ret    0
E>


А где тут минус

и and с чем-то не тем...
Тут или какой-то хитрый рюх или 1<<32 не в то посчитался, (лучше, конечно, 1ULL<<32 писать) или ещё чего...
Если таки это эквивалентный код, во что трудно поверить, то может он и быстрее, кстати...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re[6]: Как обойти strict aliasing rule?
От: Eeel Россия  
Дата: 18.06.16 13:08
Оценка:
Здравствуйте, Erop, Вы писали:

E>А где тут минус

E>и and с чем-то не тем...
E>Тут или какой-то хитрый рюх или 1<<32 не в то посчитался, (лучше, конечно, 1ULL<<32 писать) или ещё чего...

Наверно, потому что имелось в виду

a + b — ( (1ull<<32)&((a+b)^~(a^b)) )

(В оригинале суффикса ull не было.)

Теперь получается

    mov    rax, QWORD PTR [rcx]
    mov    r8, rax
    xor    r8, rdx
    not    r8
    lea    rcx, QWORD PTR [rax+rdx]
    xor    r8, rcx
    mov    rcx, 4294967296                ; 0000000100000000H
    and    r8, rcx
    sub    rax, r8
    add    rax, rdx
    ret    0
Отредактировано 27.06.2016 18:07 S. Schlongberg . Предыдущая версия . Еще …
Отредактировано 21.06.2016 9:52 S. Schlongberg . Предыдущая версия .
Отредактировано 18.06.2016 15:08 S. Schlongberg . Предыдущая версия .
Отредактировано 18.06.2016 13:09 S. Schlongberg . Предыдущая версия .
bit_cast
От: Eeel Россия  
Дата: 18.06.16 13:13
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>memmove позволяет битово копировать данные разных типов без УБЭ. Но от огребания от индейца не спасёт, как уже сказали.


Похоже, memcpy/memmove самый годный способ. Clang, GCC и MSVC его оптимизируют на mov. Хотя, окончательное решение, что использовать, буду принимать на основе бенчмарков.

В проекте Chromium даже есть функция bit_cast, которая именно так и сделана.

https://chromium.googlesource.com/chromium/src/+/1587f7d/base/macros.h#76

template <class Dest, class Source>
inline Dest bit_cast(const Source& source) {
  COMPILE_ASSERT(sizeof(Dest) == sizeof(Source), VerifySizesAreEqual);
  Dest dest;
  memcpy(&dest, &source, sizeof(dest));
  return dest;
}


А вот с memmove еще интересней. Согласно пунктам 3.9.2 и 3.9.3 стандарта C++ 14, получается, я могу сделать так:

template <class Dest, class Source>
inline Dest& bit_cast(Source& source) {
    // Проверки
    
    return *((Dest*)std::memmove(&source, &source, sizeof(Dest)));
}


То есть обход strict aliasing rule заключается в вызове ничего не делающей memmove.

Update: Хотя нет, не получается. Там написано:
For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2

А у меня объекты-то не различные.
Отредактировано 27.06.2016 18:10 S. Schlongberg . Предыдущая версия . Еще …
Отредактировано 21.06.2016 9:52 S. Schlongberg . Предыдущая версия .
Отредактировано 18.06.2016 14:47 S. Schlongberg . Предыдущая версия .
Отредактировано 18.06.2016 14:43 S. Schlongberg . Предыдущая версия .
Отредактировано 18.06.2016 13:55 S. Schlongberg . Предыдущая версия .
Отредактировано 18.06.2016 13:54 S. Schlongberg . Предыдущая версия .
Отредактировано 18.06.2016 13:53 S. Schlongberg . Предыдущая версия .
Отредактировано 18.06.2016 13:53 S. Schlongberg . Предыдущая версия .
Отредактировано 18.06.2016 13:27 S. Schlongberg . Предыдущая версия .
Re: bit_cast
От: T4r4sB Россия  
Дата: 18.06.16 14:10
Оценка:
Здравствуйте, Eeel, Вы писали:

E>То есть обход strict aliasing rule заключается в вызове ничего не делающей memmove.


Интересное кино, не думал о таком. Но проверять бы не рискнул, т.к. стандарт до запятых не знаю, если что и знаю, то только в целом и общем.
Нет такой подлости и мерзости, на которую бы не пошёл gcc ради бессмысленных 5% скорости в никому не нужном синтетическом тесте
Re[2]: bit_cast
От: Eeel Россия  
Дата: 18.06.16 14:42
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>Интересное кино, не думал о таком. Но проверять бы не рискнул, т.к. стандарт до запятых не знаю, если что и знаю, то только в целом и общем.


Я неправ, так как там написано:

For any trivially copyable type T, if two pointers to T point to distinct T objects obj1 and obj2


А у меня объекты-то не различные, так что нет, так нельзя, видимо.
Отредактировано 27.06.2016 18:11 S. Schlongberg . Предыдущая версия . Еще …
Отредактировано 21.06.2016 9:52 S. Schlongberg . Предыдущая версия .
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.