#c #performance #math #optimization
#c #Производительность #математика #оптимизация
Вопрос:
Код X:
float resu<
int a, b;
result = (float)a (float)b;
Код Y:
float result, a, b;
result = a b;
Какой код самый быстрый и использует меньше ресурсов?
Комментарии:
1. Разница достаточно мала, поэтому вы можете ее игнорировать.
2. Эти два кода выполняют разные действия. Оба имеют неопределенное поведение, хотя переменные не инициализированы.
3. Этот вопрос бессмыслен без контекста. В любом случае
a
илиb
должны были быть заданы некоторые значения ранее. Итак, существует предыдущий код, который в одном случае работает сint
для полученияint
значений дляa
andb
и, а в другом случае работает сfloat
для полученияfloat
значений дляa
andb
. Следовательно, разница между двумя программами, использующими код X или код Y, заключается не только в коде X или коде Y, и поэтому разница во времени их выполнения обусловлена не только кодом X или кодом Y. Ответ, который быстрее, также должен зависеть от других частей программы.4. @EugeneSh.: Показ объявлений перед другим кодом является общим соглашением для отображения типов переменных и не означает, что код в программе буквально содержит только те объявления, за которыми сразу следует другой показанный код. Вы уже должны это знать.
Ответ №1:
Предполагая, что переполнений нет, Y будет немного быстрее, потому что есть накладные расходы на преобразование an int
в a float
.
Однако разница будет очень небольшой, и, если производительность не критична, хорошей практикой является представление a
и b
наиболее «естественным» типом данных, а не наиболее производительным. То есть, если у них нет дробной части (по своей природе) float
, это непригодно, даже если некоторые вычисления имеют float
результаты.
Также обратите внимание, что диапазон float
, который может представлять точные целые числа, вероятно, уже, чем int
. На типичной платформе where int
является 32-разрядным, а float
также 32-разрядным, но использует некоторые биты для кодирования показателя степени.
На некоторых платформах double
может быть еще быстрее, потому float
что внутренне преобразуется в double
и обратно. Обычно рекомендуется, если пространство не является критическим (например, многие миллионы значений в размещенных средах или крошечное встроенное устройство) double
.
Ответ №2:
Вы используете неинициализированные переменные, и две части кода не совсем одинаковы из-за потери точности, но я проигнорирую это, давайте поговорим концептуально.
Я предполагаю, что в обоих случаях ‘a’ и ‘b’ являются целочисленными значениями, независимо от их типа.
Во-первых, когда речь идет об операциях с плавающей запятой, это всегда зависит от архитектуры. Не любой процессор поддерживает операции с плавающей запятой таким же образом.
Однако хорошо, давайте предположим, что вы говорите о x86_64.
Далее идет оптимизация. Обратите внимание, что (float)a (float)b
это не то же самое, что (float)(a b)
, потому что преобразование с плавающей запятой может потерять точность (хотя для int, возможно, и нет, для long определенно вы можете потерять точность). Таким образом, это не может быть оптимизировано или не может быть оптимизировано, по крайней мере, в некоторых случаях.
Таким образом, оптимизатор, вероятно, не будет иметь большого значения между двумя случаями.
Теперь также обратите внимание, что преобразование из int в float и обратно является отдельной инструкцией по сборке (в отличие, например, от int в long), поскольку целые и плавающие числа имеют разный формат в памяти, это cvtsi2ss
инструкция.
Вывод: в первом случае выполняется тот же объем вычислений, но в нем также есть преобразования. Так что это немного медленнее.
Смотрите Разборку здесь, которая иллюстрирует разницу:
Случай 1: https://godbolt.org/z/fsMjoE
Случай 2: https://godbolt.org/z/v1xzr1
Комментарии:
1.
Pay attention that (float)a (float)b is not the same as (float)(a b), because floating point conversion may lose precision
проблема где-то в другом месте.
Ответ №3:
Эти два примера совершенно разные, и сравнивать их имеет очень мало смысла. Почему?
Первый пример добавляет два целых числа, преобразованных в числа с плавающей запятой. Компилятор должен преобразовать оба значения в float, а затем добавить их.
Второй пример просто добавляет два числа с плавающей запятой без какого-либо преобразования.
Я считаю, что вы хотите сравнить
float foo(int a, int b) {
return (float)a (float)b;
}
float bar(int a, int b) {
return a b;
}
The resulting code
pxor xmm0, xmm0
pxor xmm1, xmm1
cvtsi2ss xmm0, edi
cvtsi2ss xmm1, esi
addss xmm0, xmm1
ret
bar:
add edi, esi
pxor xmm0, xmm0
cvtsi2ss xmm0, edi
ret
Функция bar более эффективна, но она может не дать того же результата, если сложение двух целых чисел переполняется (они добавляются первыми, затем результат преобразуется в число с плавающей запятой), что вызывает UB.
Ответ №4:
Этот код на языке Си:
float test(int a, int b)
{
float result = (float)a (float)b;
return resu<
}
float test1(float a, float b)
{
float result = a b;
return resu<
}
Может быть скомпилировано во что-то подобное с помощью оптимизирующего компилятора:
test:
cvtsi2ss xmm1, edi
cvtsi2ss xmm0, esi
addss xmm0, xmm1
ret
test1:
addss xmm0, xmm1
ret
Итак, вы можете видеть, что test1
это меньше, чем test
, что нормально, потому что обе функции не выполняют одно и то же. Во test1
всем float
так, что никаких преобразований делать не нужно. Перед test
int
добавлением s необходимо преобразовать в float.