Re[9]: Когда это наконец станет defined behavior?
От: σ  
Дата: 28.04.23 14:42
Оценка:
TB>>> Оператор + левоассоциативный

σ>>При чём тут это?


TB>При том что второе сложение обязано выполниться после первого


Что в стандарте это подтверждает?

TB>>> второе сложение в любом случае выполнится после первого.


σ>>А чтения из a — в любом порядке. Оба до вызова bar, оба после, или с вызовом между.


TB>И что же гцц даже с о3 зассал один раз прочитать а и удвоить в регистре?


ХЗ. Missing optimization? MSVC вроде не ссыт https://godbolt.org/z/v8TzGK13K

TB>Ты не видишь что от порядка чтений меняется результат?


И?
Re[10]: Когда это наконец станет defined behavior?
От: T4r4sB Россия  
Дата: 28.04.23 15:04
Оценка:
Здравствуйте, σ, Вы писали:

σ>Что в стандарте это подтверждает?


Выражение в теле функции однозначно парсится как

Operator+(operator+(a, bar()), a)

σ>ХЗ. Missing optimization? MSVC вроде не ссыт https://godbolt.org/z/v8TzGK13K


Ок, плохой пример, bar()+a неоднозначно.

int foo(const int& a) {
  int ll = a;
  int l = ll + bar();
  return l + a;
}



TB>>Ты не видишь что от порядка чтений меняется результат?


σ>И?


Ну уб получается.
Отредактировано 28.04.2023 15:10 T4r4sB . Предыдущая версия .
Re[11]: Когда это наконец станет defined behavior?
От: σ  
Дата: 28.04.23 15:18
Оценка:
σ>>Что в стандарте это подтверждает?

TB>Выражение в теле функции однозначно парсится как


TB>Operator+(a,operator+(bar(), a))


Допустим. Это как-то противоречит
> чтения из a — в любом порядке. Оба до вызова bar, оба после, или с вызовом между.
?

σ>>ХЗ. Missing optimization? MSVC вроде не ссыт https://godbolt.org/z/v8TzGK13K


TB>Ок, плохой пример, bar()+a неоднозначно.


TB>
TB>int foo(const int& a) {
TB>  int ll = a;
TB>  int l = ll + bar();
TB>  return l + a;
TB>}
TB>


TB>Хотя ret 0 это жесть


А сколько должно быть

TB>>>Ты не видишь что от порядка чтений меняется результат?


σ>>И?


TB>Ну уб получается.


Почему?
Re[12]: Когда это наконец станет defined behavior?
От: T4r4sB Россия  
Дата: 28.04.23 15:30
Оценка:
Здравствуйте, σ, Вы писали:

Да, согласен, может сначала прочитать второй аргумент а потом вычислить первый.
Поэтому я исправил пример:


TB>>
TB>>int foo(const int& a) {
TB>>  int ll = a;
TB>>  int l = ll + bar();
TB>>  return l + a;
TB>>}
TB>>


Гцц по прежнему два чтения из памяти.

TB>>Хотя ret 0 это жесть


σ>А сколько должно быть :???


С этой инструкцией, я просто не сразу вспомнил что означает число в команде ret.



σ>Почему?


Потому что от желания оптимизатора может поменяться результат функции
Re[13]: Когда это наконец станет defined behavior?
От: σ  
Дата: 28.04.23 15:47
Оценка:
TB>Гцц по прежнему два чтения из памяти.

¯\_(ツ)_/¯ Может, посчитали, что прочитать 2 раза подряд — недорого

σ>>Почему?


TB>Потому что от желания оптимизатора может поменяться результат функции


Как-то мало для UB
Re[14]: Когда это наконец станет defined behavior?
От: T4r4sB Россия  
Дата: 28.04.23 15:48
Оценка:
Здравствуйте, σ, Вы писали:

TB>>Потому что от желания оптимизатора может поменяться результат функции


σ>Как-то мало для UB


