Члены класса и aliasing
От: Шахтер Интернет  
Дата: 02.05.20 10:07
Оценка: 27 (5)
Небольшой пример.

struct Count
 {
  int val;

  explicit Count(PtrLen<const int> r) CCORE_NOINLINE // no aliasing
   {
    val=0;

    for(int v : r ) val+=v;
   }

  void sum(PtrLen<const int> r) CCORE_NOINLINE // possible aliasing with val
   {
    val=0;

    for(int v : r ) val+=v;
   }
 }; 

int Sum(PtrLen<const int> r) // no aliasing
 {
  int val=0;

  for(int v : r ) val+=v;

  return val;
 }


Здесь GCC при генерации кода делает предположения о возможном алиасинге данных.

В конструкторе он предполагает отсутствие такового. Поэтому он хорошо раскручивает код с использованием SIMD инструкций.

В методе sum() он предполагает возможность алиасинга. Поэтому код получается менее оптимальным.

В функции Sum() он опять предполагает отсутствие алиасинга и генерирует более оптимальный код.

Мораль.

1) Следите за алиасингом при программировании на скорость.
2) Локальные переменные лучше членов класса.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re: Члены класса и aliasing
От: andyp  
Дата: 02.05.20 20:58
Оценка:
Здравствуйте, Шахтер, Вы писали:

Ш>Небольшой пример.


  Код
Ш>
Ш>struct Count
Ш> {
Ш>  int val;

Ш>  explicit Count(PtrLen<const int> r) CCORE_NOINLINE // no aliasing
Ш>   {
Ш>    val=0;

Ш>    for(int v : r ) val+=v;
Ш>   }

Ш>  void sum(PtrLen<const int> r) CCORE_NOINLINE // possible aliasing with val
Ш>   {
Ш>    val=0;

Ш>    for(int v : r ) val+=v;
Ш>   }
Ш> }; 

Ш>int Sum(PtrLen<const int> r) // no aliasing
Ш> {
Ш>  int val=0;

Ш>  for(int v : r ) val+=v;

Ш>  return val;
Ш> }
Ш>


Ш>Здесь GCC при генерации кода делает предположения о возможном алиасинге данных.


Ш>В конструкторе он предполагает отсутствие такового. Поэтому он хорошо раскручивает код с использованием SIMD инструкций.


Ш>В методе sum() он предполагает возможность алиасинга. Поэтому код получается менее оптимальным.


Ш>В функции Sum() он опять предполагает отсутствие алиасинга и генерирует более оптимальный код.


Небольшой вопрос, если бы в примере копили в private член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен.

PS Можно немного почитить и копить в int64_t
Re[2]: Члены класса и aliasing
От: watchmaker  
Дата: 02.05.20 21:06
Оценка: 6 (1)
Здравствуйте, andyp, Вы писали:


A>Небольшой вопрос, если бы в примере копили в private член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен.

Почему это не может?

Count ca[100]{...};

ca[53].sum({begin(ca), end(ca)});

Это даже не хак для StandardLayoutType
Re[3]: Члены класса и aliasing
От: andyp  
Дата: 02.05.20 21:36
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>
W>Count ca[100]{...};

W>ca[53].sum({begin(ca), end(ca)});
W>


Спасибо. Как-то не думал в эту сторону.
Re[3]: Члены класса и aliasing
От: σ  
Дата: 02.05.20 23:01
Оценка:
A>>Небольшой вопрос, если бы в примере копили в private член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен.
W>Почему это не может?

W>
W>Count ca[100]{...};

W>ca[53].sum({begin(ca), end(ca)});
W>

W>Это даже не хак для StandardLayoutType

Щито ты этим хотел сказать? Что ты типа можешь `Count ca[100]` использовать как `int ca[100]`, если `int val` — единственный мембер `Count` (и он во всех остальных отношениях standard-layout class)? Не можешь.

Что можно — это
Count c { ... };
c.sum({ reinterpret_cast<int*>(&c), 1 });
Отредактировано 02.05.2020 23:59 σ . Предыдущая версия .
Re[3]: Члены класса и aliasing
От: Шахтер Интернет  
Дата: 03.05.20 10:15
Оценка:
Здравствуйте, watchmaker, Вы писали:

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



A>>Небольшой вопрос, если бы в примере копили в private член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен.

W>Почему это не может?

W>
W>Count ca[100]{...};

W>ca[53].sum({begin(ca), end(ca)});
W>

W>Это даже не хак для StandardLayoutType

Только если sizeof(Count) == sizeof (int) .
Можно, конечно, сделать массив длинны 1.

Ну и, конечно, надо вызывать вот так


ca[53].sum( {(int *)&ca,DimOf(ca)} );
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re[4]: Члены класса и aliasing
От: σ  
Дата: 03.05.20 16:05
Оценка:
A>>>Небольшой вопрос, если бы в примере копили в private член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен.
W>>Почему это не может?

W>>
W>>Count ca[100]{...};

