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) Локальные переменные лучше членов класса.
Здравствуйте, σ, Вы писали:
Ш>>>>Ну и, конечно, надо вызывать вот так
Ш>>>>
Ш>>>>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)` значения не имеет. Она нелегальна в любом случае.
Бред. Она полностью легальна. Если стандарт в этом месте плохо прописан -- это проблема кривых рук авторов стандарта.
Здравствуйте, σ, Вы писали:
Ш>>Бред. Она полностью легальна. Если стандарт в этом месте плохо прописан -- это проблема кривых рук авторов стандарта. σ>
Если в памяти лежат подряд объекты одного типа, вы совершенно законно можете ездить по ним указателем.
И это не хак, а базовое свойство С++ как языка. Если вы этого не понимаете, то вам в этом мире делать нечего, отправляйтесь лучше в Яву.
Без этого свойства, например, невозможно реализовать vector (или любой аналогичный класс), или более навороченные контейнеры объектов.
А что касается стандарта, то он не создаёт язык, а лишь формализует его. Не всегда достаточно правильно.
AG>>>Как могла бы работать функция memcpy, которая копирует структуру через нетипизированный указатель и размер в другую структуру? AG>>>Или такую функцию реализовать средствами С++ невозможно? σ>>Да, невозможно. AD>Это почему?
Недостаточно C++-средств для реализации memcpy.
A>Небольшой вопрос, если бы в примере копили в private член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен.
Почему это не может?
σ>>А для легальности арифметики с полученным указателем `sizeof(Count) == sizeof (int)` значения не имеет. Она нелегальна в любом случае.
AG>Как могла бы работать функция memcpy, которая копирует структуру через нетипизированный указатель и размер в другую структуру? AG>Или такую функцию реализовать средствами С++ невозможно?
AG>>>>>Как могла бы работать функция memcpy, которая копирует структуру через нетипизированный указатель и размер в другую структуру? AG>>>>>Или такую функцию реализовать средствами С++ невозможно? σ>>>>Да, невозможно. AD>>>Это почему? σ>>Недостаточно C++-средств для реализации memcpy. AD>Вот я и спрашиваю, чего именно не хватает?
См. это. (Правда, там вроде wording протух, а новая ревизия пока не в открытом доступе).
Ну или https://rsdn.org/forum/cpp/7312471?tree=tree
σ>>UB даже при `sizeof(Count) == sizeof (int)`.
Ш>Это почему? Каст (int *)&ca полностью легален. Хотя лучше сделать так &ca->val . Здесь то уж точно никакого криминала.
Потому что [expr.add]/4.
Для легальности каста `sizeof(Count) == sizeof (int)` не нужен.
А для легальности арифметики с полученным указателем `sizeof(Count) == sizeof (int)` значения не имеет. Она нелегальна в любом случае.
Ш>Если в памяти лежат подряд объекты одного типа, вы совершенно законно можете ездить по ним указателем.
Бред.
Ш>Без этого свойства, например, невозможно реализовать vector (или любой аналогичный класс), или более навороченные контейнеры объектов.
Бред.
Ш>А что касается стандарта, то он не создаёт язык, а лишь формализует его.
Формализует разумные действия. К которым удаление гланд через жопу не относится.
Ш> Не всегда достаточно правильно.
Пруф, что в том месте стандарт неправилен.
Здравствуйте, Шахтер, Вы писали: Ш>Небольшой пример.
Код
Ш>
Ш>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 член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен.
A>>Небольшой вопрос, если бы в примере копили в private член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен. W>Почему это не может?
W>
Щито ты этим хотел сказать? Что ты типа можешь `Count ca[100]` использовать как `int ca[100]`, если `int val` — единственный мембер `Count` (и он во всех остальных отношениях standard-layout class)? Не можешь.
Что можно — это
Count c { ... };
c.sum({ reinterpret_cast<int*>(&c), 1 });
Здравствуйте, watchmaker, Вы писали:
W>Здравствуйте, andyp, Вы писали:
A>>Небольшой вопрос, если бы в примере копили в private член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен. W>Почему это не может?
W>
A>>>Небольшой вопрос, если бы в примере копили в private член класса, посчитал бы компилятор, что на него внешний указатель алиасить не может? В теории вроде бы как должен. W>>Почему это не может?
W>>
W>>Это даже не хак для StandardLayoutType
Ш>Только если sizeof(Count) == sizeof (int) . Ш>Можно, конечно, сделать массив длинны 1.
Ш>Ну и, конечно, надо вызывать вот так
Ш>
Здравствуйте, Шахтер, Вы писали:
Ш>Это почему? Каст (int *)&ca полностью легален.
В плюсах не легален. Это ж как раз aliasing. Точнее, сам каст-то легален, но пользоваться им нельзя.
Ш>Хотя лучше сделать так &ca->val . Здесь то уж точно никакого криминала.
Так да, но там речь шла про private член, так что просто не сможешь.
Но всегда можно геттер запилить, чтобы он этот адрес возвращал, так что приватность не роялит никак.
Здравствуйте, 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
A>2. В конструкторе — память формально ещё не доступна для остальной программы, если эта программа не ill formed. A>3. В стеке. Все, что пришло в качестве аргументов не должно указывать на текущий стек фрейм, опять же, если прога не ill formed
Щас бы, в 2k20, путать well-formed/ill-formed и (un)defined behavior.
Кстати, насчёт третьего пункта. Был в C++ (с 03 до 17, ЕМНИП, влом проверять) такой пассаж
Скрытый текст
который некоторые "гении" вроде ОП-а использовали как оправдание бредням типа Ш>Если в памяти лежат подряд объекты одного типа, вы совершенно законно можете ездить по ним указателем.
Почему, например, вызываемая функция не могла угадать адрес аргумента или локальной переменной вызывающей функции, без прямой передачи ей указателя на них или без записи такого указателя в глобальную переменную и т.п.? Вроде этот пассаж разрешает. Хоть из /dev/random считать биты и слепить из них значение указателя.
Здравствуйте, σ, Вы писали:
σ>Почему, например, вызываемая функция не могла угадать адрес аргумента или локальной переменной вызывающей функции, без прямой передачи ей указателя на них или без записи такого указателя в глобальную переменную и т.п.? Вроде этот пассаж разрешает. Хоть из /dev/random считать биты и слепить из них значение указателя.
Почему ж не могла? Могла. Только вот компилятор не обязан на такое не закладываться, когда код функции генерит. И правильно делает имхо, предположение о том, что в функцию приходит указатель на объект, а не хитро сконструированный мусор, достаточно разумно.
σ>>Почему, например, вызываемая функция не могла угадать адрес аргумента или локальной переменной вызывающей функции, без прямой передачи ей указателя на них или без записи такого указателя в глобальную переменную и т.п.? Вроде этот пассаж разрешает. Хоть из /dev/random считать биты и слепить из них значение указателя.
A>Почему ж не могла? Могла. Только вот компилятор не обязан на такое не закладываться, когда код функции генерит. И правильно делает имхо, предположение о том, что в функцию приходит указатель на объект, а не хитро сконструированный мусор, достаточно разумно.
Не знаю, откуда взялись функции в которые что-то "приходит".
Ладно, попробую ещё раз объяснить.
Есть код
int f() { int i = 0; g(); return i; }
Может ли компилятор соптимизировать тело до `g(); return 0;` и почему? Вроде как тот пассаж из стандарта говорит, что `g()` может угадать адрес `i` (и, следовательно, изменить значение).
Здравствуйте, σ, Вы писали:
σ>А для легальности арифметики с полученным указателем `sizeof(Count) == sizeof (int)` значения не имеет. Она нелегальна в любом случае.
Как могла бы работать функция memcpy, которая копирует структуру через нетипизированный указатель и размер в другую структуру?
Или такую функцию реализовать средствами С++ невозможно?
Здравствуйте, Шахтер, Вы писали:
Ш>Aliasing объекта и его члена легален.
Да, уже разобрался. Тут не алиасинг легален, а его просто нет. Для SLT указатели на первый член и структуру interconvertible, поэтому там object accessed получается не структура, а именно первый член и тогда dynamic type == aliased type.
σ>Не знаю, откуда взялись функции в которые что-то "приходит".
В данном случае про аргумент речь шла, так что ты не можешь получить адрес объекта, который еще не появился в стеке, и передать его в функцию. Сначала должен быть объект, а уж указатель на него ты можешь out of thin air получить. То, что ты получишь из /dev/random, указателем на объект не является, так как объекта еще нет.
σ>Ладно, попробую ещё раз объяснить. σ>Есть код σ>
σ>int f() { int i = 0; g(); return i; }
σ>
σ>Может ли компилятор соптимизировать тело до `g(); return 0;` и почему? Вроде как тот пассаж из стандарта говорит, что `g()` может угадать адрес `i` (и, следовательно, изменить значение).
Здесь обратная ситуация, объект уже есть.
Имхо может, хоть я и не бог весть какой законник. По "as_if rule". Твой int не volatile, так что прога может и не посчитать запись в него в g() "observable behavior". Здесь стандарт много на волю реализации отдает. Если возможностей судить о g() у компилятора нет, то он оставит все как есть.
A>В данном случае про аргумент речь шла, так что ты не можешь получить адрес объекта, который еще не появился в стеке, и передать его в функцию.
Перечитай ещё раз то моё сообщение. Словесное описание совпадает с кодом, который я запостил позже.
A> Сначала должен быть объект, а уж указатель на него ты можешь out of thin air получить.
Не можешь. Это был бы кошмар для оптимизаций.
σ>>Ладно, попробую ещё раз объяснить. σ>>Есть код σ>>
σ>>int f() { int i = 0; g(); return i; }
σ>>
σ>>Может ли компилятор соптимизировать тело до `g(); return 0;` и почему? Вроде как тот пассаж из стандарта говорит, что `g()` может угадать адрес `i` (и, следовательно, изменить значение).
A>Здесь обратная ситуация, объект уже есть.
Это код, который я описывал словами.
A>Имхо может, хоть я и не бог весть какой законник. По "as_if rule". Твой int не volatile, так что прога может и не посчитать запись в него в g() "observable behavior".
Какая-то странная логика. Так и `int i = 0; i = 2; return i;` можно соптимизировать до `return 0;`, а чо, запись двойки это не "observable behavior".
A> Здесь стандарт много на волю реализации отдает. Если возможностей судить о g() у компилятора нет, то он оставит все как есть.
Так ведь не оставляет, а оптимизирует
Здравствуйте, σ, Вы писали:
A>>В данном случае про аргумент речь шла, так что ты не можешь получить адрес объекта, который еще не появился в стеке, и передать его в функцию. σ>Перечитай ещё раз то моё сообщение. Словесное описание совпадает с кодом, который я запостил позже.
Понял, ты изначально говорил о ситуации, про которую потом код привел. Невнимателен, виноват, думал что разговор всё еще про то, что аргумент функции не может алиасить на локальную переменную.
A>> Сначала должен быть объект, а уж указатель на него ты можешь out of thin air получить. σ>Не можешь. Это был бы кошмар для оптимизаций.
Так вроде твоя ж была цитата как раз про то, что указатель на валидный объект может быть ЛЮБЫМ способом рассчитан Абы б он указывал действительно на валидный существующий объект.
A>>Имхо может, хоть я и не бог весть какой законник. По "as_if rule". Твой int не volatile, так что прога может и не посчитать запись в него в g() "observable behavior". σ>Какая-то странная логика. Так и `int i = 0; i = 2; return i;` можно соптимизировать до `return 0;`, а чо, запись двойки это не "observable behavior". A>> Здесь стандарт много на волю реализации отдает. Если возможностей судить о g() у компилятора нет, то он оставит все как есть. σ>Так ведь не оставляет, а оптимизирует
Говорю ж, не знаю что точно подразумевается под observable behavior. В стандарте так написано, что фактически надо сравнить выполнение оптимизированной и неоптимизированной проги на абстрактной машине, что само по себе смехотворно.
Здравствуйте, Alexander G, Вы писали:
AG>А в локальную переменную.
AG>Осталась привычка со времён, когда до изобретения атомиков думали, что int мембер тоже атомик.
AG>И продолжает помогать.
AG>
AG>struct Count
AG> {
AG> int val;
AG> void sum(PtrLen<const int> r)
AG> {
AG> int tmp_val=0;
AG> for(int v : r ) tmp_val+=v;
AG> val = tmp_val;
AG> }
AG> }
AG>
Я тоже так делаю, когда сильно озабочен производительностью.
Здравствуйте, σ, Вы писали:
AG>>Как могла бы работать функция memcpy, которая копирует структуру через нетипизированный указатель и размер в другую структуру? AG>>Или такую функцию реализовать средствами С++ невозможно? σ>Да, невозможно.
Здравствуйте, σ, Вы писали:
AG>>>>Как могла бы работать функция memcpy, которая копирует структуру через нетипизированный указатель и размер в другую структуру? AG>>>>Или такую функцию реализовать средствами С++ невозможно? σ>>>Да, невозможно. AD>>Это почему? σ>Недостаточно C++-средств для реализации memcpy.
Для private полей, вроде как таких гарантий нет. И структура с private вроде бы не POD же?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Типа, если вообще все нестатические поля private, то можно?
Прикольное правило
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском