fortran - Tiempo de CPU en precisión cuádruple vs. doble
scientific-computing quadruple-precision (2)
Es interesante notar que si cambias:
sqrt_qp = sqrt(sqrt_qp)
sqrt_qp = 2._qp
a
sqrt_qp = sqrt(2._qp)
¡el cálculo será más rápido!
Estoy haciendo algunas simulaciones a largo plazo en las que trato de lograr la mayor precisión posible en la solución de sistemas de ODE. Estoy tratando de averiguar cuánto tiempo toman los cálculos de precisión cuádruple (128 bits) frente a la precisión doble (64 bits). Busqué en Google un poco y vi varias opiniones al respecto: algunos dicen que tomará 4 veces más tiempo, otros 60-70 veces ... Así que decidí tomar el asunto en mis manos y escribí un sencillo programa de referencia de Fortran:
program QUAD_TEST
implicit none
integer,parameter :: dp = selected_int_kind(15)
integer,parameter :: qp = selected_int_kind(33)
integer :: cstart_dp,cend_dp,cstart_qp,cend_qp,crate
real :: time_dp,time_qp
real(dp) :: sum_dp,sqrt_dp,pi_dp,mone_dp,zero_dp
real(qp) :: sum_qp,sqrt_qp,pi_qp,mone_qp,zero_qp
integer :: i
! ==============================================================================
! == TEST 1. ELEMENTARY OPERATIONS ==
sum_dp = 1._dp
sum_qp = 1._qp
call SYSTEM_CLOCK(count_rate=crate)
write(*,*) ''Testing elementary operations...''
call SYSTEM_CLOCK(count=cstart_dp)
do i=1,50000000
sum_dp = sum_dp - 1._dp
sum_dp = sum_dp + 1._dp
sum_dp = sum_dp*2._dp
sum_dp = sum_dp/2._dp
end do
call SYSTEM_CLOCK(count=cend_dp)
time_dp = real(cend_dp - cstart_dp)/real(crate)
write(*,*) ''DP sum: '',sum_dp
write(*,*) ''DP time: '',time_dp,'' seconds''
call SYSTEM_CLOCK(count=cstart_qp)
do i=1,50000000
sum_qp = sum_qp - 1._qp
sum_qp = sum_qp + 1._qp
sum_qp = sum_qp*2._qp
sum_qp = sum_qp/2._qp
end do
call SYSTEM_CLOCK(count=cend_qp)
time_qp = real(cend_qp - cstart_qp)/real(crate)
write(*,*) ''QP sum: '',sum_qp
write(*,*) ''QP time: '',time_qp,'' seconds''
write(*,*)
write(*,*) ''DP is '',time_qp/time_dp,'' times faster.''
write(*,*)
! == TEST 2. SQUARE ROOT ==
sqrt_dp = 2._dp
sqrt_qp = 2._qp
write(*,*) ''Testing square root ...''
call SYSTEM_CLOCK(count=cstart_dp)
do i = 1,10000000
sqrt_dp = sqrt(sqrt_dp)
sqrt_dp = 2._dp
end do
call SYSTEM_CLOCK(count=cend_dp)
time_dp = real(cend_dp - cstart_dp)/real(crate)
write(*,*) ''DP sqrt: '',sqrt_dp
write(*,*) ''DP time: '',time_dp,'' seconds''
call SYSTEM_CLOCK(count=cstart_qp)
do i = 1,10000000
sqrt_qp = sqrt(sqrt_qp)
sqrt_qp = 2._qp
end do
call SYSTEM_CLOCK(count=cend_qp)
time_qp = real(cend_qp - cstart_qp)/real(crate)
write(*,*) ''QP sqrt: '',sqrt_qp
write(*,*) ''QP time: '',time_qp,'' seconds''
write(*,*)
write(*,*) ''DP is '',time_qp/time_dp,'' times faster.''
write(*,*)
! == TEST 3. TRIGONOMETRIC FUNCTIONS ==
pi_dp = acos(-1._dp); mone_dp = 1._dp; zero_dp = 0._dp
pi_qp = acos(-1._qp); mone_qp = 1._qp; zero_qp = 0._qp
write(*,*) ''Testing trigonometric functions ...''
call SYSTEM_CLOCK(count=cstart_dp)
do i = 1,10000000
mone_dp = cos(pi_dp)
zero_dp = sin(pi_dp)
end do
call SYSTEM_CLOCK(count=cend_dp)
time_dp = real(cend_dp - cstart_dp)/real(crate)
write(*,*) ''DP cos: '',mone_dp
write(*,*) ''DP sin: '',zero_dp
write(*,*) ''DP time: '',time_dp,'' seconds''
call SYSTEM_CLOCK(count=cstart_qp)
do i = 1,10000000
mone_qp = cos(pi_qp)
zero_qp = sin(pi_qp)
end do
call SYSTEM_CLOCK(count=cend_qp)
time_qp = real(cend_qp - cstart_qp)/real(crate)
write(*,*) ''QP cos: '',mone_qp
write(*,*) ''QP sin: '',zero_qp
write(*,*) ''QP time: '',time_qp,'' seconds''
write(*,*)
write(*,*) ''DP is '',time_qp/time_dp,'' times faster.''
write(*,*)
end program QUAD_TEST
Resultados de una ejecución típica, después de compilar con gfortran 4.8.4
, sin ningún indicador de optimización:
Testing elementary operations...
DP sum: 1.0000000000000000
DP time: 0.572000027 seconds
QP sum: 1.00000000000000000000000000000000000
QP time: 4.32299995 seconds
DP is 7.55769205 times faster.
Testing square root ...
DP sqrt: 2.0000000000000000
DP time: 5.20000011E-02 seconds
QP sqrt: 2.00000000000000000000000000000000000
QP time: 2.60700011 seconds
DP is 50.1346169 times faster.
Testing trigonometric functions ...
DP cos: -1.0000000000000000
DP sin: 1.2246467991473532E-016
DP time: 2.79600000 seconds
QP cos: -1.00000000000000000000000000000000000
QP sin: 8.67181013012378102479704402604335225E-0035
QP time: 5.90199995 seconds
DP is 2.11087275 times faster.
Algo debe estar pasando aquí. Mi suposición es que sqrt
se calcula con gfortran
través de un algoritmo optimizado, que probablemente no se haya implementado para cálculos de cuádruple precisión. Este podría no ser el caso para sin
y cos
, pero ¿por qué las operaciones elementales son 7.6 veces más lentas en precisión cuádruple mientras que para las funciones trigonométricas las cosas se desaceleran solo en un factor de 2? Si el algoritmo utilizado para las funciones trigonométricas fuera el mismo para precisión cuádruple y doble, también esperaría ver un aumento de siete veces en el tiempo de CPU para ellos.
¿Cuál es la desaceleración promedio en los cálculos científicos cuando se utiliza la precisión de 128 bits, en comparación con 64 bits?
Estoy ejecutando esto en un Intel i7-4771 @ 3.50GHz.
Más un comentario extendido que una respuesta, pero ...
Las CPU actuales proporcionan una gran cantidad de aceleración de hardware para la aritmética de coma flotante de doble precisión. Algunos incluso proporcionan instalaciones para una mayor precisión. Más allá de eso, estás limitado a implementaciones de software que son (como habrás notado) considerablemente más lentas.
Sin embargo, el factor exacto de esta ralentización es casi imposible de predecir en casos genéricos. Depende de su CPU (por ejemplo, qué aceleración tiene incorporada) y de la pila de software. Para la precisión doble, normalmente utiliza diferentes bibliotecas matemáticas que para la precisión cuádruple, y es posible que utilicen algoritmos diferentes para las operaciones básicas.
Para una operación / algoritmo en particular en un hardware dado usando el mismo algoritmo, probablemente pueda derivar un número, pero esto seguramente no será universalmente cierto.