diff mbox

[v3] Add math-inline benchmark

Message ID 001c01d0f094$1fafb7e0$5f0f27a0$@com
State New
Headers show

Commit Message

Wilco Sept. 16, 2015, 3:26 p.m. UTC
> Siddhesh Poyarekar wrote:
> Sorry, I assumed someone else (Carlos?) was reviewing this and then
> forgot about it.  I've put review comments inline.  The major concern I
> have is that you're duplicating contents of math_private.h to compare
> it against the gcc builtins, which is fine as a first hack if you want
> to avoid changing actual code for the tests, but may not be a good idea
> in the long term.  That is, there is a risk that the inlines may be
> modified but this test doesn't realize that.
> 
> I don't mind this going in as a first revision (with other changes
> recommended below) provided you're willing to remedy this situation in
> the next iteration so that there is at least a failure in running the
> benchtest if the definitions of the inlines change in any way.

I previously posted a patch to enable inlines in math.h which replace the
GLIBC internal inlines. I also posted a patch to rename all uses of
__isinf_ns to isinf so they also use the new inline. I am going to post a 
patch remove all the math_private inlines as they are never used after
these changes.

So these functions are not duplicated, and it is necessary to add a copy
to benchmark the old implementation. Ie. the only duplication is 
EXTRACT_WORDS64, which seems fairly minor.

I've updated my patch with your other comments, see attached.

Wilco
---
 benchtests/Makefile             |   6 +-
 benchtests/bench-math-inlines.c | 285 ++++++++++++++++++++++++++++++++++++++++
 benchtests/bench-skeleton.c     |  18 +--
 benchtests/bench-util.c         |  34 +++++
 benchtests/bench-util.h         |  28 ++++
 5 files changed, 355 insertions(+), 16 deletions(-)
 create mode 100644 benchtests/bench-math-inlines.c
 create mode 100644 benchtests/bench-util.c
 create mode 100644 benchtests/bench-util.h

Comments

Siddhesh Poyarekar Sept. 17, 2015, 5:29 a.m. UTC | #1
On Wed, 2015-09-16 at 16:26 +0100, Wilco Dijkstra wrote:
> I previously posted a patch to enable inlines in math.h which replace
> the
> GLIBC internal inlines. I also posted a patch to rename all uses of
> __isinf_ns to isinf so they also use the new inline. I am going to
> post a 
> patch remove all the math_private inlines as they are never used
> after
> these changes.
> 
> So these functions are not duplicated, and it is necessary to add a
> copy
> to benchmark the old implementation. Ie. the only duplication is 
> EXTRACT_WORDS64, which seems fairly minor.
> 
> I've updated my patch with your other comments, see attached.

Looks good to me.

Thanks,
Siddhesh
Joseph Myers Sept. 18, 2015, 4:10 p.m. UTC | #2
Now the benchmark and the consequent use of inlines have been checked in, 
please close the relevant bugs as FIXED (setting the milestone to 2.23) 
and retest / ping / repost any other related patches that haven't been 
reviewed and are now unblocked.
Joseph Myers Sept. 18, 2015, 4:13 p.m. UTC | #3
Oh, and for GCC 6 and later __builtin_signbit is type-generic (postdating 
the start of your work on these issues), so allowing a simpler definition 
of the signbit macro in that case.
Joseph Myers Sept. 18, 2015, 5:17 p.m. UTC | #4
The inlines appear to cause i386 test failures from excess range and 
precision issues, where previously calls to __finite from the wrappers had 
the effect of hiding those issues.  I've files bugs 18980 and 18981 for 
those problems and will look at fixing them.
diff mbox

Patch

diff --git a/benchtests/Makefile b/benchtests/Makefile
index 8e615e5..cb62099 100644
--- a/benchtests/Makefile
+++ b/benchtests/Makefile
@@ -50,7 +50,10 @@  stdlib-bench := strtod
 
 stdio-common-bench := sprintf
 
-benchset := $(string-bench-all) $(stdlib-bench) $(stdio-common-bench)
+math-benchset := math-inlines
+
+benchset := $(string-bench-all) $(stdlib-bench) $(stdio-common-bench) \
+	    $(math-benchset)
 
 CFLAGS-bench-ffs.c += -fno-builtin
 CFLAGS-bench-ffsll.c += -fno-builtin
