Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

pow( double, int )

2 views
Skip to first unread message

Bonita Montero

unread,
Sep 7, 2022, 8:15:29 AM9/7/22
to
In C++ gibt es ein pow( double, int ), das es meines Wissens so in C
nicht gibt. Daher habe ich das mal nach-implementiert:

double cPowPow( double d, int e )
{
unsigned eAbs = e >= 0 ? e : -e;
double v = 1.0, sq = d;
for( ; eAbs; eAbs >>= 1, sq *= sq )
v *= (int)(eAbs & 1) * sq + (int)(eAbs & 1 ^ 1);
v = e >= 0 ? v : 1.0 / v;
return v;
}

Der Witz an obiger Implementation: Normalerweise müsste ich jedes Bit
von e einzeln abfragen und darauf einen bedingten Sprung machen und
wenn das Bit gesetzt ist v mit sq multiplzizieren.
Helfen würd hier ein conditional move für floating point Werte, aber das
gibt es so weder für die 87-FPU, noch für SSE oder AVX. Also hab ich mir
damit beholfen, eAbs zu maskieren und den Wert in einen floating point
Wert zu casten und sq damit zu multiplizieren. Ist das Bit nicht gesetzt
gibt das hinter dem Plus halt eins, dass alternativ damit multipliziert
wird.
Auf meiner AMD Zen2-CPU ist das Ganze dann etwas mehr als das Doppelte
schnell als meine C++ pow( double, int )-Implementation von Visual C++.

Hier noch ein wenig Mess-Code in C++20, unten dran ist die C-Funktion
im Vergleich, die minimal langsamer ist als die C++-Variante:

#if defined(_WIN32)
#define NOMINMAX
#include <Windows.h>
#endif
#include <iostream>
#include <cmath>
#include <chrono>
#include <random>
#include <vector>
#include <atomic>
#include <utility>
#include <thread>
#include <mutex>
#include <semaphore>
#include <vector>
#include <functional>

using namespace std;
using namespace chrono;

double powPow( double d, int e );
double cPowPow( double d, int e );

int main()
{
#if defined(_WIN32)
SetThreadAffinityMask( GetCurrentThread(), 1 );
SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_HIGHEST );
SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL );
#endif
constexpr size_t
N = 0x400,
ROUNDS = 10'000;
vector<int> exps( N );
{
mt19937_64 mt;
uniform_int_distribution<int> uid( numeric_limits<int>::min(),
numeric_limits<int>::max() );
for( int &v : exps )
v = uid( mt );
}
auto threadBenchPow = [&]<typename Pow>( bool dual, Pow pow ) -> double
requires requires( Pow pow ) { { pow( 1.0, 1 ) } -> same_as<double>; }
{
unsigned nThreads = !dual ? 1 : 2;
atomic<double> t( 0.0 );
counting_semaphore semReady( 0 );
atomic_uint syncReady( nThreads );
atomic<double> aSum;
auto benchPow = [&]( Pow pow )
{
semReady.acquire();
if( syncReady.fetch_sub( 1, memory_order_relaxed ) != 1 )
while( syncReady.load( memory_order_relaxed ) );
double sum = 0.0;
auto start = high_resolution_clock::now();
for( size_t r = ROUNDS; r--; )
for( int e : exps )
sum += pow( 10.0, e );
t.fetch_add( (double)duration_cast<nanoseconds>(
high_resolution_clock::now() - start ).count() / ((double)N * ROUNDS),
memory_order_relaxed );
aSum.store( sum, memory_order_relaxed );
};
vector<jthread> threads;
threads.reserve( 2 );
for( unsigned t = nThreads; t--; )
threads.emplace_back( [&]() { benchPow( pow ); } ),
#if defined(_WIN32)
SetThreadAffinityMask( threads.back().native_handle(), !dual ? 1 :
(DWORD_PTR)1 << jthread::hardware_concurrency() / 2 );
#else
1;
#endif
semReady.release( nThreads );
threads.resize( 0 );
return t.load( memory_order_relaxed ) / (int)nThreads;
};
using pow_fn_t = double (*)( double, int );
auto
powTest = bind( threadBenchPow, placeholders::_1, (pow_fn_t)[]( double
d, int e ) { return pow( d, e ); } ),
powPowTest = bind( threadBenchPow, placeholders::_1, (pow_fn_t)powPow ),
cPowPowTest = bind( threadBenchPow, placeholders::_1, (pow_fn_t)cPowPow );
static struct test_t
{
char const *descr;
decltype(powTest) powFn;
} const tests[] =
{
{ "pow", powTest },
{ "powPow", powPowTest },
{ "cPowPow", cPowPowTest }
};
for( test_t const &test : tests )
{
cout << test.descr << endl;
bool dual = false;
double tSingle;
do
{
int nThreads = 1 + dual;
double t = test.powFn( dual ) / nThreads;
cout << "\t" << nThreads << ": " << t;
if( !dual )
tSingle = t,
cout << endl;
else
cout << " " << trunc( (tSingle / t * 100.0 - 100.0) * 100.0 ) /
100.0 << "%" << endl;
} while( dual = !dual );
}
}