Лол это и называется уб

σ>¯\_(ツ)_/¯ Может, посчитали, что прочитать 2 раза подряд — недорого


А почему в таком коде решили, что прочитать 2 раза подряд — дорого?
int foo(int* a, float* b) {
  *a = 8;
  *b = 42.0;
  return *a + 9;
}

https://godbolt.org/z/xq7nPEMz7
Отредактировано 28.04.2023 17:11 T4r4sB . Предыдущая версия .
Re[15]: Когда это наконец станет defined behavior?
От: rg45 СССР  
Дата: 28.04.23 20:04
Оценка:
Здравствуйте, T4r4sB, Вы писали:


TB>>>Потому что от желания оптимизатора может поменяться результат функции


σ>>Как-то мало для UB


TB>Лол это и называется уб


Я думаю, тебе не помешало бы разобраться с такими понятиями как undefined behavior, unspecified behavior и implementation defined behavior. По иронии судьбы первые два пункта в этом списке имеют одинаковую аббревиатуру (UB), но при этом очень разный смысл. Можно почитать здесь: https://en.cppreference.com/w/cpp/language/ub.

Вдобавок в параграфе с описанием unspecified behavior есть еще очень полезная ссылка — Order of evaluation, имеющая непосредственное отношение к обсуждаемому вопросу:

There is no concept of left-to-right or right-to-left evaluation in C++. This is not to be confused with left-to-right and right-to-left associativity of operators: the expression a() + b() + c() is parsed as (a() + b()) + c() due to left-to-right associativity of operator+, but c() may be evaluated first, last, or between a() or b() at run time


То есть, порядок выполнения операций (ассоциативность) и порядок вычисления операндов (подвыражений) — это две разные и независимые вещи. Порядок вычисления операндов регулируется отношениями "sequenced before/after", но он определен далеко не для всех случаев. Во многих случаях этот порядок отдан на откуп разработчикам компиляторов и относится к UNSPECIFIED behavior. И это совсем не то же самое, что UNDEFINED behavior. И аббревиатура UB, сколько мне попадалось, всегда употребляется только в смысле undefined behavior.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 28.04.2023 20:29 rg45 . Предыдущая версия . Еще …
Отредактировано 28.04.2023 20:28 rg45 . Предыдущая версия .
Отредактировано 28.04.2023 20:23 rg45 . Предыдущая версия .
Отредактировано 28.04.2023 20:22 rg45 . Предыдущая версия .
Отредактировано 28.04.2023 20:20 rg45 . Предыдущая версия .
Отредактировано 28.04.2023 20:16 rg45 . Предыдущая версия .
Отредактировано 28.04.2023 20:09 rg45 . Предыдущая версия .
Re[16]: Когда это наконец станет defined behavior?
От: T4r4sB Россия  
Дата: 28.04.23 20:29
Оценка: -1
Здравствуйте, rg45, Вы писали:

TB>>Лол это и называется уб


R>Я думаю, тебе не помешало бы разобраться с такими понятиями как undefined behavior, unspecified behavior и implementation defined behavior.


То, от чего меняется логика при оптимизации — это как раз undefined behavior.
Что и написано по твоей ссылке, целый раздел есть "UB and optimization".

R>То есть, порядок выполнения операций (ассоциативность) и порядок вычисления операндов (подвыражений) — это две разные и независимые вещи.


Да, мой изначальный пример был плох, потому что зависел от порядка вычисления операндов.
Я уже исправил.
int bar();

int foo(const int& a) {
  int ll = a;
  int l = ll + bar();
  return l + a;
}

Так вот, почему гцц ссыт убрать лишнее чтение из памяти?
Отредактировано 28.04.2023 20:32 T4r4sB . Предыдущая версия .
Re[17]: Когда это наконец станет defined behavior?
От: rg45 СССР  
Дата: 28.04.23 20:48
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>То, от чего меняется логика при оптимизации — это как раз undefined behavior.