@@ -58,6 +61,7 @@  CFLAGS-bench-ffsll.c += -fno-builtin
 bench-malloc := malloc-thread
 
 $(addprefix $(objpfx)bench-,$(bench-math)): $(libm)
+$(addprefix $(objpfx)bench-,$(math-benchset)): $(libm)
 $(addprefix $(objpfx)bench-,$(bench-pthread)): $(shared-thread-library)
 $(objpfx)bench-malloc-thread: $(shared-thread-library)
 
diff --git a/benchtests/bench-math-inlines.c b/benchtests/bench-math-inlines.c
new file mode 100644
index 0000000..a70fb33
--- /dev/null
+++ b/benchtests/bench-math-inlines.c
@@ -0,0 +1,285 @@ 
+/* Measure math inline functions.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+#define SIZE 1024
+#define TEST_MAIN
+#define TEST_NAME "math-inlines"
+#define TEST_FUNCTION test_main ()
+#include "bench-timing.h"
+#include "json-lib.h"
+#include "bench-util.h"
+
+#include <stdlib.h>
+#include <math.h>
+#include <stdint.h>
+
+#define BOOLTEST(func)					  \
+static int __attribute__((noinline))			  \
+func ## _f (double d, int i)				  \
+{							  \
+  if (func (d))						  \
+    return (int) d + i;					  \
+  else							  \
+    return 5;						  \
+}							  \
+static int						  \
+func ## _t (volatile double *p, size_t n, size_t iters)   \
+{							  \
+  int i, j;						  \
+  int res = 0;						  \
+  for (j = 0; j < iters; j++)				  \
+    for (i = 0; i < n; i++)				  \
+      if (func ## _f (p[i] * 2.0, i) != 0)		  \
+	res += 5;					  \
+  return res;						  \
+}
+
+#define VALUETEST(func)					  \
+static int __attribute__((noinline))			  \
+func ## _f (double d)					  \
+{							  \
+  return func (d);					  \
+}							  \
+static int						  \
+func ## _t (volatile double *p, size_t n, size_t iters)	  \
+{							  \
+  int i, j;						  \
+  int res = 0;						  \
+  for (j = 0; j < iters; j++)				  \
+    for (i = 0; i < n; i++)				  \
+      res += func ## _f (p[i] * 2.0);			  \
+  return res;						  \
+}
+
+typedef union
+{
+  double value;
+  uint64_t word;
+} ieee_double_shape_type;
+
+#define EXTRACT_WORDS64(i,d)				  \
+do {							  \
+  ieee_double_shape_type gh_u;				  \
+  gh_u.value = (d);					  \
+  (i) = gh_u.word;					  \
+} while (0)
+
+/* Inlines similar to existing math_private.h versions.  */
+
+static __always_inline int
+__isnan_inl (double d)
+{
+  uint64_t di;
+  EXTRACT_WORDS64 (di, d);
+  return (di & 0x7fffffffffffffffull) > 0x7ff0000000000000ull;
+}
+
+static __always_inline int
+__isinf_ns2 (double d)
+{
+  uint64_t di;
+  EXTRACT_WORDS64 (di, d);
+  return (di & 0x7fffffffffffffffull) == 0x7ff0000000000000ull;
+}
+
+static __always_inline int
+__finite_inl (double d)
+{
+  uint64_t di;
+  EXTRACT_WORDS64 (di, d);
+  return (di & 0x7fffffffffffffffull) < 0x7ff0000000000000ull;
+}
+
+#define __isnormal_inl(X) (__fpclassify (X) == FP_NORMAL)
+
+/* Inlines for the builtin functions.  */
+
+#define __isnan_builtin(X) __builtin_isnan (X)
+#define __isinf_ns_builtin(X) __builtin_isinf (X)
+#define __isinf_builtin(X) __builtin_isinf_sign (X)
+#define __isfinite_builtin(X) __builtin_isfinite (X)
+#define __isnormal_builtin(X) __builtin_isnormal (X)
+#define __fpclassify_builtin(X) __builtin_fpclassify (FP_NAN, FP_INFINITE,  \
+				  FP_NORMAL, FP_SUBNORMAL, FP_ZERO, (X))
+
+static double __attribute ((noinline))
+kernel_standard (double x, double y, int z)
+{
+  return x * y + z;
+}
+
+volatile double rem1 = 2.5;
+
+static __always_inline double
+remainder_test1 (double x)
+{
+  double y = rem1;
+  if (((__builtin_expect (y == 0.0, 0) && !__isnan_inl (x))
+	|| (__builtin_expect (__isinf_ns2 (x), 0) && !__isnan_inl (y))))
+    return kernel_standard (x, y, 10);
+
+  return remainder (x, y);
+}
+
+static __always_inline double
+remainder_test2 (double x)
+{
+  double y = rem1;
+  if (((__builtin_expect (y == 0.0, 0) && !__builtin_isnan (x))
+	|| (__builtin_expect (__builtin_isinf (x), 0) && !__builtin_isnan (y))))
+    return kernel_standard (x, y, 10);
+
+  return remainder (x, y);
+}
+
+/* Create test functions for each possibility.  */
+
+BOOLTEST (__isnan)
+BOOLTEST (__isnan_inl)
+BOOLTEST (__isnan_builtin)
+BOOLTEST (isnan)
+
+BOOLTEST (__isinf)
+BOOLTEST (__isinf_builtin)
+BOOLTEST (__isinf_ns2)
+BOOLTEST (__isinf_ns_builtin)
+BOOLTEST (isinf)
+
+BOOLTEST (__finite)
+BOOLTEST (__finite_inl)
+BOOLTEST (__isfinite_builtin)
+BOOLTEST (isfinite)
+
+BOOLTEST (__isnormal_inl)
+BOOLTEST (__isnormal_builtin)
+BOOLTEST (isnormal)
+
+VALUETEST (__fpclassify)
+VALUETEST (__fpclassify_builtin)
+VALUETEST (fpclassify)
+
+VALUETEST (remainder_test1)
+VALUETEST (remainder_test2)
+
+typedef int (*proto_t) (volatile double *p, size_t n, size_t iters);
+
+typedef struct
+{
+  const char *name;
+  proto_t fn;
+} impl_t;
+
+#define IMPL(name) { #name, name ## _t }
+
+static impl_t test_list[] =
+{
+  IMPL (__isnan),
+  IMPL (__isnan_inl),
+  IMPL (__isnan_builtin),
+  IMPL (isnan),
+
+  IMPL (__isinf),
+  IMPL (__isinf_ns2),
+  IMPL (__isinf_ns_builtin),
+  IMPL (__isinf_builtin),
+  IMPL (isinf),
+
+  IMPL (__finite),
+  IMPL (__finite_inl),
+  IMPL (__isfinite_builtin),
+  IMPL (isfinite),
+
+  IMPL (__isnormal_inl),
+  IMPL (__isnormal_builtin),
+  IMPL (isnormal),
+
+  IMPL (__fpclassify),
+  IMPL (__fpclassify_builtin),
+  IMPL (fpclassify),
+
+  IMPL (remainder_test1),
+  IMPL (remainder_test2)
+};
+
+static void
+do_one_test (json_ctx_t *json_ctx, proto_t test_fn, volatile double *arr,
+	     size_t len, const char *testname)
+{
+  size_t iters = 500;
+  timing_t start, stop, cur;
+
+  json_attr_object_begin (json_ctx, testname);
+
+  TIMING_NOW (start);
+  test_fn (arr, len, iters);
+  TIMING_NOW (stop);
+  TIMING_DIFF (cur, start, stop);
+
+  json_attr_double (json_ctx, "duration", cur);
+  json_attr_double (json_ctx, "iterations", iters);
+  json_attr_double (json_ctx, "mean", cur / iters);
+  json_attr_object_end (json_ctx);
+}
+
+static volatile double arr1[SIZE];
+static volatile double arr2[SIZE];
+
+int
+test_main (void)
+{
+  json_ctx_t json_ctx;
+  size_t i;
+
+  bench_start ();
+
+  json_init (&json_ctx, 2, stdout);
+  json_attr_object_begin (&json_ctx, TEST_NAME);
+
+  /* Create 2 test arrays, one with 10% zeroes, 10% negative values,
+     79% positive values and 1% infinity/NaN.  The other contains
+     50% inf, 50% NaN.  This relies on rand behaving correctly.  */
+
+  for (i = 0; i < SIZE; i++)
+    {
+      int x = rand () & 255;
+      arr1[i] = (x < 25) ? 0.0 : ((x < 50) ? -1 : 100);
+      if (x == 255) arr1[i] = __builtin_inf ();
+      if (x == 254) arr1[i] = __builtin_nan ("0");
+      arr2[i] = (x < 128) ? __builtin_inf () : __builtin_nan ("0");
+    }
+
+  for (i = 0; i < sizeof (test_list) / sizeof (test_list[0]); i++)
+    {
+      json_attr_object_begin (&json_ctx, test_list[i].name);
+      do_one_test (&json_ctx, test_list[i].fn, arr2, SIZE, "inf/nan");
+      json_attr_object_end (&json_ctx);
+    }
+
+  for (i = 0; i < sizeof (test_list) / sizeof (test_list[0]); i++)
+    {
+      json_attr_object_begin (&json_ctx, test_list[i].name);
+      do_one_test (&json_ctx, test_list[i].fn, arr1, SIZE, "normal");
+      json_attr_object_end (&json_ctx);
+    }
+
+  json_attr_object_end (&json_ctx);
+  return 0;
+}
+
+#include "bench-util.c"
+#include "../test-skeleton.c"
diff --git a/benchtests/bench-skeleton.c b/benchtests/bench-skeleton.c
index e357f0c..bc820df 100644
--- a/benchtests/bench-skeleton.c
+++ b/benchtests/bench-skeleton.c
@@ -24,21 +24,9 @@ 
 #include <inttypes.h>
 #include "bench-timing.h"
 #include "json-lib.h"