double powPow( double d, int e )
{
auto run = [&]<bool Neg>( bool_constant<Neg> ) -> double
{
unsigned eAbs = !Neg ? e : -e;
double v = 1.0, sq = d;
for( ; eAbs; eAbs >>= 1, sq *= sq )
v *= (int)(eAbs & 1) * sq + (int)(eAbs & 1 ^ 1);
v = !Neg ? v : 1.0 / v;
return v;
};
if( e >= 0 )
return run( false_type() );
else
return run( true_type() );
}

double cPowPow( double d, int e )
{
unsigned eAbs = e >= 0 ? e : -e;
double v = 1.0, sq = d;
for( ; eAbs; eAbs >>= 1, sq *= sq )
v *= (int)(eAbs & 1) * sq + (int)(eAbs & 1 ^ 1);
v = e >= 0 ? v : 1.0 / v;
return v;
}

Marcel Mueller

unread,
Sep 9, 2022, 7:11:45 AM9/9/22
to
Am 07.09.22 um 14:15 schrieb Bonita Montero:
> In C++ gibt es ein pow( double, int ), das es meines Wissens so in C
> nicht gibt. Daher habe ich das mal nach-implementiert:

Ich glaube, du interpretierst da mehr rein als es ist.
C++ macht keinerlei abweichende Garantien bzgl. dieser Überladungen. Ab
C++11 wurde sie sogar durch eine generische Überladung ersetzt. Dabei
geht es aber vmtl. eher darum zusätzliche, komplexe Datentypen zu
unterstützen bzw. Mehrdeutigkeiten zu vermeiden, als irgendeine
Sonderlocke für Int-Exponenten zu unterstützen.

Da steht:
> If the base is finite negative and the exponent is finite but not an integer value, it causes a domain error.

Anders gesagt, negative Base funktioniert bei Int-Exponenten immer. Das
gilt für alle Überladungen. Und es gilt für C99 AFAIK genauso.

Das man gegenüber den Standardbibliotheken immer nochmal Performance
heraus quetschen kann, ist keine Überraschung. Aber ob das dann wirklich
alle Sonderfälle korrekt behandelt und die spezifizierte Präzision
liefert, steht nochmal auf einem anderen Blatt.


Marcel

Bonita Montero

unread,
Sep 9, 2022, 8:44:54 AM9/9/22
to
Am 09.09.2022 um 13:11 schrieb Marcel Mueller:

> Das man gegenüber den Standardbibliotheken immer nochmal Performance
> heraus quetschen kann, ist keine Überraschung. ...

Ne, das ist eine Überraschung,
denn i.d.R. sind die Standard-Bibltiotheken ziemlich ausgefuchst.
Ist nur an der Stelle noch keiner auf die Idee gekommen, sowas
wie ein floating point CMOV hier so indirekt zu implementieren
und somit einen nicht vorhersagbaren Sprung einzusparen.

Marcel Mueller

unread,
Sep 9, 2022, 10:27:59 AM9/9/22
to
Am 09.09.22 um 14:45 schrieb Bonita Montero:
> Am 09.09.2022 um 13:11 schrieb Marcel Mueller:
>
>> Das man gegenüber den Standardbibliotheken immer nochmal Performance
>> heraus quetschen kann, ist keine Überraschung. ...
>
> Ne, das ist eine Überraschung,
> denn i.d.R. sind die Standard-Bibltiotheken ziemlich ausgefuchst.

Stark unterschiedlich.
Häufig performancekritische Funktionen sind i.d.R. recht gut optimiert,
andere manchmal. Und dann gibt es halt noch das Problem, dass gerade die
Floating-Point-Funktionen recht krude Definitionen für diverse
Sonderfälle haben, so dass um die Built-In Instruktionen der
Architekturen immer noch einiges an Overhead gepackt werden muss. Das
Setzen von errno ist nur ein Aspekt davon.

Für reale Anwendungen sind diese Grenzfälle und Seiteneffekte oft, aber
eben nicht immer, irrelevant. Da kann man schon noch einmal etwas holen
(vor allem bedingte Sprünge).

> Ist nur an der Stelle noch keiner auf die Idee gekommen, sowas
> wie ein floating point CMOV hier so indirekt zu implementieren
> und somit einen nicht vorhersagbaren Sprung einzusparen.