TB>Что и написано по твоей ссылке, целый раздел есть "UB and optimization".

Я не очень понимаю, для чего тебе понадобилось пeрефразировать собственные высказывания, но перед этим ты утверждал буквально следующее:

TB>Потому что от желания оптимизатора может поменяться результат функции
TB>Лол это и называется уб


Так вот это не верно. Может поменяться результат вызова функции и это не обязательно будет UB (undefined behavior).

Пример:

int a = 42;
int b = foo(a, a += 1);

Результат может быть разным. Но это проявление unspecified behavior, а не undefined.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 28.04.2023 21:42 rg45 . Предыдущая версия . Еще …
Отредактировано 28.04.2023 20:51 rg45 . Предыдущая версия .
Re[17]: Когда это наконец станет defined behavior?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 29.04.23 08:37
Оценка:
Здравствуйте, T4r4sB, Вы писали:

R>>Я думаю, тебе не помешало бы разобраться с такими понятиями как undefined behavior, unspecified behavior и implementation defined behavior.


TB>То, от чего меняется логика при оптимизации — это как раз undefined behavior.


Таки нет. Undefined — это когда от твоего нарушения может взорваться вообще всё и не только в этом месте. Unspecified — это когда что-то произойдёт на выбор из описанных вариантов, но что именно — на этапе написания зафиксировать нельзя; причём в стандарте все такие случаи имеют локальный эффект (например, порядок вычисления аргументов функции или операндов в a+b — это не повлияет уже на соседний оператор).
И вот такие вещи, как порядок вычисления аргументов функции, от оптимизаций могут меняться весьма значительно.

R>>То есть, порядок выполнения операций (ассоциативность) и порядок вычисления операндов (подвыражений) — это две разные и независимые вещи.


TB>Так вот, почему гцц ссыт убрать лишнее чтение из памяти?


Тут и банально, и нет.

С одной стороны, он не знает, что делает этот bar(), может ли он поменять ту переменную, которая тебе передана по ссылке как a.
Запиши вместо декларации bar(), например: int bar() { return 1; } и увидишь, что он соптимизировал оба чтения в одно: (gcc 9.4.0, -O2, убрал незначащее)

Z3fooRKi:
        movl    (%rdi), %eax
        leal    1(%rax,%rax), %eax
        ret

Видно, что bar() заинлайнилась, а чтение переменной "a" одно на оба случая.

А вот теперь фокус — подставляем барьер памяти в bar():

int bar() {                                                                    
  std::atomic_signal_fence(std::memory_order_acquire);                         
  return 1;                                                                    
}


И код получился вообще потрясающий:

_Z3fooRKi:
        movl    (%rdi), %edx
        movl    (%rdi), %eax
        leal    1(%rdx,%rax), %eax
        ret


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

Вывод — срабатывал принцип "мы не знаем, что делает bar(), значит, он мог поменять память как угодно".

А вот что меня таки смущает — почему это происходит при том, что в аргументе было "const int& a", а не "int& a". Clang — точно так же. Это значит, что константность для них действует одинаково как "мы не имеем права это менять", но не действует как "это не может поменять кто-то снаружи", несмотря на то, что формально это const.

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

И, естественно, всё это ещё в контексте предположений о том, что компилятор не может переставить порядок выполнения сложений в этой последовательности. Вот этот момент меня тоже смущает, но, может, его тут уже разрешили (надо перечитать, но рассказ про левоассоциативность сложения может решать этот аспект).
The God is real, unless declared integer.
Отредактировано 29.04.2023 8:51 netch80 . Предыдущая версия .
Re[3]: Когда это наконец станет defined behavior?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 29.04.23 08:57
Оценка: +1
Здравствуйте, kov_serg, Вы писали:

IM>>>Однако я не нашёл в стандарте C++20 упоминания, что reinterpret_cast может начать lifetime объекта. Хотя malloc и memcpy теперь легальны с этой точки зрения, т.е. этот пропозал был включён в стандарт, но обошли reinterpret_cast.


