Почему деление с плавающей запятой быстрее, чем целочисленное деление в c ?

#performance #c 11 #floating-point #int #division

#Производительность #c 11 #с плавающей запятой #int #деление

Вопрос:

Рассмотрим следующий фрагмент кода на C : (visual studio 2015)

Первый блок

 const int size = 500000000;
int sum =0;
int *num1 = new int[size];//initialized between 1-250
int *num2 = new int[size];//initialized between 1-250
for (int i = 0; i < size; i  )
{
    sum  =(num1[i] / num2[i]);
}
  

Второй блок

 const int size = 500000000;
int sum =0;
float *num1 = new float [size]; //initialized between 1-250 
float *num2 = new float [size]; //initialized between 1-250
for (int i = 0; i < size; i  )
{
    sum  =(num1[i] / num2[i]);
}
  

Я ожидал, что первый блок выполняется быстрее, потому что это целочисленная операция. Но второй блок значительно быстрее , хотя это операция с плавающей запятой . вот результаты моего контрольного показателя :
Деление:

 Type    Time
uint8   879.5ms
uint16  885.284ms
int     982.195ms
float   654.654ms
  

А также умножение с плавающей запятой быстрее, чем целочисленное умножение.
вот результаты моего контрольного показателя :

Умножение:

 Type    Time
uint8   166.339ms
uint16  524.045ms
int     432.041ms
float   402.109ms
  

Спецификация моей системы: процессор core i7-7700, оперативная память 64 ГБ, Visual Studio 2015

Комментарии:

1. Скорость деления зависит от значений. Вам нужно инициализировать свои значения чем-то (например rand() ), чтобы тест был значимым.

2. Разве код не выполняет много делений на 0? (потенциальный запуск передачи исключений с помощью математики целых чисел) Попробуйте использовать ненулевые делители.

3. @chux Целочисленное деление на 0 завершает работу приложения.

4. @Maxim , chux : спасибо за ответ. как я уже сказал в своем вопросе, это фрагмент кода, а не целая программа. в моем основном коде я инициализировал свои переменные значениями от 1 до 250.

5. В этом вопросе о производительности в C отсутствует информация о компиляторе и используемых флагах компиляции. Как таковой, это плохой вопрос.

Ответ №1:

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

int32_t для деления требуется быстрое деление 31-разрядных чисел, тогда как float для деления требуется быстрое деление 24-разрядных мантисс (ведущая в мантиссе подразумевается и не хранится в виде числа с плавающей запятой) и более быстрое вычитание 8-разрядных показателей.

Смотрите отличное подробное объяснение того, как выполняется деление в CPU.

Возможно, стоит упомянуть, что инструкции SSE и AVX обеспечивают только деление с плавающей запятой, но не целочисленное деление. Инструкции SSE / intrinsincs могут быть использованы для увеличения скорости ваших float вычислений в четыре раза.

Если вы посмотрите на таблицы инструкций Агнера Фога, например, для Skylake, задержка 32-разрядного целочисленного деления составляет 26 циклов процессора, тогда как задержка скалярного деления с плавающей запятой SSE составляет 11 циклов процессора (и, что удивительно, для деления четырех упакованных чисел с плавающей запятой требуется столько же времени).

Также обратите внимание, что в C и C нет деления на числа короче, чем int , так что uint8_t и uint16_t сначала повышаются до int , а затем происходит деление int s . uint8_t деление выглядит быстрее, чем int потому что при преобразовании в int установлено меньше битов, что приводит к более быстрому завершению деления.

Комментарии:

1. Хотя ниже int нет деления, в разделе «как если бы» компиляторы могут свободно делить, используя деление на под-целое число, если результаты одинаковы.

2. @Yakk-AdamNevraumont Это возможно. У вас есть пример, который это демонстрирует? Потому что я не видел, чтобы это происходило на практике, см., Например godbolt.org/z/ujcxvT