I am trying to optimize code for Monte Carlo simulation. Even minute performance differences piles up after 100 million iterations and thus I need to squeeze every nanosecond from math operations!
One area where I thought I could save a lot stems from the fact that I only require precision of 4 significant digits. It therefore seems natural to use float rather than double.
However, some testing suggests that double still performs better! This is unexpected.
Why is it that despite the fact that float is 32 bits and double 64 bits, mathh functions are quicker to perform exp(double) and pow(double, double) than exp(float) and pow(float, float) (or even expf and powf)? Here is some code...

#include <math.h>
#include <iostream>
#include "Timer.h"

int main()
{
	double a = 23.14;
	float c = 23.14;
	Timer t;
	t.tic();
	for (int i = 0; i < 10000000; i++)
		expf(c);
	cout<<"expf(float) returns " << expf(c)<<" and took "<<t.toc()<< " seconds." << endl;
	t.tic();
	for (int i = 0; i < 10000000; i++)
		exp(c);
	cout<<"exp(float) returns " << exp(c)<<" and took "<<t.toc()<< " seconds." << endl;
	t.tic();
	for (int i = 0; i < 10000000; i++)
		exp(a);
	cout<<"exp(double) returns " << exp(a)<<" and took "<<t.toc()<< " seconds." << endl;
}

Dani AI

Generated

A few clarifying points that tie the thread together and give practical next steps.

The surprising result is not unusual. As found, calls to library math functions often behave counter‑intuitively: raw storage width (32 vs 64 bits) is only one factor. ’s original remark about promotion was corrected by (promotion to double only happens for unprototyped calls or variadic args). ’s note about modern 64‑bit CPUs is also relevant — many processors and ABIs treat single‑ and double‑precision math similarly at the pipeline level.

Likely causes to investigate

  • Many libm implementations provide expf/powf as thin wrappers that convert the float to double, call the double routine, then convert back; that removes any speed advantage.
  • Header and overload choices matter: <cmath> in C++ gives proper overloads the compiler can optimize; <math.h> can behave differently.
  • Compiler flags, ABI (x87 vs SSE), and CPU frequency scaling can change timings dramatically. Also, pow is especially expensive (often implemented via exp(log(x)*y)).

Concrete diagnostics (do these first)

  • Compile optimized and inspect generated assembly to see whether expf is actually being called or whether the compiler calls exp. Example commands to generate and inspect code:
    g++ -O3 -march=native -ffast-math -fno-math-errno -mfpmath=sse test.cpp -S -o test.s
    objdump -d -M intel a.out | grep -E "exp|expf|pow|powf"
  • Ensure float literals use an f suffix, include <cmath>, and prevent the optimizer removing the loop (use a volatile sink or accumulate the result). Run a warm‑up and set the CPU governor to performance to avoid Turbo/Scaling artifacts.

If math is the hotspot

  • For 4 significant digits, replace libm calls with a tuned approximation (low‑order minimax polynomial, table + interpolation, or a vetted “fast exp” routine).
  • Use a vectorized math library (SLEEF/Intel SVML/VDT) or hand SIMD to compute many exps at once.
  • Precompute or batch inputs where possible.

Bottom line: switching to float alone won’t guarantee speedups for exp/pow. Measuring and inspecting the actual generated calls is the fastest way to identify the real bottleneck and choose an appropriate optimization path.

Recommended Answers

All 5 Replies

>>However, some testing suggests that double still performs better! This is unexpected.

Yup. floats are always converted to doubles when used as function parameters. Other factors may influence it too, such as the math coprocessor on your computer.

Yup. floats are always converted to doubles when used as function parameters.

http://groups.google.com/group/comp.lang.c/browse_thread/thread/2aaf5360b08c89a9/1000b1f7fb33ea53?ie=UTF-8&q=float+promoted+double+function+group%3Acomp.lang.c&pli=1

No, that's only true if
(1) you call the function without a prototype in scope, or
(2) it's a variable argument to a variadic function like 'printf'.

commented: you are right +25

Dave: Yes, I see you are correct. I wrote a short test program and had the compiler produce assembler instructions, which showed the same behavior as what you posted.

floats are made for spaced optimization, it does not necessarily have to
be faster than double, especially in a 64bit CPU.

How about you show some code, so we can try to help you,
although monte carlo problem should be slow to execute as it is quite
easy to implement.

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.