W>>ca[53].sum({begin(ca), end(ca)});
W>>

W>>Это даже не хак для StandardLayoutType

Ш>Только если sizeof(Count) == sizeof (int) .

Ш>Можно, конечно, сделать массив длинны 1.

Ш>Ну и, конечно, надо вызывать вот так


Ш>

Ш>ca[53].sum( {(int *)&ca,DimOf(ca)} );

Ш>


UB даже при `sizeof(Count) == sizeof (int)`.
Re[5]: Члены класса и aliasing
От: Шахтер Интернет  
Дата: 03.05.20 19:31
Оценка:
Здравствуйте, σ, Вы писали:

Ш>>Ну и, конечно, надо вызывать вот так


Ш>>

Ш>>ca[53].sum( {(int *)&ca,DimOf(ca)} );

Ш>>


σ>UB даже при `sizeof(Count) == sizeof (int)`.


Это почему? Каст (int *)&ca полностью легален. Хотя лучше сделать так &ca->val . Здесь то уж точно никакого криминала.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re[6]: Члены класса и aliasing
От: σ  
Дата: 03.05.20 19:52
Оценка: -1
Ш>>>Ну и, конечно, надо вызывать вот так

Ш>>>

Ш>>>ca[53].sum( {(int *)&ca,DimOf(ca)} );

Ш>>>


σ>>UB даже при `sizeof(Count) == sizeof (int)`.


Ш>Это почему? Каст (int *)&ca полностью легален. Хотя лучше сделать так &ca->val . Здесь то уж точно никакого криминала.


Потому что [expr.add]/4.
Для легальности каста `sizeof(Count) == sizeof (int)` не нужен.
А для легальности арифметики с полученным указателем `sizeof(Count) == sizeof (int)` значения не имеет. Она нелегальна в любом случае.
Re[7]: Члены класса и aliasing
От: Шахтер Интернет  
Дата: 03.05.20 20:57
Оценка: -1 :)
Здравствуйте, σ, Вы писали:

Ш>>>>Ну и, конечно, надо вызывать вот так


Ш>>>>

Ш>>>>ca[53].sum( {(int *)&ca,DimOf(ca)} );

Ш>>>>


σ>>>UB даже при `sizeof(Count) == sizeof (int)`.


Ш>>Это почему? Каст (int *)&ca полностью легален. Хотя лучше сделать так &ca->val . Здесь то уж точно никакого криминала.


σ>Потому что [expr.add]/4.

σ>Для легальности каста `sizeof(Count) == sizeof (int)` не нужен.
σ>А для легальности арифметики с полученным указателем `sizeof(Count) == sizeof (int)` значения не имеет. Она нелегальна в любом случае.

Бред. Она полностью легальна. Если стандарт в этом месте плохо прописан -- это проблема кривых рук авторов стандарта.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Re[8]: Члены класса и aliasing
От: σ  
Дата: 03.05.20 21:10
Оценка: -1 :)
Ш>Бред. Она полностью легальна. Если стандарт в этом месте плохо прописан -- это проблема кривых рук авторов стандарта.
Отредактировано 04.05.2020 1:59 σ . Предыдущая версия .
Re[9]: Члены класса и aliasing
От: Шахтер Интернет  
Дата: 04.05.20 04:12
Оценка: +1 -1
Здравствуйте, σ, Вы писали:

Ш>>Бред. Она полностью легальна. Если стандарт в этом месте плохо прописан -- это проблема кривых рук авторов стандарта.

σ>

Если в памяти лежат подряд объекты одного типа, вы совершенно законно можете ездить по ним указателем.
И это не хак, а базовое свойство С++ как языка. Если вы этого не понимаете, то вам в этом мире делать нечего, отправляйтесь лучше в Яву.

Без этого свойства, например, невозможно реализовать vector (или любой аналогичный класс), или более навороченные контейнеры объектов.

А что касается стандарта, то он не создаёт язык, а лишь формализует его. Не всегда достаточно правильно.
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Отредактировано 04.05.2020 4:21 Шахтер . Предыдущая версия .
Re[10]: Члены класса и aliasing
От: σ  
Дата: 04.05.20 13:12
Оценка: -1
Ш>Если в памяти лежат подряд объекты одного типа, вы совершенно законно можете ездить по ним указателем.
Бред.

Ш>Без этого свойства, например, невозможно реализовать vector (или любой аналогичный класс), или более навороченные контейнеры объектов.

Бред.

Ш>А что касается стандарта, то он не создаёт язык, а лишь формализует его.

Формализует разумные действия. К которым удаление гланд через жопу не относится.

Ш> Не всегда достаточно правильно.

Пруф, что в том месте стандарт неправилен.
Отредактировано 04.05.2020 18:23 σ . Предыдущая версия .
Re[6]: Члены класса и aliasing
От: andrey.desman Россия  
Дата: 05.05.20 10:36
Оценка:
Здравствуйте, Шахтер, Вы писали:

