Вопрос о производительности при выполнении математики на c

#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 and b и, а в другом случае работает с float для получения float значений для a and b . Следовательно, разница между двумя программами, использующими код 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;
}
 

https://godbolt.org/z/xvaarY

 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.