Wenn er meistens genauso abbiegt, sollte das eigentlich auch der BTB
Cache abfangen.


Marcel

Bonita Montero

unread,
Sep 9, 2022, 11:07:23 AM9/9/22
to
Am 09.09.2022 um 16:27 schrieb Marcel Mueller:

> Stark unterschiedlich.
> Häufig performancekritische Funktionen sind i.d.R. recht gut optimiert,
> andere manchmal. Und dann gibt es halt noch das Problem, dass gerade
> die Floating-Point-Funktionen recht krude Definitionen für diverse
> Sonderfälle haben, so dass um die Built-In Instruktionen der
> Architekturen immer noch einiges an Overhead gepackt werden muss. Das
> Setzen von errno ist nur ein Aspekt davon.

Aha, was aus <math> setzt denn bitte errno ?

> Wenn er meistens genauso abbiegt, sollte das eigentlich auch der BTB
> Cache abfangen.

Der ist transparent und kann nicht abgefagt werden.


Stefan Reuther

unread,
Sep 9, 2022, 11:54:07 AM9/9/22
to
Am 09.09.2022 um 17:07 schrieb Bonita Montero:
> Am 09.09.2022 um 16:27 schrieb Marcel Mueller:
>> Stark unterschiedlich.
>> Häufig performancekritische Funktionen sind i.d.R. recht gut
>> optimiert, andere manchmal. Und dann gibt es halt noch das Problem,
>> dass gerade
>> die  Floating-Point-Funktionen recht krude Definitionen für diverse
>> Sonderfälle haben, so dass um die Built-In Instruktionen der
>> Architekturen immer noch einiges an Overhead gepackt werden muss. Das
>> Setzen von errno ist nur ein Aspekt davon.
>
> Aha, was aus <math> setzt denn bitte errno ?

Grundsätzlich alles (z.B. sin(1.0/0.0), exp(1e30)).

Praktisch würde ich mich nicht darauf verlassen, dass jeder Compiler das
in jedem Fall auch tut.


Stefan

Thomas Koenig

unread,
Sep 9, 2022, 1:07:38 PM9/9/22
to
Stefan Reuther <stefa...@arcor.de> schrieb:
errno kann die Performance ganz schön ausbremsen, ich würde
daher hoffen, dass jeder vernünftige Compiler eine Option hat,
das Setzen von errno abzuschalten.

Bonita Montero

unread,
Sep 9, 2022, 11:35:33 PM9/9/22
to
Am 09.09.2022 um 19:07 schrieb Thomas Koenig:

> errno kann die Performance ganz schön ausbremsen, ...

Wie denn ?
Das ist doch nur eine thread-lokale globale Variable.


Bonita Montero

unread,
Sep 9, 2022, 11:44:23 PM9/9/22
to
Am 09.09.2022 um 19:07 schrieb Thomas Koenig:

> errno kann die Performance ganz schön ausbremsen, ich würde
> daher hoffen, dass jeder vernünftige Compiler eine Option hat,
> das Setzen von errno abzuschalten.

Ich hab das Ganze mal gerade überprüft.

#include <iostream>
#include <chrono>
#include <errno.h>

using namespace std;
using namespace chrono;

int gSum = 0;

int main()
{
constexpr size_t ROUNDS = 1'000'000'000;
int sum = 0;
auto volatile &errNo = errno;
auto start = high_resolution_clock::now();
for( size_t r = ROUNDS; r--; )
sum += errNo;
gSum = sum;
double ns = (double)duration_cast<nanoseconds>(
high_resolution_clock::now() - start ).count() / ROUNDS;
cout << ns << endl;
}

Auf jeden Fall dauert das Abfragen von errno auf meinem Linux
-Rechner, einem Phenom II X4 945 (3GHz) ca. eine 2/3 Nanoskunde.

Bonita Montero

unread,
Sep 10, 2022, 12:14:16 AM9/10/22
to
Ich hab das Ganze jetzt mal mit Loop-Unrolling ge-benchmarkt.

#include <iostream>
#include <chrono>
#include <utility>
#include <errno.h>

using namespace std;
using namespace chrono;

int gSum = 0;

int main()
{
auto unroll = []<size_t ... Indices, typename Fn>(
index_sequence<Indices ...>, Fn fn ) { ((Indices, fn()), ...); };
constexpr size_t
UNROLL = 10,
ROUNDS = 1'000'000'000 / UNROLL;
int sum = 0;
auto volatile &errNo = errno;
auto start = high_resolution_clock::now();
for( size_t r = ROUNDS; r--; )
unroll( make_index_sequence<UNROLL>(), [&]() { sum += errNo; } );
gSum = sum;
double ns = (double)duration_cast<nanoseconds>(
high_resolution_clock::now() - start ).count() / ((double)ROUNDS * UNROLL);
cout << ns << endl;
}

Auf jeden Fall hat das Abfragen von errno jetzt nur noch einen
Takt Throughput, d.h. 1/3 Nanosekunde.

Stefan Reuther

unread,
Sep 10, 2022, 2:56:27 AM9/10/22
to
Am 10.09.2022 um 05:44 schrieb Bonita Montero:
> Am 09.09.2022 um 19:07 schrieb Thomas Koenig:
>> errno kann die Performance ganz schön ausbremsen, ich würde
>> daher hoffen, dass jeder vernünftige Compiler eine Option hat,
>> das Setzen von errno abzuschalten.
>
> Ich hab das Ganze mal gerade überprüft.

Nein, hast du nicht, denn du hast (a) das Auslesen von (b) einer
Speicheradresse in (c) einer Schleife mit (d) hier off-topic'nem
C++-Code geprüft, nicht (a) das Setzen (b) von errno (c) in einer
Mathe-Funktion in (d) C.

Denn damit wird aus

// ein Assemblerbefehl
return __builtin_fsin(d);

sowas wie

// viele Assemblerbefehle inkl. mindestens einem Sprung
if (some_condition(d)) {
*__get_thread_local_errno() = ERANGE;
return __builtin_nan();
} else {
return __builtin_fsin();
}

bzw. entscheidet sich der Compiler gleich dafür, dass ihm das zu komplex
wird, und generiert einen 'call' in die Library anstatt halt den einen
Assemblerbefehl zu verwenden.


Stefan

Bonita Montero

unread,
Sep 10, 2022, 5:28:30 AM9/10/22
to
> Nein, hast du nicht, denn du hast (a) das Auslesen von (b) einer
> Speicheradresse in (c) einer Schleife mit (d) hier off-topic'nem
> C++-Code geprüft, nicht (a) das Setzen (b) von errno (c) in einer
> Mathe-Funktion in (d) C.
>
> Denn damit wird aus
>
> // ein Assemblerbefehl
> return __builtin_fsin(d);
>
> sowas wie
>
> // viele Assemblerbefehle inkl. mindestens einem Sprung
> if (some_condition(d)) {
> *__get_thread_local_errno() = ERANGE;
> return __builtin_nan();
> } else {
> return __builtin_fsin();
> }

So wie Du dich zuerst ausdrücktest war das halt nur auf das Setzen von
errno bezogen. Aber sollte das wie oben aussehen, dann ist das an der
Stelle auch kein Problem, denn für Performance-relevante Fälle wird
errno eben nicht gesetzt und der Sprung korrekt vorhergesagt, wobei
der Regel-Fall, also da wo kein Fehler auftritt, direkt hinter der
Afrage kommen sollte damit der Decoder einen glatten Durchlauf macht
(in C++ kann man das mit [[likely]] oder [[unlikely]] anweisen). Von
daher weiß ich echt nicht was das Problem sein soll, denn das existiert
so wie von dir besagt eben nicht.
Und die Abfrage auf fehlerhafte Parameter ist wohl erst recht kein
Perfomance-Problem bei einer so Zeit-aufwendigen Operation wie sin();
kannst mir mal eine andere Operation nennen wo das aneilig ins Gewicht
fällt.

BTW: In meiner Schleife ist es auch egal ob errno gelesen oder geschrie-
ben wird; das ist auf modernen CPUs gleich schnell bzw. wenn Du das so
oft in Folge machst wie ich, dann stapeln sich die Operationen out of
order Takt-weise in der Load- / Store-Queue. Auch ist das eigentlich
nicht off-topic, denn der disktuierte Teil, also das Setzen von errno,
ist ja nicht C++-spezifisch.


Bonita Montero

unread,
Sep 10, 2022, 5:44:15 AM9/10/22
to
Am 10.09.2022 um 08:54 schrieb Stefan Reuther:

> Denn damit wird aus
>
> // ein Assemblerbefehl
> return __builtin_fsin(d);
>
> sowas wie
>
> // viele Assemblerbefehle inkl. mindestens einem Sprung
> if (some_condition(d)) {
> *__get_thread_local_errno() = ERANGE;
> return __builtin_nan();
> } else {
> return __builtin_fsin();
> }

Wenn ich das __builtin_sin mache, dann macht das einfach "call sin@PLT"
und bastelt keinen Code drumherum, selbst wenn ich x87-Arithmetik beim
Compiler anfordere. Und das wird errno sicher nicht per Aufruf setzen.
D.h. Du hast zu viel Phantasie.

Marcel Mueller

unread,
Sep 11, 2022, 11:32:59 AM9/11/22
to
Am 10.09.22 um 11:44 schrieb Bonita Montero:
> Wenn ich das __builtin_sin mache, dann macht das einfach "call sin@PLT"
> und bastelt keinen Code drumherum, selbst wenn ich x87-Arithmetik beim
> Compiler anfordere. Und das wird errno sicher nicht per Aufruf setzen.
> D.h. Du hast zu viel Phantasie.

Versuch's nochmal mit -fno-math-errno. ;-)


Marcel

Bonita Montero

unread,
Sep 11, 2022, 12:34:44 PM9/11/22
to
Es ging Stefan darum, dass wenn man das Intrinsic __builtin_sin nutzt
die Sinus-Funktion inline ist, meiner Meinung nach ggf. halt durch eine
entsprechende x87CPU-Instruktion, und Stefans Meinung nach ist das ganze
Error-Handling wieder Teil irgendwelcher Library-Calls, was dann ent-
sprechend langsam wäre.
Ich hab halt gezeigt, dass selbst wenn ich dem Compiler sage, dass x87
-Arithmetik genutz werden soll einfach nur ein blanker Libary -Call für
die Sinus-Operation gemacht wird. Und der wird sicher nicht selbst errno
per Libary-Call schreiben.
Die Sache ist einfach: Helmut war ja letztens mal der Meinung, dass die
transzendenten x87-Operationenen Fantastilliarden mal schneller seien
als das man das selbst implementieren könnte. Ich hab halt gezeigt, dass
das nicht so ist bzw. die Libary-Implementationen die auf SSE aufsetzen
sogar ein Vielfaches schneller sind. Dementsprechend ist es hier im
diskutierten Zusammenhang nachvollziehbar, dass __builtin_sin gar keine
entsprechende transzendente CPU-Instruktionen nutz, sondern einfach nur
einen Libary-Call macht, denn der ist ja auf modernen CPUs effizienter
implementierbar.
Ein anderer Punkt war eben, dass es eigentlich egal ist, ob errno direkt
gesetzt wird oder über meinentwegen zigfach verschachtelte Libary Calls,
denn diese Fehler-Situation wird ja i.d.R. übersprungen und der entspre-
chende Sprung nahezu immer korrekt vorhergesagt.


Stefan Reuther

unread,
Sep 12, 2022, 12:40:26 PM9/12/22
to
Am 11.09.2022 um 18:34 schrieb Bonita Montero:
> Am 11.09.2022 um 17:32 schrieb Marcel Mueller:
>> Am 10.09.22 um 11:44 schrieb Bonita Montero:
>>> Wenn ich das __builtin_sin mache, dann macht das einfach "call sin@PLT"
>>> und bastelt keinen Code drumherum, selbst wenn ich x87-Arithmetik beim
>>> Compiler anfordere. Und das wird errno sicher nicht per Aufruf setzen.
>>> D.h. Du hast zu viel Phantasie.
>>
>> Versuch's nochmal mit -fno-math-errno. ;-)
>
> Es ging Stefan darum, dass wenn man das Intrinsic __builtin_sin nutzt
> die Sinus-Funktion inline ist,

Nein, war er nicht.

Stefan hat angenommen, dass die Abstraktion von "der Pseudocode
'__builtin_fsin'" zu "die Assemblerinstruktion 'fsin'" gelingt, da den
realen Assemblercode zu zitieren doch recht sperrig gewesen wäre.

Der Punkt ist: Code wie 'sin(x)' generiert eben NICHT nur eine
Assemblerinstruktion, sondern wenigstens die Prüfung einer Bedingung
(was einige Instruktionen, darunter mindestens ein Load einer Konstanten
und einen bedingten Sprung kostet) sowie den Zugriff auf eine
thread-lokale Variable (der im Gegensatz zu deinem Mess-Aufbau auch
nicht aus der Schleife optimiert wird). Und das optimiert weder ein
Branch-Predictor noch ein Cache komplett weg.

*Natürlich* kann man dann ein paar Faktoren schneller sein, wenn man
eine eigene Version der Funktion ohne diese Prüfungen baut.


Stefan

Thomas Koenig

unread,
Sep 12, 2022, 3:18:39 PM9/12/22
to
Stefan Reuther <stefa...@arcor.de> schrieb:
> Am 11.09.2022 um 18:34 schrieb Bonita Montero:
>> Am 11.09.2022 um 17:32 schrieb Marcel Mueller:
>>> Am 10.09.22 um 11:44 schrieb Bonita Montero:
>>>> Wenn ich das __builtin_sin mache, dann macht das einfach "call sin@PLT"
>>>> und bastelt keinen Code drumherum, selbst wenn ich x87-Arithmetik beim
>>>> Compiler anfordere. Und das wird errno sicher nicht per Aufruf setzen.
>>>> D.h. Du hast zu viel Phantasie.
>>>
>>> Versuch's nochmal mit -fno-math-errno. ;-)
>>
>> Es ging Stefan darum, dass wenn man das Intrinsic __builtin_sin nutzt
>> die Sinus-Funktion inline ist,
>
> Nein, war er nicht.
>
> Stefan hat angenommen, dass die Abstraktion von "der Pseudocode
> '__builtin_fsin'" zu "die Assemblerinstruktion 'fsin'" gelingt, da den
> realen Assemblercode zu zitieren doch recht sperrig gewesen wäre.

fsin gibt es aus gutem Grund nur bei x87-Instruktionen. Das ist
eine richtig aufwändige Instruktion, auf Zen1 z.B. hat sie zwischen
50 und 170 Zyklen latency auf Zen1. Der SSE-Befehlssatz hat keine
transzendenten Funktionen mehr.

> Der Punkt ist: Code wie 'sin(x)' generiert eben NICHT nur eine
> Assemblerinstruktion,

Könnte er schon (zumindest wenn man eine Programmiersprache verwendet,
in der errno nicht verwendet wird). -fno-math-errno existiert
aus einem Grund... (und ich lese gerade in der gcc-Doku, dass auf
Darwin errno gar nicht gesetzt wird. Bisher haben keine Horden
von C-Programmierern das Apple-Hauptquartier in Cupertino gestürmt,
daher gehe ich davon aus, dass das stillschweigend geduldet wird).

Stefan Kanthak

unread,
Sep 12, 2022, 5:53:17 PM9/12/22
to
"Thomas Koenig" <tko...@netcologne.de> schrieb, noch immer ahnungslos:
> Stefan Reuther <stefa...@arcor.de> schrieb:

>> Der Punkt ist: Code wie 'sin(x)' generiert eben NICHT nur eine
>> Assemblerinstruktion,
>
> Könnte er schon (zumindest wenn man eine Programmiersprache verwendet,
> in der errno nicht verwendet wird).

AUTSCH: FSIN ist dummerweise nur fuer Argumente kleiner 0x1p63 und
groesser -0x1p63 definiert; Argumente (ungleich NAN, +INF und -INF)
ausserhalb dieses Wertebereichs muessen reduziert werden.

> -fno-math-errno existiert aus einem Grund... (und ich lese gerade in
> der gcc-Doku, dass auf Darwin errno gar nicht gesetzt wird.

Ach? Wieso liest Du nicht im C-Standard nach?
Dort koenntest Du die von einer standardkonformen Implementierung der
Sprache C zu erfuellenden Anforderungen durch Suche nach
math_errhandling, MATH_ERREXCEPT und MATH_ERRNO finden.

> Bisher haben keine Horden von C-Programmierern das Apple-Hauptquartier
> in Cupertino gestürmt, daher gehe ich davon aus, dass das stillschweigend
> geduldet wird).

Besser kannst Du Deine Ahnungslosigkeit nicht demonstrieren!

Stefan
--
<https://www.duden.de/rechtschreibung/Kanthaken>

Thomas Koenig

unread,
Sep 13, 2022, 1:00:32 AM9/13/22
to
Stefan Kanthak <postmaster@[127.0.0.1]> schrieb:
> "Thomas Koenig" <tko...@netcologne.de> schrieb, noch immer ahnungslos:
>> Stefan Reuther <stefa...@arcor.de> schrieb:
>
>>> Der Punkt ist: Code wie 'sin(x)' generiert eben NICHT nur eine
>>> Assemblerinstruktion,
>>
>> Könnte er schon (zumindest wenn man eine Programmiersprache verwendet,
>> in der errno nicht verwendet wird).
>
> AUTSCH: FSIN ist dummerweise nur fuer Argumente kleiner 0x1p63 und
> groesser -0x1p63 definiert; Argumente (ungleich NAN, +INF und -INF)
> ausserhalb dieses Wertebereichs muessen reduziert werden.
>
>> -fno-math-errno existiert aus einem Grund... (und ich lese gerade in
>> der gcc-Doku, dass auf Darwin errno gar nicht gesetzt wird.
>
> Ach? Wieso liest Du nicht im C-Standard nach?
> Dort koenntest Du die von einer standardkonformen Implementierung der
> Sprache C zu erfuellenden Anforderungen durch Suche nach
> math_errhandling, MATH_ERREXCEPT und MATH_ERRNO finden.

Und Darwin scheint keine konforme Implemntierung zu haben.

>
>> Bisher haben keine Horden von C-Programmierern das Apple-Hauptquartier
>> in Cupertino gestürmt, daher gehe ich davon aus, dass das stillschweigend
>> geduldet wird).
>
> Besser kannst Du Deine Ahnungslosigkeit nicht demonstrieren!

