slow math sin function for some values on amd64

Bug #192134 reported by Petr Cervenka
2
Affects Status Importance Assigned to Milestone
GLibC
Fix Released
Wishlist
glibc (Ubuntu)
Triaged
Medium
Unassigned

Bug Description

Hello.
I have found that math sin function is unreasonable slow (400us on Athlon64 A2 4800+) for some values. It only happens on 64bit distribution.
Used versions:
Kubuntu 7.10 amd64 gutsy gutter
linux kernel 2.6.24
libc6 2.6.1-1ubuntu10
libc6-dev 2.6.1-1ubuntu10

Some of those values: -----------------------------------------------------------------------
0.93340582292648832662962377071381 00 b0 6b e3 75 de ed 3f 0x3fedde75e36bb000
2.3328432680770916363144351635128 d0 ad 38 bb a9 a9 02 40 0x4002a9a9bb38add0
3.7439477503636453548097051680088 00 f5 cd e0 9a f3 0d 40 0x400df39ae0cdf500
3.9225160069792437411706487182528 50 19 80 12 50 61 0f 40 0x400f615012801950
4.0711651639931289992091478779912 20 dc 4f 85 df 48 10 40 0x401048df854fdc20
4.7858438478542097982426639646292 c0 2f e9 3f b4 24 13 40 0x401324b43fe92fc0
5.9840767662578002727968851104379 a0 52 df d1 b1 ef 17 40 0x4017efb1d1df52a0

Short program for testing: ------------------------------------------------------------------
#include <stdlib.h>
#include <sstream>
#include <iostream>
#include <iomanip>
#include <math.h>
#include <sys/time.h>

using namespace std;

int main(int argc, char** argv) {

    volatile union {
        double dbl;
        unsigned char hex[sizeof(double)];
    } value;

    if (argc == sizeof(double)+1) {
        for (int i=0; i < sizeof(double); i++) {
            istringstream s(argv[i+1]);
            int tmp;
            s >> hex >> tmp;
            value.hex[i] = tmp;
        }
    } else if (argc == 2) {
        istringstream s(argv[1]);
        double tmp;
        s >> tmp;
        value.dbl = tmp;
    } else {
        cout << "usage: sintest 00 b0 6b e3 75 de ed 3f\n"
                " sintest 0.93340582292648832662962377071381";
        return (EXIT_FAILURE);
    }

    cout.precision(32);
    cout << value.dbl << endl;

    cout << "start\n";

    struct timeval time1, time2;
    gettimeofday(&time1, NULL);
    for (int i=0; i < 10000; i++) {
        volatile double out = sin(value.dbl);
    }
    gettimeofday(&time2, NULL);

    long long diftime = 1000000ll * (time2.tv_sec - time1.tv_sec) +
                         (time2.tv_usec - time1.tv_usec);

    cout << "end: " << diftime / 1000000ll << '.' << setw(6) << setfill('0') << diftime % 1000000ll << " s" << endl;

    return (EXIT_SUCCESS);
}

Petr Cervenka

Revision history for this message
In , Petr Cervenka (grugh) wrote :

