[PATCH] Add math-inline benchmark
Ondřej Bílka
neleai@seznam.cz
Fri Jul 10 18:13:00 GMT 2015
On Fri, Jul 10, 2015 at 05:09:16PM +0100, Wilco Dijkstra wrote:
> > OndÅej BÃlka wrote:
> > On Mon, Jul 06, 2015 at 03:50:11PM +0100, Wilco Dijkstra wrote:
> > >
> > >
> > > > OndÅej BÃlka wrote:
> > > > But with latency hiding by using argument first suddenly even isnan and
> > > > isnormal become regression.
> > > >
>
> That doesn't look correct - it looks like this didn't use the built-ins at all,
> did you forget to apply that patch?
>
No, from what you wrote I expected that patch already tests builtins
which doesn't. Applied patch and got different results. When I added
patch results are similar.
> Anyway I received a new machine so now GLIBC finally builds for x64. Since
> there appear large variations from run to run I repeat the same tests 4 times
> by copying the FOR_EACH_IMPL loop. The first 1 or 2 are bad, the last 2
> converge to useable results. So I suspect frequency scaling is an issue here.
>
> Without the sin(tmp) part I get:
>
> remainder_test2_t: 40786.9 192862
> remainder_test1_t: 43008.2 196311
> __fpclassify_test2_t: 2856.56 3020.12
> __fpclassify_test1_t: 3043.53 3135.89
> __fpclassify_t: 12500.6 10152.5
> fpclassify_t: 2972.54 3047.65
> __isnormal_inl2_t: 4619.55 14491.1
> __isnormal_inl_t: 12896.3 10306.7
> isnormal_t: 4254.42 3667.87
> __finite_inl_t: 3979.58 3991.6
> __finite_t: 7039.61 7039.37
> isfinite_t: 2992.65 2969.25
> __isinf_inl_t: 2852.1 3239.23
> __isinf_t: 8991.81 8813.44
> isinf_t: 3241.75 3241.54
> __isnan_inl_t: 4003.51 3977.73
> __isnan_t: 7054.54 7054.5
> isnan_t: 2819.66 2801.94
>
> And with the sin() addition:
>
> remainder_test2_t: 105093 214635
> remainder_test1_t: 106635 218012
> __fpclassify_test2_t: 64290.9 32116.6
> __fpclassify_test1_t: 64365.1 32310.2
> __fpclassify_t: 72006.1 41607
> fpclassify_t: 64190.3 33450.1
> __isnormal_inl2_t: 65959.1 33672
> __isnormal_inl_t: 71875.7 41727.3
> isnormal_t: 65676.1 32826.1
> __finite_inl_t: 69600.6 35293.3
> __finite_t: 67653.8 38627.2
> isfinite_t: 64435.9 34904.9
> __isinf_inl_t: 68556.6 33176
> __isinf_t: 69066.4 39562.7
> isinf_t: 64755.5 34244.6
> __isnan_inl_t: 69577.3 34776.2
> __isnan_t: 67538.8 38321.3
> isnan_t: 63963 33276.6
>
> The remainder test is basically math/w_remainder.c adapted to use __isinf_inl
> and __isnan_inl (test1) or the isinf/isnan built-ins (test2).
>
Which still doesn't have to mean anything, only if you test a
application that frequently uses these you will get result without
doubt.
Here a simple modification produces different results. One of many
objections is that by simply adding gcc will try to make branchless code
like converting that to res += 5 * (isnan(tmp)). So use more difficult
branch and and with following two I get __builtin_isinf lot slower.
{ double tmp = p[i] * 2.0; \
res += 3 * sin (tmp); if (func (tmp)) res += 3* sin (2 * tmp) ;} \
{ double tmp = p[i] * 2.0; \
if (func (tmp)) res += 3 * sin (2 * tmp) ;} \
__fpclassify_test2_t: 4645.32 34549.8
__fpclassify_test1_t: 4858.35 35682
__fpclassify_t: 12959.6 9402.96
fpclassify_t: 6163.18 3573.92
__isnormal_inl2_t: 72463.8 14535.8
__isnormal_inl_t: 78154.4 10665.7
isnormal_t: 72172.7 3978.68
__finite_inl_t: 69525.5 2048.91
__finite_t: 75383.9 10260.5
isfinite_t: 71913.3 3449.38
__isinf_inl_t: 2279.96 19166.1
__isinf_t: 10462.3 32308.5
isinf_t: 3706.35 22804.2
__isnan_inl_t: 2376.56 20769.1
__isnan_t: 9558.7 23683.9
isnan_t: 2025.91 19824.9
and
__fpclassify_test2_t: 69720.7 70690
__fpclassify_test1_t: 69575.2 57032
__fpclassify_t: 12839.1 9460.82
fpclassify_t: 6136.18 3355.79
__isnormal_inl2_t: 133754 34673.9
__isnormal_inl_t: 137851 41468.9
isnormal_t: 133421 37423.4
__finite_inl_t: 131153 34229.9
__finite_t: 135990 39609.5
isfinite_t: 132679 34604.8
__isinf_inl_t: 66901.3 44636.5
__isinf_t: 74330.2 53375.7
isinf_t: 68432.6 45350.5
__isnan_inl_t: 67125.3 43663.6
__isnan_t: 71864.2 49792.7
isnan_t: 67107.9 42478.3
> >From this it seems that __isinf_inl is slightly better than the builtin, but
> it does not show up as a regression when combined with sin or in the remainder
> test.
>
That doesn't hold generaly as remainder test it could be just caused by
isnan being slower than isinf.
> So I don't see any potential regression here on x64 - in fact it looks like
> inlining using the built-ins gives quite good speedups across the board. And
> besides inlining applications using GLIBC it also inlines a lot of callsites
> within GLIBC that weren't previously inlined.
>
That doesn't mean anything as this is still poor benchmark. It only uses
tight loop which doesn't measure cost of loading constants for example.
> > > Basically GCC does the array read and multiply twice just like you told it
> > > to (remember this is not using -ffast-math). Also avoid adding unnecessary
> > > FP operations and conversions (which may interact badly with timing the
> > > code we're trying to test).
> > >
> > And how do you know that most users don't use fp conversions in their
> > code just before isinf? These interactions make benchtests worthless as
> > in practice a different variant would be faster than one that you
> > measure.
>
> You always get such interactions, it's unavoidable. That's why I added some
> actual math code that uses isinf/isnan to see how it performs in real life.
>
> > > For me the fixed version still shows the expected answer: the built-ins are
> > > either faster or as fast as the inlines. So I don't think there is any
> > > regression here (remember also that previously there were no inlines at all
> > > except for a few inside GLIBC, so the real speedup is much larger).
> >
> > Thats arm only. So it looks that we need platform-specific headers and testing.
>
> Well I just confirmed the same gains apply to x64.
>
No, that doesn't confirm anything yet. You need to do more extensive
testing to get somewhat reliable answer and still you won't be sure.
I asked you to run on arm my benchmark to measure results of inlining.
I attached again version. You should run it to see how results will differ.
-------------- next part --------------
test()
{
gcc -O3 -DBRANCHED -DI1=$1 -DI2=$2 -DT$3 ft.c 2>/dev/null
echo branched
time ./a.out
gcc -O3 -DI1=$1 -DI2=$2 -DT$3 ft.c 2>/dev/null
echo builtin
time ./a.out
}
test2()
{
echo conditional add
test $1 $2 1
echo branch
test $1 $2 2
echo sum
test $1 $2 3
}
echo "don't inline"
test2 '__attribute__((noinline))' '__attribute__((noinline))'
echo inline outer call
test2 '__attribute__((noinline))' '__attribute__((always_inline))'
echo inline inner call
test2 '__attribute__((always_inline))' '__attribute__((noinline))'
echo tigth loop
test2 '__attribute__((always_inline))' '__attribute__((always_inline))'
-------------- next part --------------
#include <stdlib.h>
#include <stdio.h>
//#include <math.h>
#undef isinf2
#ifdef BRANCHED
int I2
isinf2 (double dx)
{
union u {
double d;
long l;
};
volatile union u u;
u.d = dx;
long x = u.l;
return 2 * x == 0xffe0000000000000 ? (x == 0x7ff0000000000000 ? 1 :
-1) : 0;
}
#else
int I2
isinf2(double x)
{
return __builtin_isinf (x);
}
#endif
void I1
t1(double x,double *d)
{
if (isinf2 (x))
*d+=42;
}
void I1
t2(double x,double *d)
{
if (isinf2 (x))
abort();
}
void I1
t3(double x,double *d)
{
*d += isinf2 (x);
}
int main()
{
double ret= 0.0;
int i, j;
double *d = malloc (800000);
for (i=0;i<1000;i++)
d[i]=4.2;//(rand() % 2) ? 0.0 : (rand() % 2 ? 1.3 : 1.0/0.0);
for (j=0; j<300000; j++)
#ifdef T1
for (i=0; i<1000; i++)
t1(d[i]+1.3, &ret);
#endif
#ifdef T2
for (i=0; i<1000; i++)
t2(d[i]+1.3, &ret);
#endif
#ifdef T3
for (i=0; i<1000; i++)
t3(d[i]+1.3, &ret);
#endif
return ret;
}
More information about the Libc-alpha
mailing list