Z>>Вроде для этого предложили новую функцию std::start_lifetime_as ,


_>Надо больше обрядов. Что это за религия если мало странных обрядов


Ну почему обряд... в этом есть своя логика, если думать о том, как реализуется контроль алиасинга (на всех уровнях).
Обрядом здесь скорее является желание активизировать его в любом случае, а кто не спрятался — сам виноват. Я по умолчанию бы предпочёл видеть все операции с памятью как неявные барьеры, а кому от этого медленно — вот там уже помечать нужные куски кода всеми подобными средствами.

_>ps: пара уже добавлять std::bless<function_name>


Это в другой язык, всё уже есть.
The God is real, unless declared integer.
Re[18]: Когда это наконец станет defined behavior?
От: T4r4sB Россия  
Дата: 29.04.23 09:45
Оценка:
Здравствуйте, netch80, Вы писали:

N>Таки нет. Undefined — это когда от твоего нарушения может взорваться вообще всё и не только в этом месте.


Я говорю о том, как оно работает на этапе компиляции в реальности.

N>А вот что меня таки смущает — почему это происходит при том, что в аргументе было "const int& a", а не "int& a".


Ага, и я о том же.
Вот тут я даже не знаю, чагойта стандартизаторы испугалися
Re[18]: Когда это наконец станет defined behavior?
От: rg45 СССР  
Дата: 29.04.23 10:04
Оценка: +1
Здравствуйте, netch80, Вы писали:

TB>
TB>int bar();

TB>int foo(const int& a) {
TB>  int ll = a;
TB>  int l = ll + bar();
TB>  return l + a;
TB>}
TB>


N>А вот что меня таки смущает — почему это происходит при том, что в аргументе было "const int& a", а не "int& a". Clang — точно так же. Это значит, что константность для них действует одинаково как "мы не имеем права это менять", но не действует как "это не может поменять кто-то снаружи", несмотря на то, что формально это const.


Так ясно почему — константность ссылки не гарантирует константности объекта. Один и тот же объект можно привязать одновременно к множеству разных ссылок, как константных, так и неконстантных. А функция bar может может иметь непостредственный доступ к объекту, помимо той ссылки, которая передается в функцию foo:

http://coliru.stacked-crooked.com/a/5197fe8b39464268

////////////////////////////////////////////////////////
// main.cpp

#include <iostream>

int bar();

int foo(const int& a) {
  int ll = a;
  int l = ll + bar();
  return l + a;
}

int main()
{
   extern int x;

   std::cout << x << std::endl;        // -> 42
   std::cout << foo(x) << std::endl;   // -> 128
   std::cout << x << std::endl;        // -> 43
}

////////////////////////////////////////////////////////
// bar.cpp

int x = 42;

int bar() {
   return ++x;
}
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 29.04.2023 10:16 rg45 . Предыдущая версия . Еще …
Отредактировано 29.04.2023 10:13 rg45 . Предыдущая версия .
Re[19]: Когда это наконец станет defined behavior?
От: T4r4sB Россия  
Дата: 29.04.23 10:27
Оценка:
Здравствуйте, rg45, Вы писали:

R>А функция bar может может иметь непостредственный доступ к объекту, помимо той ссылки, которая передается в функцию foo:


Надо стандартизаторам идею подкинуть: а почему б такую ситуацию тоже не назвать UB? Типа передали константную ссылку — значит компилятор вправе предполагать что содержимое не меняется! Это ж сколько можно долей процента выиграть в реальных приложениях! А сколько будет новых непонятных падений!
Re[4]: Когда это наконец станет defined behavior?
От: T4r4sB Россия  
Дата: 29.04.23 10:32
Оценка:
Здравствуйте, netch80, Вы писали:

N> Я по умолчанию бы предпочёл видеть все операции с памятью как неявные барьеры


То есть в цикле
while (*dst++ = *src++);

