Здравствуйте, Mystic Artifact, Вы писали:
AG>>Intel Optimization Reference Manual, September 2019 MA> Именно там эту информацию и можно найти. И в более старых мануалах тоже например.
Но там ведь написано, что signed выключают macro-fusion, а unsigned её разрешают.
Здравствуйте, Erop, Вы писали:
E>Суть в том, что в подавляющем числе мест, где нужны числа, int просто логичнее, чем беззнаковое. И интуитивно понятнее, и безопаснее и всё такое.
Ну, хз. То ли получаются те самые фломастеры, которые разные на вкус и цвет, то ли теперь надо определиться, что за "числа, которые нужны" и почему они без мнимой части, ато вдруг программист будет не только вычитание делать, но еще и корни начнет извлекать..
Здравствуйте, Erop, Вы писали:
V>>И последний: если бы int тоже был бы закольцован, то есть при переполнении просто бы отбрасывались переполненные биты, как, например, в Расте — ты все равно бы знаковым отдал предпочтение?
E>Конечно. За исключением логики оптимизатора, int и так на x86 закольцован
Аяй, погоди, че то у меня прорвало шаблон немного
На мой взгляд в конструкции "Надо нумеровать члены последовательности" + "целое число из диапазона [INT_MIN, INT_MAX]" + "кольцо модулей по вычетам какой-то неизвестной степени двойки" лишнее-то, как раз кольцо...
так все таки, речь не идет о "диапазоне против кольца"?
"целое число из закольцованного диапазона [INT_MIN, INT_MAX]" vs "целое число из закольцованного диапазона [0, UINT_MAX]"
так надо было думать если закольцованность не при чем?
Здравствуйте, Михaил, Вы писали:
М>В прошлом помнится нередко возникали траблы, когда переменная "случайно" уходила в область отрицательных значений и, будучи unsigned, превращалась в очень большое полож число, нарушая логику. Туда же и ==/!= в циклах вместо > <. С тех пор завел правило всегда юзать signed into, кроме случаев, когда нужно уместить большое число, которое не умещается в signed. В остальных случаях Unsigned не дают никакого выигрыша -Только писать больше приходится.
Вот-вот.
Во времена оные (то есть Windows 3.1) писал я одну программу. Решил, что координаты и размер окна при выходе из программы должны сохраняться в ini-файл , а при следующем запуске оттуда браться, чтобы окно оказалось на экране в том же месте. Реестр тогда не использовали для этих целей.
Прихожу однажды, запускаю программу — запускается, но окна на экране нет. Совсем нет.
Ставлю breakpoint в WM_CREATE — все в порядке, окно создается. Ставлю breakpoint в WM_DESTROY — нет, не приходит.
Появляются нехорошие мысли о самоуничтожающихся окнах без WM_DESTROY и о том, что я что-то крупно не понимаю в программировании для Win API. Мысли давлю. Но куда, черт возьми, это окно девалось ?
В общем, некоторое время я над этим размышлял, пока не догадался заглянуть в .ini файл. Оказывается, левая X — координата окна перед сохранением была отрицательной, а при записи в файл делалось преобразование к unsigned. В итоге окно по X оказалось с координатой примерно 60000 и было, видимо, показано на экране в соседней комнате
Здравствуйте, Alexander G, Вы писали:
AG>>>- fused операция при сравнении и переходе: while (i > 0) или for ( i ; i < 10 ; i++ ) CMP и JCC выполняются как одна операция только для беззнаковых.
MA>> Это не так и при чем уже довольно давно.
AG>Intel Optimization Reference Manual, September 2019
I. Я понимаю, что авторам документации Intel явно искривили руки ещё в утробе, но всё равно такие документы надо читать по тому, что в них написано, а не додумывать самому:
• Intel microarchitecture code name Nehalem supports the following enhancements in macrofusion:
— CMP can be fused with the following conditional jumps (that was not supported in Intel Core
microarchitecture):
• JL or JNGE
• JGE or JNL
• JLE or JNG
• JG or JNLE
— Macro-fusion is support in 64-bit mode.
То есть до Nehalem в 64-битке вообще было пофиг — оно не работало в принципе — но работало для проверок типа JNC (aka JAE) в 32 битах, а начиная с Nehalem — начало работать и для стандартных проверок как для знаковых. Ваши процессоры старее, чем Nehalem? Если да, то зачем?
Там ещё чуть дальше табличка для SandyBridge и потомков, не забудьте и её.
II. А с чего вы решили, что знаковые операции будут компилироваться так, как Intel рисует? Они рисуют только для своего компилятора. Вот я взял GCC5 и повторил их пример:
extern volatile int a;
void foo() {
for (int i = 0; i < 1000; i++) {
a++;
}
}
Ассемблерная выдача:
foo:
mov edx, 1000
.L2:
mov eax, DWORD PTR a[rip]
add eax, 1
mov DWORD PTR a[rip], eax
sub edx, 1
jne .L2
rep ret
Видимо, как раз из-за этих старых хохмочек они предпочитают JNE?
Я могу даже заставить её сохранять переменную цикла:
extern int* a;
void foo() {
for (int i = 0; i < 1000; i++) {
a[i]++;
}
}
Вывод: все эти химеры нужны только для ассемблерных писателей под древние процессоры (в которые входят и авторы компиляторов). Остальным уже обеспечено, что влияние знаковости устранено для основных типовых случаев.
Здравствуйте, netch80, Вы писали:
N>I. Я понимаю, что авторам документации Intel явно искривили руки ещё в утробе, но всё равно такие документы надо читать по тому, что в них написано, а не додумывать самому:
Да, нужно было дочитать, принимается.
N>То есть до Nehalem в 64-битке вообще было пофиг — оно не работало в принципе — но работало для проверок типа JNC (aka JAE) в 32 битах, а начиная с Nehalem — начало работать и для стандартных проверок как для знаковых. Ваши процессоры старее, чем Nehalem? Если да, то зачем?
N>Там ещё чуть дальше табличка для SandyBridge и потомков, не забудьте и её.
N>II. А с чего вы решили, что знаковые операции будут компилироваться так, как Intel рисует? Они рисуют только для своего компилятора. Вот я взял GCC5 и повторил их пример:
По идее компилятор должен сам оптимизировать, т.к. это Assembly/Compiler Coding Rule, не Source Coding Rule.
Но я проверил, что MSVC генерирует то, что написано.
Здравствуйте, Erop, Вы писали:
TB>>Ты на беззнаках даже тупо от ЭН до нуля проитерироваться не можешь без дополнительного бубна.
E>А как же "оператор стремления к нулю"? E>
E>size_t i = n;
E>while(i-->0) {
E> // Тут что-то делаем с n-1, n-2, ..., 1, 0
E>}
E>
С этим оператором для N-битного числа ты не сможешь сделать, чтобы первая итерация была 2**N-1 (например, в 32-битке нельзя сделать, чтобы первая итерация была для i == 0xFFFF_FFFF): тебе придётся зайти с i == 0, и while() немедленно отвергнет (а выйдя ты как раз 0xFFFF_FFFF и найдёшь в i, но будет поздно).
А иногда бывает и такая потребность, приходится учитывать. Так что нет тут полного счастья без постпроверки.
Там есть радел про macro-fusion и какие конкретно пары понимаются. И уже по моему с хасвелла парятся все тоже самое + JL/JG. Этой части раньше не было, и ее расширили. Как-то так. Сейчас с телефона, неудобно искать.
Здравствуйте, Alexander G, Вы писали:
N>>II. А с чего вы решили, что знаковые операции будут компилироваться так, как Intel рисует? Они рисуют только для своего компилятора. Вот я взял GCC5 и повторил их пример: AG>По идее компилятор должен сам оптимизировать, т.к. это Assembly/Compiler Coding Rule, не Source Coding Rule.
Угу, я об этом же. Компилятору можно говорить int или unsigned int, он всё равно внутри может пересчитать в реальные диапазоны значений и оптимизировать уже на основании этих результатов.
Кстати, Clang 6 при -O уже сразу включил векторизацию. (На -Og ещё не включил, и видно JNE в обоих случаях.)
AG>Но я проверил, что MSVC генерирует то, что написано.
Даже так... я подумал, что они таки выхлоп ICC брали. Слишком хорошо подумал, увы.
Хорошо, что у меня при выборе между GCC и Clang на ближайшие N лет таких проблем нет ;\
Здравствуйте, Mystic Artifact, Вы писали:
MA>Там есть радел про macro-fusion и какие конкретно пары понимаются. И уже по моему с хасвелла парятся все тоже самое + JL/JG. Этой части раньше не было, и ее расширили. Как-то так. Сейчас с телефона, неудобно искать.
Да, уже показали, зачеркнул устаревшее утверждение в исходном своём сообщении.
Здравствуйте, vopl, Вы писали:
V>>Причем тут size_t и почему ты считаешь, что он "более уместен"? Результат вычитания двух указателей имеет тип ptrdiff_t. Можно легко представить платформу, где первый гораздо меньше второго. V>size_t меньше ptrdiff_t? Вряд ли. Стандарт требует чтобы этот тип вмещал размер любого объекта, включая тот самый массив, разницу индексов которого вмещает ptrdiff_t. Таким образом, он гарантированно не меньше ptrdiff_t.
Пусть у нас 16-битная платформа (чтобы числа не были страшными) и адреса от 0 до 65535.
Для size_t достаточно 16 бит. Для ptrdiff_t, чтобы поддержать все значения от +65535 до -65535 — 17 бит.
Проблема общеизвестная, и её решают тем, что ни одному объекту не позволяют достигать в размере 1/2 адресного пространства (причём требование именно "меньше space/2", а не "не больше space/2"! потому что в дополнительном коде +32768 тоже не влезет). Но это значит, что для size_t достаточно на один бит меньше (15, а не 16). Ч.Т.Д.
(Реально на современных платформах общего пользования это обеспечивается тем, что половина адресного пространства занята ядром, а в остальном есть ещё код, стек и т.п.
Но на embedded запросто получить и более хитрые конфигурации, в которых проблема может вылезти в полный рост.)
V>Он "более уместен" потому что не имеет знака, который на самом деле не нужен. Из за этого, например, мой компилятор не генерирует дополнительную проверку на отрицательность в том цикле.
А с чего бы это быть той проверке? Есть масса других вариантов обойти.
Здравствуйте, Poopy Joe, Вы писали:
PJ>Здравствуйте, netch80, Вы писали:
H>>>>Во-вторых, иногда нужно не-валидное значение чего-то, что по смыслу неотрицательно. И для этого я лично использую «-1». И если переменная равна ему, то значит значение невалидное. Такая штука с unsigned не проканает, если не вводить всякие magic digits конечно. PJ>>>std::optional<uint> N>>И терять 4-8 байт вместо одного бита ;[ PJ>корретное поведение вместо креша того стоит.
И часто вам нужен размах больше половины значений соответствующего типа?
Здравствуйте, Alexander G, Вы писали:
N>>Именно в C и C++ у знаковых принципиально лучше с точки зрения производительности, что позиция, что программист всегда позаботился о переполнениях, позволяет массу оптимизаций, типа замены a+1>a на true. Были отзывы о получении 20-50% выигрыша на этом в отдельных характерно интересных случаях. AG>Ха, интересно, про неопределённость роловера для знаковых и определённость для беззнаковых уловил. AG>Интересно, как далеко можно пойти на этом неопределённом переполнении знаковых, но принял к сведению.
Ну вот пример набора оптимизаций. Реально их ещё больше.
AG>(MSVC похоже никуда не идёт, он тупо делает реальное сравнение в обоих случаях a + 1 > a)
Ну, похоже, MSVC сильно отстал. Хотя это какого года версия? Они с 2015 кричат про переход на SSA, там многое должно заметно поменяться.
N>>Что сравнивали-то? Intel рисует (почти) противоположное — для длинного умножения времена одинаковы, а для короткого (которому пофиг на знак) на 1-2 такта меньше (3 вместо до 5). AG>Обычное деление достаточно больших чисел int64_t / uint64_t, компиляция MSVC x64, беззнаковые ощутимо быстрее. Нужно воспроизвести?
Это уже речь про деление, а перед этим было про умножение
Вообще, если настроение, киньте код, я сверю в своих условиях.
AG>>>С точки зрения корректности, знаковые как бы ограждают от одного типа ошибок (отрицательное число -> переполнение), но вносят другие (отрицательное число -> выход за диапазон), шило на мыло. N>>А у положительного переполнение у обоих. А так как мелкие числа, включая отрицательные, чаще, чем крупные, то знаковые числа заведомо выигрывают. AG>В положительную сторону переполняются одинаково. Что до отрицательной, неожиданное отрицательное число, чуть-чуть вышедшее за диапазон, даёт больше вреда, чем такое же положительное, при тех же условиях вышедшее в огромные значения.
А как может положительное "выйти в огромные значения", если нет перехода через границу?
А если есть переход или хотя бы подозрение на него...
Опять же похвалю GCC и Clang — совершенно гениальная придумка (особенно если учесть вариант с тремя разными типами), такое в стандарты надо было ещё лет 20 назад вогнать. И от MS, боюсь, ещё долго не дождаться.
Здравствуйте, Vamp, Вы писали:
V>void baz(char );
V>void foo(char* begin, char* end) { V> for (int i = 0; i < (end — begin); ++i) V> baz(begin[i]);
V>Данный код предполагает, что расстояние между началом и концом уменьшается в 32 бита, и в рамках этого предположения работает отлично. Если заменить тип управляющей переменной на unsigned int, скорость кода заметно уменьшится. Связано это с тем, что компилятору придется контролировать переполнение на каждой операции (как правило, путем засовывания числа в отдельный короткий регистр), так как беззнаковые целые разрешают переполнение. Классическая рекомендация — использовать знаковое целое.
Вот этого я, кстати, не понял. Можно разъяснить?
Я прогнал оба варианта через GCC, он генерирует практически идентичный выхлоп и в 32- и в 64-битном режиме, точнее, в 32 полностью совпадают инструкции в цикле, в 64 — расширяются до 64 бит при сравнении разным образом (movsx при знаковом, чистое mov при беззнаковом).
В остальном согласен.
V>Кроме того, в С++ в математических операциях, смешивающих знаковые и беззнаковые числа, знаковые приводятся к беззнаковым, после чего операция выполняется по правилам беззнаковых.
Здравствуйте, netch80, Вы писали:
N>Здравствуйте, vopl, Вы писали:
V>>>Причем тут size_t и почему ты считаешь, что он "более уместен"? Результат вычитания двух указателей имеет тип ptrdiff_t. Можно легко представить платформу, где первый гораздо меньше второго. V>>size_t меньше ptrdiff_t? Вряд ли. Стандарт требует чтобы этот тип вмещал размер любого объекта, включая тот самый массив, разницу индексов которого вмещает ptrdiff_t. Таким образом, он гарантированно не меньше ptrdiff_t.
N>Пусть у нас 16-битная платформа (чтобы числа не были страшными) и адреса от 0 до 65535. N>Для size_t достаточно 16 бит. Для ptrdiff_t, чтобы поддержать все значения от +65535 до -65535 — 17 бит.
N>Проблема общеизвестная, и её решают тем, что ни одному объекту не позволяют достигать в размере 1/2 адресного пространства (причём требование именно "меньше space/2", а не "не больше space/2"! потому что в дополнительном коде +32768 тоже не влезет). Но это значит, что для size_t достаточно на один бит меньше (15, а не 16). Ч.Т.Д.
Да, согласен . На один бит. Но не "гораздо меньше".
N>(Реально на современных платформах общего пользования это обеспечивается тем, что половина адресного пространства занята ядром, а в остальном есть ещё код, стек и т.п. N>Но на embedded запросто получить и более хитрые конфигурации, в которых проблема может вылезти в полный рост.)
V>>Он "более уместен" потому что не имеет знака, который на самом деле не нужен. Из за этого, например, мой компилятор не генерирует дополнительную проверку на отрицательность в том цикле.
N>А с чего бы это быть той проверке? Есть масса других вариантов обойти.
Здравствуйте, vopl, Вы писали:
V>Да, согласен . На один бит. Но не "гораздо меньше".
Так в 2 раза же.
V>>>Он "более уместен" потому что не имеет знака, который на самом деле не нужен. Из за этого, например, мой компилятор не генерирует дополнительную проверку на отрицательность в том цикле.
N>>А с чего бы это быть той проверке? Есть масса других вариантов обойти.
V>Ну хз. Тут видно, один лишний test https://gcc.godbolt.org/z/HZBYFv
А, на входе... я тело цикла смотрел, там идентично. Что там на входе делается — обычно, считается, на производительность не влияет — посмотрите какой ужас компилируется при векторизации на входе и выходе основного цикла — и всё равно это считается безвредным
Но тут проблема не типа как такового — к моменту прихода оптимизаций типы уже стёрты, а диапазона значений. Проверка выбрасывается в правом случае потому, что после конверсии end-beg в беззнаковое оказывается, что в условии немедленного выхода "0 >= (unsigned size_t)(end-beg)" случай "больше" невозможен и его можно не проверять. Но это корректно только до тех пор, пока реально end >= beg! Вы косвенно, за счёт неявной конверсии к беззнаковому в этом сравнении, гарантируете это условие (и получите очень больно, если нарушите его).
В нормальном современном языке можно было бы это условие записать явно, как часть предусловий на вызов функции, и тогда 1) проверка была бы выброшена и со знаковой переменной цикла, 2) компилятор получил бы шанс проверить вызовы функции на корректность.
Здравствуйте, Erop, Вы писали:
E>2) С-шные unsigned типы -- это не целые. Это кольцо по модулю вычетов.
Это важно исключительно там, где не исключено переполнение. Если переполнение исключено, нет никакого смысла говорить о кольце.
E>Почему тебе кажется более естественным нумеровать элементы последовательности элементами такого кольца, а не ограниченными по модулю целыми числами, для меня загадка...
Возможно, потому, что последовательности традиционно нумеруются с единицы или нуля...
Здравствуйте, Erop, Вы писали:
E>Вот есть у меня подпоследовательность. Почему бессмысленно ссылаться на "элемент, предшествующий первому элементу нашей подпоседовательности"?
Можно, почему нельзя. Но это именно под последовательность, с относительным доступом. Для такого доступа совершенно естественны числа со знаком, здесь я против них ничего не имею.
N>Но тут проблема не типа как такового — к моменту прихода оптимизаций типы уже стёрты, а диапазона значений. Проверка выбрасывается в правом случае потому, что после конверсии end-beg в беззнаковое оказывается, что в условии немедленного выхода "0 >= (unsigned size_t)(end-beg)" случай "больше" невозможен и его можно не проверять. Но это корректно только до тех пор, пока реально end >= beg! Вы косвенно, за счёт неявной конверсии к беззнаковому в этом сравнении, гарантируете это условие (и получите очень больно, если нарушите его).
Не очень понимаю, про какие проверки вы все говорите: godbolt (смотреть на серенькое и желтенькое, т.е. содержимое цикла). IMHO, разница между int и unsigned в том, что unsigned — это целое по модулю 2^32 и при переполнении он должен оставаться 32-х битным, поэтому компилятор работает с unsigned в отдельном 32-х битном регистре и там появляется лишняя команда (на самом деле регистр тот же, но приходится из 32-х бит делать 64, как результат — лишняя команда). Для int переполнение — это UB, поэтому он использует сразу 64-х битный регистр, который будет вести себя как 32-х битный, если нет переполнения. Поэтому дополнительной команды там нет, ну, и в случае переполнения UB проявится в том, что будет поведение не как у 32-х битного числа.
Здравствуйте, netch80, Вы писали:
V>>Да, согласен . На один бит. Но не "гораздо меньше".
N>Так в 2 раза же.
Ок, в 2 раза, на один бит.
V>>>>Он "более уместен" потому что не имеет знака, который на самом деле не нужен. Из за этого, например, мой компилятор не генерирует дополнительную проверку на отрицательность в том цикле.
N>>>А с чего бы это быть той проверке? Есть масса других вариантов обойти.
V>>Ну хз. Тут видно, один лишний test https://gcc.godbolt.org/z/HZBYFv
N>А, на входе... я тело цикла смотрел, там идентично. Что там на входе делается — обычно, считается, на производительность не влияет — посмотрите какой ужас компилируется при векторизации на входе и выходе основного цикла — и всё равно это считается безвредным
Мелочь — не считается?
N>Но тут проблема не типа как такового — к моменту прихода оптимизаций типы уже стёрты, а диапазона значений. Проверка выбрасывается в правом случае потому, что после конверсии end-beg в беззнаковое оказывается, что в условии немедленного выхода "0 >= (unsigned size_t)(end-beg)" случай "больше" невозможен и его можно не проверять.
Именно проблема типа. Более конкретно — знакового или беззнакового типа. Еще более конкретно — проблема выбора этого типа программистом. Оптимизации непричем. Вот именно, что проверка в случае беззнаковых сразу выбрасывается так как она не имеет смысла. А в случае знаковых — нужна, и она производится, с помощью того самого лишнего теста. Могло бы не быть лишнего теста, если бы использовался "правильный" тип, но программист засунул туда знаковый. Именно по этому я считаю беззнаковый более подходящим для, цитирую ТС:
счетчики, индексы и прочие... размеры, количества и прочее...
Вот она и та самая бритва окама сыграла — если не нужна знаковость — не используй, потому что она не бесплатна, как минимум, этим одним лишним тестом. Вот оно, то самое "не хуже, а иногда чуть-лучше".
N>Но это корректно только до тех пор, пока реально end >= beg! Вы косвенно, за счёт неявной конверсии к беззнаковому в этом сравнении, гарантируете это условие (и получите очень больно, если нарушите его).
Не получу) У меня только "счетчики, индексы, размеры, количества...", у меня только такие ситуации в рассмотрении, в которых end >= beg. Сейчас я не физик или математик, мне не нужно "натуральное число". Завтра попадется задача, где будет нужно знаковое число — ну просто возьму его и буду использовать. Не то чтобы я упорот, просто немного ошарашивают рекомендации типа "всегда только знаковые типы!". Да ёлы-палы, пацаны! Никогда не говори "никогда"!