Сообщение Re[5]: Когда это наконец станет defined behavior? от 29.04.2023 15:42
Изменено 29.04.2023 15:56 netch80
Re[5]: Когда это наконец станет defined behavior?
Здравствуйте, T4r4sB, Вы писали:
N>> Я по умолчанию бы предпочёл видеть все операции с памятью как неявные барьеры
TB>То есть в цикле
TB>
TB>Значит каждый раз не только читать содержимое указателей src,dst, но и каждый раз читать содержимое куска памяти, где лежат значения этих указателей?
Так... я не знаю, как ответить, потому что твой вопрос настолько нелепо звучит, что или я его вообще не понял, или ты не понял, что тут происходит.
По пунктам:
1. Если src и dst это локальные переменные функции, и никто не берёт их адрес для каких-то целей (или делает это позже данного кода), то то, что я говорю, на них не распространяется. Эти переменные могут быть в стеке, в регистре, прыгать туда-обратно по настроению компилятора — неважно.
Аргументы функции в этом смысле включаются в локальные переменные — если они не приняты по ссылке (а нахрена?)
2. А вот содержимое памяти по этим указателям — да, должен читать. И ровно это, если ты имеешь в виду логику strcpy (ты ведь зачем-то такой пример взял, да?), происходит и сейчас. Компилятор обязан читать и писать память под указателями, причём даже по двум причинам: указатели одинакового типа — значит, логика алиасинга "память под указателями разных типов независима", и указатели типа char* — что убивает независимость операций (есть такое специальное правило).
3. Я что-то не понял отношения между частями твоего вопроса. Если бы это было "не только читать содержимое куска памяти... но и читать содержимое указателей", было бы понятно. А так — нет.
То, что я имел в виду изначально, это вещи следующего вида.
Самое простое: если мы видим, например,
то объединять их не положено, пока не будет разрешено, например, в стиле
Или, например, у нас есть код:
Сейчас за счёт разнотипности *a и *c считается, что присвоение любому a[i] не может влиять на значение *c, поэтому при любой оптимизации значение *c начинает кэшироваться.
Берём тот же gcc:
А теперь меняем int *c на float *c, и это разрешение компилятору уходит, потому что он уже подозревает, что присвоение a[i] может повлиять на *c:
А теперь я добавляю слово restrict (увы, только C, не C++) к float *c, и оно возвращается к однократному чтению:
Вот то что я хочу видеть по умолчанию — это такой себе anti-restrict (как в asm2) для всех операций с памятью (то есть с любым, что не является локальной переменной, у которой не брали адрес), а кроме того запрет переупорядочения доступа к ним.
В примере 2, если кто-то считает при этом, что недостаточно скорости для доступа к *c, то он может сложить в локальную переменную — или таки атрибутами выставить облегчение, ослабив тотальный алиасинг — в пределах одного куска исходного кода.
Надеюсь, теперь идея понятна?
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 | |
| |
А теперь меняем int *c на float *c, и это разрешение компилятору уходит, потому что он уже подозревает, что присвоение a[i] может повлиять на *c:
| asm2 | |
| |
А теперь я добавляю слово restrict (увы, только C, не C++) к float *c, и оно возвращается к однократному чтению:
| asm3 | |
| |
Вот то что я хочу видеть по умолчанию — это такой себе anti-restrict (как в asm2) для всех операций с памятью (то есть с любым, что не является локальной переменной, у которой не брали адрес), а кроме того запрет переупорядочения доступа к ним.
В примере 2, если кто-то считает при этом, что недостаточно скорости для доступа к *c, то он может сложить в локальную переменную — или таки атрибутами выставить облегчение, ослабив тотальный алиасинг — в пределах одного куска исходного кода.
Надеюсь, теперь идея понятна?
Re[5]: Когда это наконец станет defined behavior?
Здравствуйте, T4r4sB, Вы писали:
N>> Я по умолчанию бы предпочёл видеть все операции с памятью как неявные барьеры
TB>То есть в цикле
TB>
TB>Значит каждый раз не только читать содержимое указателей src,dst, но и каждый раз читать содержимое куска памяти, где лежат значения этих указателей?
Так... я не знаю, как ответить, потому что твой вопрос настолько нелепо звучит, что или я его вообще не понял, или ты не понял, что тут происходит.
По пунктам:
1. Если src и dst это локальные переменные функции, и никто не берёт их адрес для каких-то целей (или делает это позже данного кода), то то, что я говорю, на них не распространяется. Эти переменные могут быть в стеке, в регистре, прыгать туда-обратно по настроению компилятора — неважно.
Аргументы функции в этом смысле включаются в локальные переменные — если они не приняты по ссылке (а нахрена?)
2. А вот содержимое памяти по этим указателям — да, должен читать. И ровно это, если ты имеешь в виду логику strcpy (ты ведь зачем-то такой пример взял, да?), происходит и сейчас. Компилятор обязан читать и писать память под указателями, причём даже по двум причинам: указатели одинакового типа — значит, логика алиасинга "память под указателями разных типов независима", и указатели типа char* — что убивает независимость операций (есть такое специальное правило).
3. Я что-то не понял отношения между частями твоего вопроса. Если бы это было "не только читать содержимое куска памяти... но и читать содержимое указателей", было бы понятно. А так — нет.
То, что я имел в виду изначально, это вещи следующего вида.
Самое простое: если мы видим, например,
то объединять их не положено, пока не будет разрешено, например, в стиле
Или, например, у нас есть код:
Сейчас за счёт разнотипности *a и *c считается, что присвоение любому a[i] не может влиять на значение *c, поэтому при любой оптимизации значение *c начинает кэшироваться.
Берём тот же gcc:
А теперь меняем int *c на float *c, и это разрешение компилятору уходит, потому что он уже подозревает, что присвоение a[i] может повлиять на *c:
А теперь я добавляю слово restrict (увы, только C, не C++) к float *c, и оно возвращается к однократному чтению:
Вот то что я хочу видеть по умолчанию — это такой себе anti-restrict (как в asm2) для всех операций с памятью (то есть с любым, что не является локальной переменной, у которой не брали адрес), а кроме того запрет переупорядочения доступа к ним.
В примере 2, если кто-то считает при этом, что недостаточно скорости для доступа к *c, то он может сложить в локальную переменную — или таки атрибутами выставить облегчение, ослабив тотальный алиасинг — в пределах одного куска исходного кода.
Надеюсь, теперь идея понятна?
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 | |
| |
А теперь меняем int *c на float *c, и это разрешение компилятору уходит, потому что он уже подозревает, что присвоение a[i] может повлиять на *c:
| asm2 | |
| |
А теперь я добавляю слово restrict (увы, только C, не C++) к float *c, и оно возвращается к однократному чтению:
| asm3 | |
| |
Вот то что я хочу видеть по умолчанию — это такой себе anti-restrict (как в asm2) для всех операций с памятью (то есть с любым, что не является локальной переменной, у которой не брали адрес), а кроме того запрет переупорядочения доступа к ним.
В примере 2, если кто-то считает при этом, что недостаточно скорости для доступа к *c, то он может сложить в локальную переменную — или таки атрибутами выставить облегчение, ослабив тотальный алиасинг — в пределах одного куска исходного кода.
Надеюсь, теперь идея понятна?