Значит каждый раз не только читать содержимое указателей src,dst, но и каждый раз читать содержимое куска памяти, где лежат значения этих указателей?
Re[20]: Когда это наконец станет defined behavior?
От: rg45 СССР  
Дата: 29.04.23 10:34
Оценка: +1
Здравствуйте, T4r4sB, Вы писали:

TB>Надо стандартизаторам идею подкинуть: а почему б такую ситуацию тоже не назвать UB? Типа передали константную ссылку — значит компилятор вправе предполагать что содержимое не меняется! Это ж сколько можно долей процента выиграть в реальных приложениях! А сколько будет новых непонятных падений!


Другими словами, ты предлагаешь запретить привязывать константные ссылки к неконстантным объектам? Сомнительная идея как по мне. Передали константную ссылку — значит, тот участок логики, которому передали ссылку, может только читать данные, но не имеет права менять их. Например, это процедура вывода данных на экран или в файл — ее дело только вывод данных. Но это ж не значит, что эти данные не может менять вообще нигде и никто. Таким образом ссылки выступают как средство разграничения прав доступа — кто-то может и читать, и писать, а кто-то только читать. И это очень даже полезная возможность.
--
Не можешь достичь желаемого — пожелай достигнутого.
Отредактировано 29.04.2023 10:59 rg45 . Предыдущая версия . Еще …
Отредактировано 29.04.2023 10:42 rg45 . Предыдущая версия .
Отредактировано 29.04.2023 10:41 rg45 . Предыдущая версия .
Отредактировано 29.04.2023 10:40 rg45 . Предыдущая версия .
Re[17]: Когда это наконец станет defined behavior?
От: rg45 СССР  
Дата: 29.04.23 11:29
Оценка:
Здравствуйте, T4r4sB, Вы писали:

TB>
TB>int bar();

TB>int foo(const int& a) {
TB>  int ll = a;
TB>  int l = ll + bar();
TB>  return l + a;
TB>}
TB>

TB>Так вот, почему гцц ссыт убрать лишнее чтение из памяти?

Кстати, неплохой вопрос к собеседованиям.
--
Не можешь достичь желаемого — пожелай достигнутого.
Re[20]: Когда это наконец станет defined behavior?
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 29.04.23 13:15
Оценка: +2
Здравствуйте, T4r4sB, Вы писали:

TB>Типа передали константную ссылку — значит компилятор вправе предполагать что содержимое не меняется!


Если объект создается константным — вправе, и вполне себе предполагает (например, если объект статический, и есть возможность разместить его в RO-секции). А если откуда-то просто приходит ссылка на уже существующий объект, то весь смысл const заключается лишь в запрете его изменения через данную ссылку.
Re[5]: Когда это наконец станет defined behavior?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 29.04.23 15:42
Оценка:
Здравствуйте, T4r4sB, Вы писали:

N>> Я по умолчанию бы предпочёл видеть все операции с памятью как неявные барьеры


TB>То есть в цикле

TB>
TB>while (*dst++ = *src++);
TB>

TB>Значит каждый раз не только читать содержимое указателей src,dst, но и каждый раз читать содержимое куска памяти, где лежат значения этих указателей?

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

По пунктам:

1. Если src и dst это локальные переменные функции, и никто не берёт их адрес для каких-то целей (или делает это позже данного кода), то то, что я говорю, на них не распространяется. Эти переменные могут быть в стеке, в регистре, прыгать туда-обратно по настроению компилятора — неважно.
Аргументы функции в этом смысле включаются в локальные переменные — если они не приняты по ссылке (а нахрена?)

2. А вот содержимое памяти по этим указателям — да, должен читать. И ровно это, если ты имеешь в виду логику strcpy (ты ведь зачем-то такой пример взял, да?), происходит и сейчас. Компилятор обязан читать и писать память под указателями, причём даже по двум причинам: указатели одинакового типа — значит, логика алиасинга "память под указателями разных типов независима", и указатели типа char* — что убивает независимость операций (есть такое специальное правило).