I would like to repost my previously deleted bug by (lazy IMHO)
<email address hidden>. The math sin function is at least 1000x slower on 64bit
distributions for special numbers (and carlos dosn't care about it).
I can't try it with CVS head, because I cannot connect to cvs through our firewall.
But even when I tried the latest snapshot, I couldn't build it (maybe another bug):
a - elf/dl-vdso.os
: /home/inova/projects/glibc/build/libc_pic.a
gcc -nostdlib -nostartfiles -r -o
/home/inova/projects/glibc/build/elf/librtld.map.o '-Wl,-('
/home/inova/projects/glibc/build/elf/dl-allobjs.os
/home/inova/projects/glibc/build/libc_pic.a -lgcc '-Wl,-)'
-Wl,-Map,/home/inova/projects/glibc/build/elf/librtld.mapT
/home/inova/projects/glibc/build/libc_pic.a(init-first.os):(.data+0x0): multiple
definition of `__libc_multiple_libcs'
/home/inova/projects/glibc/build/elf/dl-allobjs.os:/home/inova/projects/glibc/src/glibc-20080218/elf/rtld.c:641:
first defined here
/home/inova/projects/glibc/build/libc_pic.a(dl-addr.os): In function
`_dl_addr_inside_object':
/home/inova/projects/glibc/src/glibc-20080218/elf/dl-addr.c:158: multiple
definition of `_dl_addr_inside_object'
/home/inova/projects/glibc/build/elf/dl-allobjs.os:/home/inova/projects/glibc/src/glibc-20080218/elf/dl-open.c:700:
first defined here
collect2: ld returned 1 exit status
make[2]: *** [/home/inova/projects/glibc/build/elf/librtld.map] Error 1
make[2]: Leaving directory `/home/inova/projects/glibc/src/glibc-20080218/elf'
make[1]: *** [elf/subdir_lib] Error 2
make[1]: Leaving directory `/home/inova/projects/glibc/src/glibc-20080218'
make: *** [all] Error 2

Please, anyone with 64bit distribution and glibc CVS head, could you try the
attached example and post the time results of it? (or help me to build the
snapshot...)
Thank you

====== Original bug report ======================================
The math sin(double) function is in 64bit distribution (Kubuntu 7.10 AMD64 and
Fedora - unknown version) unreasonable slow (~400 microseconds on Atlon64 X2
4800+!!!) for some special values. In 32bit distribution is everything fine.
I captured some of those values:
0.93340582292648832662962377071381 0x3fedde75e36bb000
2.3328432680770916363144351635128 0x4002a9a9bb38add0
3.7439477503636453548097051680088 0x400df39ae0cdf500
3.9225160069792437411706487182528 0x400f615012801950
4.0711651639931289992091478779912 0x401048df854fdc20
4.7858438478542097982426639646292 0x401324b43fe92fc0
5.9840767662578002727968851104379 0x4017efb1d1df52a0

Example:
#include <math.h>
int main(int argc, char** argv) {
    volatile double value = 0.93340582292648832662962377071381;
    volatile double out;
    int i;
    for (i=0; i < 20000; i++)
        out = sin(value);
    return 0;
}

Revision history for this message
In , Jakub Jelinek (jakub-redhat) wrote :

Most of the double routines in libm come from IBM accurate matematical library,
which ensures <= 0.5ulp error. Trigonometric etc. functions are computed using
floating point computations, but if the possible error from that is too high, it
uses slower multiprecision computation to guarantee ultimate precise result.
Guess you just picked some worst-case values.
i386 uses the non-precise hardware instructions instead, so doesn't guarantee
the <= 0.5ulp precision.

Revision history for this message
In , Joseph-codesourcery (joseph-codesourcery) wrote :

Subject: Re: Slow sine function for special values on AMD64
 - second attempt

On Thu, 21 Feb 2008, jakub at redhat dot com wrote:

> which ensures <= 0.5ulp error. Trigonometric etc. functions are
> computed using floating point computations, but if the possible error
> from that is too high, it uses slower multiprecision computation to
> guarantee ultimate precise result. Guess you just picked some worst-case
> values.

Note that the crlibm developers were willing to contribute their code, an
advantage of which is *much* better worst-case performance.

Revision history for this message
In , Jakub Jelinek (jakub-redhat) wrote :

Yeah, I'm aware of crlibm, I think if it proves itself that it won't be much
slower on average, has the same ultimate precision guarantees and faster
worst-cases, I don't see a reason why it can't be integrated. It will be a lot
of work to integrate it though.

Revision history for this message
In , Petr Cervenka (grugh) wrote :

Is there any compile flag or #define, which can disable the <=0.5 ulp precision
and the math sin function will use only the fast built-in fp intructions?
For our real-time software it is necessary to be "quick", the ultra precision
has low priority.
Now we are using a workaround: I can put the original argument to long double
variable and call sinl function with long double result. Both, the new argument
and the result, have to be volatile to disable the compiler optimization of it
(probably uses the "fast" sin instead).

Results of sin(0.93340582292648832662962377071381)
----------------------------------------------------
distr function value result_type printf_format
--------------------------------------------------------------------
32 sin 0.80365140438773496889268699305831 double "%.32g"
32 sinl 0.80365140438773496889268699305831 double "%.32g"
32 sinl 0.80365140438773491338153576180048 long double "%.32Lg

64 sin 0.80365140438773485787038453054265 double "%.32g"
 (~ -5.5511151231257827021181583404541e-17 difference from 80bit value)
64 sinl 0.80365140438773496889268699305831 double "%.32g"
 (~ +5.5511151231257827021181583404541e-17 difference from 80bit value )
64 sinl 0.80365140438773491338153576180048 long double "%.32Lg"

Revision history for this message
In , Petr Cervenka (grugh) wrote :

I'm not the only one with such problems:
http://sources.redhat.com/bugzilla/show_bug.cgi?id=5997
I assume that for the 64-bit distribution (x86_64), it should use sin and sinf
from i386 arch (sysdeps\i386\fpu\s_sin.S and sysdeps\i386\fpu\s_sinf.S) and only
sinl implementation is explicit x86_64. But the sin and sinf are now used as
software versions (IBM library). And it's usually bit slower, sometimes MUCH
MORE slower (1000x).
IBM library is perhaps only emergency implementation (if there is no hw support)
and it's not used for "better" (<= 0.5ULP) precision.
"The First Step is to Admit You Have a Problem!"

Revision history for this message
Matthias Klose (doko) wrote :

please could you recheck with 8.04 (hardy), and 8.10 (intrepid)?

Changed in glibc:
status: New → Incomplete
Revision history for this message
Petr Cervenka (grugh) wrote :

I have tried it with Kubuntu KDE4 8.04 AMD64 and the error is still there. But it's as I expected, because the error is in glibc library and it has nothing to do with kubuntu or ubuntu. Link to my bug post to glibc (ignored successfully lately):
http://sourceware.org/bugzilla/show_bug.cgi?id=5781
Sorry for posting it here. It was when I didn't know the true source of the error.
Thank you for your effort anyway.

Changed in glibc:
status: Unknown → Confirmed
Matthias Klose (doko)
Changed in glibc:
importance: Undecided → Medium
status: Incomplete → Triaged
Changed in glibc:
importance: Unknown → Medium
Revision history for this message
In , Jsm28 (jsm28) wrote :

Confirmed with current sources. Suspending until a faster correctly rounding implementation (such as that proposed in http://gcc.gnu.org/ml/gcc/2012-02/msg00298.html ) is available as this is probably not amenable to a simple local fix.

Changed in glibc:
status: Confirmed → Incomplete
Revision history for this message
In , Siddhesh (siddhesh) wrote :

FWIW, the function now runs much faster after the multiple precision improvements. The worst case is only about a 100 times slower now instead of 1000 times.

I've not looked yet, but I think there is a case for capping maximum precision for worst case computation for sin (and all trigonometric functions) as well, so this could get even better.

Revision history for this message
In , Siddhesh (siddhesh) wrote :

Opening this since I've been working on improvements to the multiple precision bits that should have positive effect here. In fact as I mentioned in comment 7, improvements are already evident.

Since optimization patches can go on forever, I'm going to put a cap on it for the resolution of this bug. The cap is to implement findings of [1] if applicable.

[1] http://perso.ens-lyon.fr/jean-michel.muller/TMDworstcases.pdf

Changed in glibc:
status: Incomplete → In Progress
Changed in glibc:
importance: Medium → Wishlist
Revision history for this message
In , John-wilkinson (john-wilkinson) wrote :

I have also come across a very similar issue on i7 Intel platforms, please see bug 16531. Calls to cos can take around 0.15 ms, 1000 times their normal time, which is a serious problem for the real-time system we are developing.

Revision history for this message
In , John-wilkinson (john-wilkinson) wrote :

*** Bug 16531 has been marked as a duplicate of this bug. ***

Revision history for this message
In , Carlos-0 (carlos-0) wrote :

(In reply to John Wilkinson from comment #9)
> I have also come across a very similar issue on i7 Intel platforms, please
> see bug 16531. Calls to cos can take around 0.15 ms, 1000 times their normal
> time, which is a serious problem for the real-time system we are developing.

The default libm functions never guarantee constant runtime. You will have this same problem for many of the functions provided by the library.

However we are working on enhancing libm to include something like what you're looking for. Please have look at and comment:
https://sourceware.org/glibc/wiki/libm

Revision history for this message
In , Jsm28 (jsm28) wrote :

Really the issue for sin/cos/sincos is the same, so retitling the bug.

Revision history for this message
In , Jsm28 (jsm28) wrote :

*** Bug 14412 has been marked as a duplicate of this bug. ***

Revision history for this message
In , John-wilkinson (john-wilkinson) wrote :

(In reply to Carlos O'Donell from comment #11)
> However we are working on enhancing libm to include something like what
> you're looking for. Please have look at and comment:
> https://sourceware.org/glibc/wiki/libm

Thanks that looks useful. Is there a release schedule?

Revision history for this message
In , Carlos-0 (carlos-0) wrote :

(In reply to John Wilkinson from comment #14)
> (In reply to Carlos O'Donell from comment #11)
> > However we are working on enhancing libm to include something like what
> > you're looking for. Please have look at and comment:
> > https://sourceware.org/glibc/wiki/libm
>
> Thanks that looks useful. Is there a release schedule?

Not yet. I'll update the wiki when I can commit resources. That doesn't stop others from joining in the discussion, or adding notes to the wiki like use cases and requirements.

Revision history for this message
In , Petr Cervenka (grugh) wrote :

Simple workaround to use fast computation is to use functions from spec. header similar to following:

#ifndef FAST_MATH_H
#define FAST_MATH_H

#include <cmath>

inline double fast_sin(long double x) {
    return sinl(x);
}

inline double fast_cos(long double x) {
    return cosl(x);
}

inline double fast_tan(long double x) {
    return tanl(x);
}

inline double fast_asin(long double x) {
    return asinl(x);
}

inline double fast_acos(long double x) {
    return acosl(x);
}

inline double fast_atan(long double x) {
    return atanl(x);
}

inline double fast_atan2(long double x, long double y) {
    return atan2l(x, y);
}

inline double fast_sinh(long double x) {
    return sinhl(x);
}

inline double fast_cosh(long double x) {
    return coshl(x);
}

inline double fast_asinh(long double x) {
    return asinhl(x);
}

inline double fast_acosh(long double x) {
    return acoshl(x);
}

inline double fast_pow(long double x, long double y) {
    return powl(x, y);
}

inline double fast_sqrt(long double x) {
    return sqrtl(x);
}

inline double fast_exp(long double x) {
    return expl(x);
}

inline double fast_log(long double x) {
    return logl(x);
}

inline double fast_log10(long double x) {
    return log10l(x);
}

#endif /* FAST_MATH_H */

Revision history for this message
In , Vincent-srcware (vincent-srcware) wrote :

(In reply to Petr Cervenka from comment #16)
> Simple workaround to use fast computation is to use functions from spec.
> header similar to following:
[long double versions: sinl, etc.]

This is not quite correct. These long double versions currently have a lower worst-case time (this might change in the future), but in average they are slower than the current double versions, with a factor around 5 - 6 from some tests on my machine. So, use this workaround only if you want a better worst-case time, e.g. for real-time system (this is your case[1], isn't it?).

[1] http://www.xenomai.org/pipermail/xenomai/2008-February/012416.html

The current library is slow, and libraries such as CRlibm could greatly improve things, but hard-to-round cases would always be slower than the average cases. So, the best implementation depends on the user's application. I suppose that most users would be happy with a *good* correctly rounded implementation since the loss due to correct rounding should hardly be noticeable *in average* compared to an implementation with just a very good accuracy (something close to 0.5 ulp). Then there are users who accept to sacrifice correct rounding and accuracy for faster functions. IMHO, the question is whether the GNU libc should implement such variants and provide a way for the user to select them (but this could mean other two variants or more[2]) or the user should build his own library based on his own requirements, e.g. with tools like MetaLibm[3] or other future tools.

[2] This should cover the accuracy for small arguments, but users may also have different requirements concerning the range reduction (large arguments).

[3] http://www.metalibm.org/

Revision history for this message
In , Joseph-codesourcery (joseph-codesourcery) wrote :

As I outlined in
<https://sourceware.org/ml/libc-alpha/2013-07/msg00444.html>, and as
described in the discussion of accuracy goals in the glibc manual, I don't
think functions such as sin and cos should try to be correctly rounded,
only functions such as crsin and crcos (if added).

Revision history for this message
In , Wdijkstr (wdijkstr) wrote :

This was fixed by a series of commits starting from 19a8b9a300f2f1f0012aff0f2b70b09430f50d9e

All slow paths have now been removed from all math functions.

Revision history for this message
In , Joseph-codesourcery (joseph-codesourcery) wrote :

If you log in to Bugzilla with your @gcc.gnu.org address you should have
access to mark bugs RESOLVED / FIXED with target milestone set to the
first release that will have the fix, rather than just commenting that
they are fixed.

Revision history for this message
In , Siddhesh-n (siddhesh-n) wrote :

The last commit to remove slow paths appears to be this one:

commit f67f9c9af228f6b84579cb8c86312d3a7a206a55
Author: Anssi Hannula <email address hidden>
Date: Mon Jan 27 12:45:10 2020 +0200

    ieee754: Remove slow paths from asin and acos

So this was finished in 2.33.

Revision history for this message
In , Siddhesh-n (siddhesh-n) wrote :

I just noticed that the last slow path removal patches went in with Wilco's comment 19. Adjusting target milestone.

Changed in glibc:
status: In Progress → Fix Released
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.