ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect…

Follow publication

Benchmarking sine functions.

James William Fletcher
ITNEXT
Published in
3 min readAug 30, 2021

It is rare that anyone would want to circumvent the sin() function, but, there are a few cases where it would be beneficial. This document benchmarks the regular sin() function against a linearly interpolated wavetable, an aliased wavetable, an infinite impulse response sine wave, and seven polynomial approximations of varying accuracy.

The IIR sine wave is setup with initial parameters and then called on a per tick basis, it is not the kind of function you’d pass parameters to on every call; just on initialisation. These are good for audio applications when you need simple and high-quality sine waves at a low cost. All other functions provided are designed to be passed a theta, omega, radians, degree, etc on each call.

You might like to take a look at the code for the benchmark and compile it yourself before we continue, if not that is fine, but if you do it’s over here:
https://gist.github.com/mrbid/1f25bfc27d97b81d5d9ec5e45f81a6e1
compile using the command; gcc sine_bench.c -Ofast -lm -o sine_bench

Now for the benchmarks, one thing to keep in mind is that these benchmarks were performed with the -Ofast optimisation flag enabled, the reason for this is that the margins are quite tight otherwise to the point where none of the performance gains would really be worth the precision loss over just using the regular sin() function.

Slowest to fastest...
Executions in 1 second...
-------------------------
sin(): 26,557,697
fast_sin1(): 33,017,414
fast_sin2(): 34,991,929
lerp_sin(): 37,344,972
fast_sin4(): 37,701,782
fast_sin7(): 45,245,155
fast_sin5(): 45,613,310
fast_sin3(): 45,926,931
fast_sin6(): 46,565,172
aliased_sin(): 47,113,225
IIRSine2(): 69,406,434
IIRSine(): 70,789,548

First we can see that IIRSine() is significantly faster so if you can get away with using these functions it’s a no-brainer. We also see that sin() is generally the slowest but that’s to be expected of a precision function, when we look for faster alternatives it’s a given we are going to sacrifice precision.

Now it’s time to look at the accuracy of the functions to an average deviance over 6 decimal places (0.000001):

Avg error of functions from 0 to 3.1 ...
----------------------------------------
lerp_sin(): 0.000032
aliased_sin(): 0.000030
fast_sin1(): 0.002653
fast_sin2(): 0.000125
fast_sin3(): 0.035730
fast_sin4(): 0.000012
fast_sin5(): 0.000832
fast_sin6(): 0.378000
fast_sin7(): 2.774034
Avg error of functions from 0 to 6.3 ...
----------------------------------------
lerp_sin(): 0.000032
aliased_sin(): 0.000030
fast_sin1(): 0.002660
fast_sin2(): 0.000125
fast_sin3(): 5.304368
fast_sin4(): 0.181263
fast_sin5(): 0.386772
fast_sin6(): 3.512034
fast_sin7(): 41.145363
Avg error of functions from 0 to 9.4 ...
----------------------------------------
lerp_sin(): 0.000032
aliased_sin(): 0.000030
fast_sin1(): 0.002661
fast_sin2(): 0.000121
fast_sin3(): 64.467628
fast_sin4(): 33.455360
fast_sin5(): 1.473552
fast_sin6(): 11.423285
fast_sin7(): 471.490936
Avg error of functions from 0 to 12.6 ...
-----------------------------------------
lerp_sin(): 0.000031
aliased_sin(): 0.000030
fast_sin1(): 0.002621
fast_sin2(): 0.000117
fast_sin3(): 322.956146
fast_sin4(): 1181.200317
fast_sin5(): 1.779256
fast_sin6(): 22.638031
fast_sin7(): 3915.010742

We can see that aliased_sin() is by far the most accurate approximation, but only mildly over lerp_sin(). aliased_sin() is also a lot faster to compute and holds its accuracy longer than the polynomial approximations. All of the polynomial approximations get pretty bad — pretty fast, apart from fast_sin2() by nightcracker over at GameDev forums. fast_sin1() is pretty good too but it’s always slightly slower and less accurate than fast_sin2().

Generally from these results, I would summarise that if you really need performance and a high-quality sine wave use the IIRSine() function if you can get away with it, if not, and you must avoid the sin() function at all costs then your best bet is to try the liner interpolated wavetable or aliased wavetable lerp_sin()and aliased_sin() functions respectively which come in at 33.7% and 55.8% faster than the original sin() function —making them the fastest and most accurate alternatives. But if memory is a constraint and you really cannot spare as little as 1024 bytes to 262 kilobytes for one of the wavetables then the fast_sin2() polynomial is your next best port of call.

Final notes:
The lerp_sin() function uses a 256 sample wavetable with linear interpolation whereas the aliased_sin() function uses a 65536 sample wavetable with no interpolation. The reason aliased_sin() came out more accurate is simply because there are more samples and it turns out that on modern hardware having more samples is faster than having fewer samples with interpolation.

Image Source: wikipedia

Published in ITNEXT

ITNEXT is a platform for IT developers & software engineers to share knowledge, connect, collaborate, learn and experience next-gen technologies.

Responses (1)

Write a response

SLEEP Benchmark (https://sleef.org/)
sin() Cycles: 174
Executions in 1 seconds: 25,529,223
Executions per millisecond: 25,529
Executions per microsecond: 25
~0.02552922 executions every nanosecond
Sleef_sinf_u10() Cycles: 190
Executions in 1 seconds…

--