3. Я что-то не понял отношения между частями твоего вопроса. Если бы это было "не только читать содержимое куска памяти... но и читать содержимое указателей", было бы понятно. А так — нет.

То, что я имел в виду изначально, это вещи следующего вида.
Самое простое: если мы видим, например,

int a, b, c; // глобальные
void foo() {
  b = a;
  c = a;
}


то объединять их не положено, пока не будет разрешено, например, в стиле

[[aliasing(relaxed)]]
void foo() {
  b = a;
  c = a;
}


Или, например, у нас есть код:

void boo(float *a, int n, int *c) {
  for (int i = 0; i < n; ++i) { a[i] *= *c; }
}


Сейчас за счёт разнотипности *a и *c считается, что присвоение любому a[i] не может влиять на значение *c, поэтому при любой оптимизации значение *c начинает кэшироваться.
Берём тот же gcc:

  asm1
boo:
.LFB0:
        testl   %esi, %esi
        jle     .L1
        pxor    %xmm1, %xmm1
        leal    -1(%rsi), %eax
        cvtsi2ssl       (%rdx), %xmm1 <-- Вот тут один раз прочитал и запомнил
        leaq    4(%rdi,%rax,4), %rax
.L3:
        movss   (%rdi), %xmm0
        addq    $4, %rdi
        mulss   %xmm1, %xmm0
        movss   %xmm0, -4(%rdi)
        cmpq    %rax, %rdi
        jne     .L3
.L1:
        ret


А теперь меняем int *c на float *c, и это разрешение компилятору уходит, потому что он уже подозревает, что присвоение a[i] может повлиять на *c:

  asm2
boo:
        testl   %esi, %esi
        jle     .L1
        movq    %rdi, %rax
        leal    -1(%rsi), %ecx
        leaq    4(%rdi,%rcx,4), %rcx
.L3:
        movss   (%rax), %xmm0
        mulss   (%rdx), %xmm0 <-- Читает на каждой итерации
        movss   %xmm0, (%rax)
        addq    $4, %rax
        cmpq    %rcx, %rax
        jne     .L3
.L1:
        ret


А теперь я добавляю слово restrict (увы, только C, не C++) к float *c, и оно возвращается к однократному чтению:

  asm3
boo:
        testl   %esi, %esi
        jle     .L1
        movss   (%rdx), %xmm1 <-- Прочли один раз и запомнили
        movq    %rdi, %rax
        leal    -1(%rsi), %edx
        leaq    4(%rdi,%rdx,4), %rdx
.L3:
        movaps  %xmm1, %xmm0
        mulss   (%rax), %xmm0
        movss   %xmm0, (%rax)
        addq    $4, %rax
        cmpq    %rdx, %rax
        jne     .L3
.L1:
        ret


Вот то что я хочу видеть по умолчанию — это такой себе anti-restrict (как в asm2) для всех операций с памятью (то есть с любым, что не является локальной переменной, у которой не брали адрес), а кроме того запрет переупорядочения доступа к ним.
В примере 2, если кто-то считает при этом, что недостаточно скорости для доступа к *c, то он может сложить в локальную переменную — или таки атрибутами выставить облегчение, ослабив тотальный алиасинг — в пределах одного куска исходного кода.

Надеюсь, теперь идея понятна?
The God is real, unless declared integer.
Отредактировано 29.04.2023 15:56 netch80 . Предыдущая версия .
Re[19]: Когда это наконец станет defined behavior?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 29.04.23 15:44
Оценка:
Здравствуйте, T4r4sB, Вы писали:

N>>Таки нет. Undefined — это когда от твоего нарушения может взорваться вообще всё и не только в этом месте.


TB>Я говорю о том, как оно работает на этапе компиляции в реальности.


И я о том же. В чём ты видишь разницу?
The God is real, unless declared integer.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.