Ш>Это почему? Каст (int *)&ca полностью легален.


В плюсах не легален. Это ж как раз aliasing. Точнее, сам каст-то легален, но пользоваться им нельзя.

Ш>Хотя лучше сделать так &ca->val . Здесь то уж точно никакого криминала.


Так да, но там речь шла про private член, так что просто не сможешь.
Но всегда можно геттер запилить, чтобы он этот адрес возвращал, так что приватность не роялит никак.
Отредактировано 05.05.2020 10:38 andrey.desman . Предыдущая версия .
Re[6]: Члены класса и aliasing
От: σ  
Дата: 05.05.20 14:20
Оценка: -1
Ш>>>Ну и, конечно, надо вызывать вот так
Ш>>>
ca[53].sum( {(int *)&ca,DimOf(ca)} );

σ>>UB даже при `sizeof(Count) == sizeof (int)`.
Ш>Это почему? Каст (int *)&ca полностью легален.

Ой-вей, `ca` ведь ещё и массив. Тут UB будет из-за [expr.add]/6.
Из-за [expr.add]/4 UB будет при использовании `&ca->val`.
Отредактировано 05.05.2020 19:21 σ . Предыдущая версия . Еще …
Отредактировано 05.05.2020 16:12 σ . Предыдущая версия .
Re[7]: Члены класса и aliasing
От: Шахтер Интернет  
Дата: 06.05.20 05:53
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Здравствуйте, Шахтер, Вы писали:


Ш>>Это почему? Каст (int *)&ca полностью легален.


AD>В плюсах не легален. Это ж как раз aliasing. Точнее, сам каст-то легален, но пользоваться им нельзя.


Aliasing объекта и его члена легален.

Для Standard Layout Types можно получить указатель на первый член класса явным кастом.


struct Alpha
 {
  [private:]

  int val;
 }; 

Alpha obj{};

int *ptr=(int *)&obj; // ptr == &obj.val


Таким способом можно пробить защиту (сюрприз!).
В XXI век с CCore.
Копай Нео, копай -- летать научишься. © Matrix. Парадоксы
Отредактировано 06.05.2020 6:36 Шахтер . Предыдущая версия .
Re[8]: Члены класса и aliasing
От: andyp  
Дата: 06.05.20 09:05
Оценка:
Здравствуйте, Шахтер, Вы писали:

Ш>Таким способом можно пробить защиту (сюрприз!).


Да уж, тогда действительно компилятор может исключить алиасинг только для

1. указателей других типов (сюда попадает то, что называется strict aliasing rules)

2. В конструкторе — память формально ещё не доступна для остальной программы, если эта программа не ill formed.

3. В стеке. Все, что пришло в качестве аргументов не должно указывать на текущий стек фрейм, опять же, если прога не ill formed

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

Спасибо, что поднял тему и помог более-менее разобраться с этим.
Re[9]: Члены класса и aliasing
От: σ  
Дата: 06.05.20 14:23
Оценка:
A>2. В конструкторе — память формально ещё не доступна для остальной программы, если эта программа не ill formed.
A>3. В стеке. Все, что пришло в качестве аргументов не должно указывать на текущий стек фрейм, опять же, если прога не ill formed
Щас бы, в 2k20, путать well-formed/ill-formed и (un)defined behavior.

Кстати, насчёт третьего пункта. Был в C++ (с 03 до 17, ЕМНИП, влом проверять) такой пассаж
https://i.paste.pics/1c503b8f695dcf88fba413bda2983bcb.png
  Скрытый текст
который некоторые "гении" вроде ОП-а использовали как оправдание бредням типа
Ш>Если в памяти лежат подряд объекты одного типа, вы совершенно законно можете ездить по ним указателем.

Почему, например, вызываемая функция не могла угадать адрес аргумента или локальной переменной вызывающей функции, без прямой передачи ей указателя на них или без записи такого указателя в глобальную переменную и т.п.? Вроде этот пассаж разрешает. Хоть из /dev/random считать биты и слепить из них значение указателя.
Отредактировано 06.05.2020 14:44 σ . Предыдущая версия .
Re: Члены класса и aliasing
От: Alexander G Украина  
Дата: 06.05.20 14:33
Оценка:
А в локальную переменную.

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

И продолжает помогать.

struct Count
 {
  int val;


  void sum(PtrLen<const int> r)
   {
    int tmp_val=0;

    for(int v : r ) tmp_val+=v;
    val = tmp_val;
   }
 }
Re[10]: Члены класса и aliasing
От: andyp  
Дата: 06.05.20 14:41
Оценка:
Здравствуйте, σ, Вы писали:

σ>Почему, например, вызываемая функция не могла угадать адрес аргумента или локальной переменной вызывающей функции, без прямой передачи ей указателя на них или без записи такого указателя в глобальную переменную и т.п.? Вроде этот пассаж разрешает. Хоть из /dev/random считать биты и слепить из них значение указателя.


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