Es gibt einen Grund, warum ich nur selten "slrn -k" verwende.
Vielen Dank für die Erinnerung.

Stefan Kanthak

unread,
Sep 13, 2022, 4:03:44 AM9/13/22
to
"Thomas Koenig" <tko...@netcologne.de> dummschwallte:

> Stefan Kanthak <postmaster@[127.0.0.1]> schrieb:
>> "Thomas Koenig" <tko...@netcologne.de> schrieb, noch immer ahnungslos:

>>> -fno-math-errno existiert aus einem Grund... (und ich lese gerade in
>>> der gcc-Doku, dass auf Darwin errno gar nicht gesetzt wird.
>>
>> Ach? Wieso liest Du nicht im C-Standard nach?
>> Dort koenntest Du die von einer standardkonformen Implementierung der
>> Sprache C zu erfuellenden Anforderungen durch Suche nach
>> math_errhandling, MATH_ERREXCEPT und MATH_ERRNO finden.
>
> Und Darwin scheint keine konforme Implemntierung zu haben.

Dummerweise muss eine standard-konforme Implementierung errno NICHT setzen.

JFTR: liefert der Hersteller von Darwin GCC mit diesem System?

>>> Bisher haben keine Horden von C-Programmierern das Apple-Hauptquartier
>>> in Cupertino gestürmt, daher gehe ich davon aus, dass das stillschweigend
>>> geduldet wird).
>>
>> Besser kannst Du Deine Ahnungslosigkeit nicht demonstrieren!
>
> Es gibt einen Grund, warum ich nur selten "slrn -k" verwende.
> Vielen Dank für die Erinnerung.

Wie unschoen, dass Du wieder einmal GAR KEINE Argumente hast!

wehret den Dumpfbacken
Stefan
--
<https://www.duden.de/rechtschreibung/Kanthaken>

Bonita Montero

unread,
Sep 13, 2022, 6:02:05 AM9/13/22
to
Am 12.09.2022 um 18:16 schrieb Stefan Reuther:

> Stefan hat angenommen, dass die Abstraktion von "der Pseudocode
> '__builtin_fsin'" zu "die Assemblerinstruktion 'fsin'" gelingt, da den
> realen Assemblercode zu zitieren doch recht sperrig gewesen wäre.


Da stand:

> // viele Assemblerbefehle inkl. mindestens einem Sprung

D.h. Du bist davon ausgegangen, dass das Wesentliche inline ist. Die
Sache ist aber, dass man das nur tut wenn der ganze Krempel irgendwie
von der Größe her passt; dass macht man nicht wenn man nicht sowas wie
FSIN nutzt, denn die ganze Komplexität von sin() ohne fsin inline in
den "aufrufenden" Code zu packen wär etwas viel.
Insofern ist mein abgeleiteter Gedanke, dass Du FSIN gemeint hast
recht naheliegend.

> Der Punkt ist: Code wie 'sin(x)' generiert eben NICHT nur eine
> Assemblerinstruktion, sondern wenigstens die Prüfung einer Bedingung
> (was einige Instruktionen, darunter mindestens ein Load einer Konstanten
> und einen bedingten Sprung kostet) sowie den Zugriff auf eine
> thread-lokale Variable (der im Gegensatz zu deinem Mess-Aufbau auch
> nicht aus der Schleife optimiert wird). Und das optimiert weder ein
> Branch-Predictor noch ein Cache komplett weg.

Heute tut sin() das sicher nicht, früher wäre das wirklich zu inlinen
gewesen. Und unter obiger Annahme passt das ja in den Kontext. Und
irgendwelches Error-Handling hängt bei den FPU-Varianten am Parity
Bit der Status-Flags, und das fragst Du einfach mit einem bedingten
Sprung ab, und da der Fehlerfall anteilig so gut wie nie auftritt
kann man sicher sagen, dass solche Sprünge so gut wie immer korrekt
vorhergesagt werden.

Bonita Montero

unread,
Sep 13, 2022, 6:04:24 AM9/13/22
to
Am 12.09.2022 um 21:18 schrieb Thomas Koenig:

> Könnte er schon (zumindest wenn man eine Programmiersprache verwendet,
> in der errno nicht verwendet wird). -fno-math-errno existiert
> aus einem Grund... (und ich lese gerade in der gcc-Doku, dass auf
> Darwin errno gar nicht gesetzt wird. Bisher haben keine Horden
> von C-Programmierern das Apple-Hauptquartier in Cupertino gestürmt,
> daher gehe ich davon aus, dass das stillschweigend geduldet wird).