+#include "bench-util.h"
 
-volatile unsigned int dontoptimize = 0;
-
-void
-startup (void)
-{
-  /* This loop should cause CPU to switch to maximal freqency.
-     This makes subsequent measurement more accurate.  We need a side effect
-     to prevent the loop being deleted by compiler.
-     This should be enough to cause CPU to speed up and it is simpler than
-     running loop for constant time. This is used when user does not have root
-     access to set a constant freqency.  */
-  for (int k = 0; k < 10000000; k++)
-    dontoptimize += 23 * dontoptimize + 2;
-}
+#include "bench-util.c"
 
 #define TIMESPEC_AFTER(a, b) \
   (((a).tv_sec == (b).tv_sec) ?						      \
@@ -56,7 +44,7 @@  main (int argc, char **argv)
   if (argc == 2 && !strcmp (argv[1], "-d"))
     detailed = true;
 
-  startup();
+  bench_start ();
 
   memset (&runtime, 0, sizeof (runtime));
 
diff --git a/benchtests/bench-util.c b/benchtests/bench-util.c
new file mode 100644
index 0000000..c4149ae
--- /dev/null
+++ b/benchtests/bench-util.c
@@ -0,0 +1,34 @@ 
+/* Benchmark utility functions.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+
+static volatile unsigned int dontoptimize = 0;
+
+void
+bench_start (void)
+{
+  /* This loop should cause CPU to switch to maximal freqency.
+     This makes subsequent measurement more accurate.  We need a side effect
+     to prevent the loop being deleted by compiler.
+     This should be enough to cause CPU to speed up and it is simpler than
+     running loop for constant time.  This is used when user does not have root
+     access to set a constant freqency.  */
+
+  for (int k = 0; k < START_ITER; k++)
+    dontoptimize += 23 * dontoptimize + 2;
+}
diff --git a/benchtests/bench-util.h b/benchtests/bench-util.h
new file mode 100644
index 0000000..930cecc
--- /dev/null
+++ b/benchtests/bench-util.h
@@ -0,0 +1,28 @@ 
+/* Benchmark utility functions.
+   Copyright (C) 2015 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <http://www.gnu.org/licenses/>.  */
+
+
+#ifndef START_ITER
+# define START_ITER (100000000)
+#endif
+
+/* bench_start reduces the random variations due to frequency scaling by
+   executing a small loop with many memory accesses.  START_ITER controls
+   the number of iterations.  */
+
+void bench_start (void);