#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