Eigentlich benötigt man das errno an der Stelle nicht wirklich, denn
wenn das Ergbnis NaN ist ist das eigentlich ausreichend. NaN ist simpel
abgefragt, einfach mit x == x, denn nur NaN ist ungleich sich selbst,
+/-Unendlich nicht.


Bonita Montero

unread,
Sep 13, 2022, 6:06:27 AM9/13/22
to
Du bist echt zum fremd-schämen.


Stefan Reuther

unread,
Sep 13, 2022, 12:27:59 PM9/13/22
to
Am 12.09.2022 um 23:41 schrieb Stefan Kanthak:
> "Thomas Koenig" <tko...@netcologne.de> schrieb, noch immer ahnungslos:
>> Stefan Reuther <stefa...@arcor.de> schrieb:
>>> Der Punkt ist: Code wie 'sin(x)' generiert eben NICHT nur eine
>>> Assemblerinstruktion,
>>
>> Könnte er schon (zumindest wenn man eine Programmiersprache verwendet,
>> in der errno nicht verwendet wird).

Zum Beispiel Pascal mit dem fpc-Compiler.

<https://gcc.godbolt.org/z/Y695MGx8d>

> AUTSCH: FSIN ist dummerweise nur fuer Argumente kleiner 0x1p63 und
> groesser -0x1p63 definiert; Argumente (ungleich NAN, +INF und -INF)
> ausserhalb dieses Wertebereichs muessen reduziert werden.

Der gcc hat zumindest im Prinzip eine Option für "das ist mir egal"
namens -ffast-math.

>> -fno-math-errno existiert aus einem Grund... (und ich lese gerade in
>> der gcc-Doku, dass auf Darwin errno gar nicht gesetzt wird.
>
> Ach? Wieso liest Du nicht im C-Standard nach?
> Dort koenntest Du die von einer standardkonformen Implementierung der
> Sprache C zu erfuellenden Anforderungen durch Suche nach
> math_errhandling, MATH_ERREXCEPT und MATH_ERRNO finden.

Für diesen Tonfall hab ich jetzt keine Lust, den C-Standard auszugraben,
aber die Single Unix Specification meint mit Verweis darauf dazu in
<https://pubs.opengroup.org/onlinepubs/9699919799/functions/sin.html>,
dass unter bestimmten Bedingungen ein "domain error shall occur", was
halt den ganzen Rattenschwanz mit errno nach sich zieht.

Eine der Bedingungen ist __STDC_IEC_559__, was gcc unter bestimmten
Bedingungen zu definieren scheint, ob unter Darwin, weiß ich nicht.


Stefan

Thomas Koenig

unread,
Sep 13, 2022, 1:47:43 PM9/13/22
to
Stefan Reuther <stefa...@arcor.de> schrieb:
> Am 12.09.2022 um 23:41 schrieb Stefan Kanthak:
>> "Thomas Koenig" <tko...@netcologne.de> schrieb, noch immer ahnungslos:
>>> Stefan Reuther <stefa...@arcor.de> schrieb:
>>>> Der Punkt ist: Code wie 'sin(x)' generiert eben NICHT nur eine
>>>> Assemblerinstruktion,
>>>
>>> Könnte er schon (zumindest wenn man eine Programmiersprache verwendet,
>>> in der errno nicht verwendet wird).
>
> Zum Beispiel Pascal mit dem fpc-Compiler.
>
><https://gcc.godbolt.org/z/Y695MGx8d>
>
>> AUTSCH: FSIN ist dummerweise nur fuer Argumente kleiner 0x1p63 und
>> groesser -0x1p63 definiert; Argumente (ungleich NAN, +INF und -INF)
>> ausserhalb dieses Wertebereichs muessen reduziert werden.
>
> Der gcc hat zumindest im Prinzip eine Option für "das ist mir egal"
> namens -ffast-math.

Die ist allerdings mit Vorsicht zu genießen, weil die ohne
Berücksichtigung irgendwelcher Rundungs- oder sonstiger Fehler
oder Statementgrenzen eifrig Formeln unordnet.

Ein bekannter Effekt ist, dass die "Kahan summation", bei
der mit großer Vorsicht eine höhere Genauigkeit mittels
vorhandener Fließkommaarithmetik implementiert wird, wieder
auf das ganz normale Aufsummieren zusammen"optimiert" wird.
Siehe https://en.wikipedia.org/wiki/Kahan_summation_algorithm .

Man kann die Option nehmen, aber man sollte schon wissen, was
man damit vielleicht anrichtet. Häufig geht das, manchmal
aber halt fällt man damit auf die Nase.
0 new messages