Двойная производительность намного быстрее, чем с плавающей запятой в C

Я пытался выяснить, будет ли использование чисел с плавающей запятой в каком-то коде на C достаточно точным для моих нужд, но после поиска и не совсем понимания того, как биты точности преобразуются в реальные числа, я решил просто написать немного кода для своего теста. случае и посмотреть, каковы были результаты.

Поплавки кажутся достаточно точными, но меня очень удивило, что на моем процессоре haswell 17 4700hq (Windows 8.1 x64, C, MSVS v120) для их работы требуется примерно на 70% больше времени. Я ожидал, что время работы будет таким же или поплавки будут работать быстрее. Но явно нет. Поэтому я отключил все оптимизации, все равно. Пробовал на отладочной версии, все те же проблемы с производительностью. AVX2 и SSE 3 показывали это.

Двойникам требуется около 197 секунд для запуска и 343 секунды для плавания.

Я просмотрел Руководство разработчика программного обеспечения для архитектур Intel® 64 и IA-32, но, учитывая его размер и отсутствие у меня опыта, я еще не нашел в нем никаких ответов на этот счет. Потом я взглянул на разборку обоих, но каких-то вопиющих отличий на мой нетренированный взгляд не заметил.

Итак, кто-нибудь знает, почему это так? Вот код, который я использовал, с единственными изменениями: с удвоения на число с плавающей запятой для всех, кроме переменной anError.

#include <errno.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <omp.h>



int main( void ) {

    clock_t start = clock() ;

    // body of a candle
    double open = 0.500000 ;
    double close = 0.500001 ;
    double end = 1 ;

    uint64_t resultCounter = 0 ;
    double anError = 0 ;

    while (open < end){
        while(close < end){
            //calc # times result is postive. Should be 0.
            double res = open - close ;
            if (res > 0 ) { 
                resultCounter++ ; 
                if (anError < fabs( res )) { anError = res ;    }
            }
            close = close + 0.000001 ;
        }
        open = open + 0.000001 ;
        close = open + .000001 ;
    }

    clock_t finish = clock() ;
    double duration = ((double) (finish - start)) / CLOCKS_PER_SEC;
    double iterations = (((end - .50000) / .000001) * ((end - .50000) / .000001)) ;
    fprintf( stdout, "\nTotal processing time was %f seconds.\n", duration ) ;
    fprintf( stdout, "Error is %f. Number of times results were incorrect %llu out of %f iterations.\n", 
        anError, resultCounter, iterations ) ;

    return 0  ;
}

РЕДАКТИРОВАТЬ: причиной, по-видимому, является отсутствие f в конце чисел (спасибо, Иоахим!). По-видимому, константа с плавающей запятой без суффикса f на самом деле является двойной! Еще одна причуда Си, которая любит кусать невежественных за зад. Не уверен, в чем причина этой странности, но пожал плечами. Если кто-то хочет написать хороший ответ на это, чтобы я мог его принять, не стесняйтесь.


person Jason White    schedule 04.09.2015    source источник
comment
В программе, использующей float вместо этого, вы не забыли использовать, например. 0.000001f вместо простого 0.000001? Или использовать fabsf вместо fabs? В противном случае компилятору придется добавить код для преобразования чисел между float и double.   -  person Some programmer dude    schedule 04.09.2015
comment
Кроме того, для более справедливого теста вам действительно следует запустить цикл вычислений несколько раз и получить среднее значение.   -  person Some programmer dude    schedule 04.09.2015
comment
Хм. Если после устранения неявных преобразований (с использованием математических функций f*()) остались какие-либо различия, результаты будут сильно зависеть от компилятора, процессора и даже от того, компилируете ли вы 32-битную или 64-битную систему. В x86 с поддержкой SSE регистры SSE имеют ширину 128 бит, поэтому вы получаете преобразование в любом случае. 8 аппаратных регистров с плавающей запятой (теперь, вероятно, эмулированных с помощью SSE) до этого были 80-битными, поэтому они тоже не подходят.   -  person dhke    schedule 04.09.2015
comment
Опубликуйте версию float тоже.   -  person chux - Reinstate Monica    schedule 04.09.2015
comment
MSVC имеет дурную привычку повышать промежуточные звенья до double, когда вы используете fp:precise. Вместо этого попробуйте fp:fast.   -  person Mysticial    schedule 04.09.2015
comment
Итак, почему число с плавающей запятой = 1,0233 по существу является двойным, если вы не вводите его как 1,0233f? Разве поплавка не должно быть достаточно?   -  person Jason White    schedule 05.09.2015
comment
@dhke: Если вы считаете, что 128-битные SSE имеют собственную четырехкратную точность, я не уверен, провалился ли маркетинг Intel ужасно или великолепно.   -  person EOF    schedule 05.09.2015
comment
потому что любое постоянное число будет преобразовано компилятором в некоторое число с плавающей запятой, максимально близкое к нему. Не гарантируется, что преобразованное число и исходное число точно равны; скорее всего, будет какая-то разница или ошибка. Преобразование в double (вместо float) минимизирует эту ошибку. Именно поэтому компиляторы конвертируют в double, чтобы максимально надежно перевести код   -  person A.S.H    schedule 05.09.2015
comment
Проблема скорее всего не в присваивании констант, а в выполнении над ними арифметических действий. Если любой из операндов является двойным, то число с плавающей запятой будет преобразовано в двойное, а результат будет преобразован обратно в число с плавающей запятой. Когда вы присваиваете переменную константе, преобразование обычно выполняется во время компиляции, а не во время выполнения.   -  person Mark Ransom    schedule 05.09.2015
comment
@JasonWhite - Обновление вопроса с ответом считается плохим стилем - Вместо того, чтобы РЕДАКТИРОВАНИЕ ответа было частью вопроса, вы должны опубликовать правильный ответ и принять свой собственный ответ - то есть, если Иоахим не ожидает напишите ответ и просто оставьте комментарий   -  person Soren    schedule 05.09.2015
comment
@EOF: регистры имеют ширину 128 бит, что облегчает преобразование. Я ничего не утверждал о фактической числовой точности во время вычислений (что отличается, поскольку вы вставляете 2 двойных числа в xmm?). Пожалуйста, не интерпретируйте утверждения, которых нет.   -  person dhke    schedule 05.09.2015
comment
@dhke: Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 2 (2A, 2B & 2C): Instruction Set Reference, A-Z Page 3-574 MOVSS—Move Scalar Single-Precision Floating-Point Values. Вы можете использовать числа с плавающей запятой одинарной точности в SSE, преобразование не требуется.   -  person EOF    schedule 05.09.2015
comment
@EOF Возможно, конверсия дала неверный сигнал, потому что вы все еще интерпретируете утверждение, которого я не делал. Инструкции заполнения и разлива SSE различаются для одинарной и двойной точности, и вы можете использовать разные (поплавки могут быть загружены даже вчетверо, если они правильно выровнены). Так что это зависит от того, во что компилируется код.   -  person dhke    schedule 06.09.2015


Ответы (1)


Согласно стандарту C:

Плавающая константа без суффикса имеет тип double. Если суффикс представляет собой букву f или F, плавающая константа имеет тип float. Если суффикс представляет собой букву l или L, плавающая константа имеет тип long double

Подробнее о константах с плавающей запятой здесь. Так :

число — это просто число с плавающей запятой

float num = 1.0f;
float num = 1.0F;

двойное значение преобразуется в число с плавающей точкой и сохраняется в числовом формате

float num = 1.0;

число с плавающей точкой преобразуется в двойное и сохраняется в числовом формате

double num = 1.0f;
double num = 1.0F;

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

person Manos Nikolaidis    schedule 04.09.2015