Add new benchmarking utility, bench-slope
authorJussi Kivilinna <jussi.kivilinna@iki.fi>
Sat, 26 Oct 2013 12:00:48 +0000 (15:00 +0300)
committerJussi Kivilinna <jussi.kivilinna@iki.fi>
Mon, 28 Oct 2013 09:29:36 +0000 (11:29 +0200)
* tests/Makefile.am (TESTS): Add 'bench-slope'.
* tests/bench-slope.c: New.
--

Bench-slope is new benchmarking tool for libgcrypt for obtaining overheadless
cycles/byte speed of cipher and hash algorithms. Tool measures the time each
operation (hash/encrypt/decrypt/authentication) takes for different buffer
sizes of from ~0kB to ~4kB and calculates the slope for these data points.
The default output is then given as nanosecs/byte and mebibytes/sec. If user
provides the speed of used CPU, tool also outputs cycles/byte result (CPU-Ghz *
ns/B = c/B).

Output without CPU speed (with ARM Cortex-A8):

$ tests/bench-slope hash

Hash:
                |  nanosecs/byte   mebibytes/sec   cycles/byte
 MD5            |      7.35 ns/B     129.7 MiB/s         - c/B
 SHA1           |     12.30 ns/B     77.53 MiB/s         - c/B
 RIPEMD160      |     15.96 ns/B     59.77 MiB/s         - c/B
 TIGER192       |     55.55 ns/B     17.17 MiB/s         - c/B
 SHA256         |     24.38 ns/B     39.12 MiB/s         - c/B
 SHA384         |     34.24 ns/B     27.86 MiB/s         - c/B
 SHA512         |     34.19 ns/B     27.90 MiB/s         - c/B
 SHA224         |     24.38 ns/B     39.12 MiB/s         - c/B
 MD4            |      5.68 ns/B     168.0 MiB/s         - c/B
 CRC32          |      9.26 ns/B     103.0 MiB/s         - c/B
 CRC32RFC1510   |      9.20 ns/B     103.6 MiB/s         - c/B
 CRC24RFC2440   |     87.31 ns/B     10.92 MiB/s         - c/B
 WHIRLPOOL      |     253.3 ns/B      3.77 MiB/s         - c/B
 TIGER          |     55.55 ns/B     17.17 MiB/s         - c/B
 TIGER2         |     55.55 ns/B     17.17 MiB/s         - c/B
 GOSTR3411_94   |     212.0 ns/B      4.50 MiB/s         - c/B
 STRIBOG256     |     630.1 ns/B      1.51 MiB/s         - c/B
 STRIBOG512     |     630.1 ns/B      1.51 MiB/s         - c/B
                =

With CPU speed (with Intel i5-4570, 3.2Ghz when turbo-boost disabled):

$ tests/bench-slope --cpu-mhz 3201 cipher arcfour blowfish aes
Cipher:
 ARCFOUR        |  nanosecs/byte   mebibytes/sec   cycles/byte
     STREAM enc |      2.43 ns/B     392.1 MiB/s      7.79 c/B
     STREAM dec |      2.44 ns/B     390.2 MiB/s      7.82 c/B
                =
 BLOWFISH       |  nanosecs/byte   mebibytes/sec   cycles/byte
        ECB enc |      7.62 ns/B     125.2 MiB/s     24.38 c/B
        ECB dec |      7.63 ns/B     125.0 MiB/s     24.43 c/B
        CBC enc |      9.18 ns/B     103.9 MiB/s     29.38 c/B
        CBC dec |      2.60 ns/B     366.2 MiB/s      8.34 c/B
        CFB enc |      9.17 ns/B     104.0 MiB/s     29.35 c/B
        CFB dec |      2.66 ns/B     358.1 MiB/s      8.53 c/B
        OFB enc |      8.97 ns/B     106.3 MiB/s     28.72 c/B
        OFB dec |      8.97 ns/B     106.3 MiB/s     28.71 c/B
        CTR enc |      2.60 ns/B     366.5 MiB/s      8.33 c/B
        CTR dec |      2.60 ns/B     367.1 MiB/s      8.32 c/B
                =
 AES            |  nanosecs/byte   mebibytes/sec   cycles/byte
        ECB enc |     0.439 ns/B    2173.0 MiB/s      1.40 c/B
        ECB dec |     0.489 ns/B    1949.5 MiB/s      1.57 c/B
        CBC enc |      1.64 ns/B     580.8 MiB/s      5.26 c/B
        CBC dec |     0.219 ns/B    4357.6 MiB/s     0.701 c/B
        CFB enc |      1.53 ns/B     623.6 MiB/s      4.90 c/B
        CFB dec |     0.219 ns/B    4350.5 MiB/s     0.702 c/B
        OFB enc |      1.51 ns/B     629.9 MiB/s      4.85 c/B
        OFB dec |      1.51 ns/B     629.9 MiB/s      4.85 c/B
        CTR enc |     0.288 ns/B    3308.5 MiB/s     0.923 c/B
        CTR dec |     0.288 ns/B    3316.9 MiB/s     0.920 c/B
        CCM enc |      1.93 ns/B     493.8 MiB/s      6.18 c/B
        CCM dec |      1.93 ns/B     494.0 MiB/s      6.18 c/B
       CCM auth |      1.64 ns/B     580.1 MiB/s      5.26 c/B
                =

Note: It's highly recommented to disable turbo-boost and dynamic CPU frequency
features when making these kind of measurements to reduce variance.

Note: The results are maximum performance for each operation; the actual speed
in application depends on various matters, such as: used buffer sizes, cache
usage, etc.

Signed-off-by: Jussi Kivilinna <jussi.kivilinna@iki.fi>
tests/Makefile.am
tests/bench-slope.c [new file with mode: 0644]

index ac84e75..c9ba5f4 100644 (file)
@@ -24,8 +24,8 @@ TESTS = version mpitests tsexp t-convert \
        fips186-dsa aeswrap pkcs1v2 random dsa-rfc6979 t-ed25519
 
 
-# The last test to run.
-TESTS += benchmark
+# The last tests to run.
+TESTS += benchmark bench-slope
 
 
 # Need to include ../src in addition to top_srcdir because gcrypt.h is
diff --git a/tests/bench-slope.c b/tests/bench-slope.c
new file mode 100644 (file)
index 0000000..62543bc
--- /dev/null
@@ -0,0 +1,1172 @@
+/* bench-slope.c - for libgcrypt
+ * Copyright © 2013 Jussi Kivilinna <jussi.kivilinna@iki.fi>
+ *
+ * This file is part of Libgcrypt.
+ *
+ * Libgcrypt 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.
+ *
+ * Libgcrypt 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 this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <time.h>
+
+#ifdef _GCRYPT_IN_LIBGCRYPT
+#include "../src/gcrypt-int.h"
+#include "../compat/libcompat.h"
+#else
+#include <gcrypt.h>
+#endif
+
+#define PGM "bench-slope"
+
+static int verbose;
+
+
+/* CPU Ghz value provided by user, allows constructing cycles/byte and other
+   results.  */
+static double cpu_ghz = -1;
+
+
+
+/*************************************** Default parameters for measurements. */
+
+/* Start at small buffer size, to get reasonable timer calibration for fast
+ * implementations (AES-NI etc). Sixteen selected to support the largest block
+ * size of current set cipher blocks. */
+#define BUF_START_SIZE                 16
+
+/* From ~0 to ~4kbytes give comparable results with results from academia
+ * (SUPERCOP). */
+#define BUF_END_SIZE                   (BUF_START_SIZE + 4096)
+
+/* With 128 byte steps, we get (4096)/128 = 32 data points. */
+#define BUF_STEP_SIZE                  128
+
+/* Number of repeated measurements at each data point. The median of these
+ * measurements is selected as data point further analysis. */
+#define NUM_MEASUREMENT_REPETITIONS    32
+
+/**************************************************** High-resolution timers. */
+
+/* This benchmarking module needs needs high resolution timer.  */
+#undef NO_GET_NSEC_TIME
+#if defined(_WIN32)
+struct nsec_time
+{
+  LARGE_INTEGER perf_count;
+};
+
+static void
+get_nsec_time (struct nsec_time *t)
+{
+  BOOL ok;
+
+  ok = QueryPerformanceCounter (&t->perf_count);
+  assert (ok);
+}
+
+static double
+get_time_nsec_diff (struct nsec_time *start, struct nsec_time *end)
+{
+  static double nsecs_per_count = 0.0;
+  double nsecs;
+
+  if (nsecs_per_count == 0.0)
+    {
+      LARGE_INTEGER perf_freq;
+      BOOL ok;
+
+      /* Get counts per second. */
+      ok = QueryPerformanceFrequency (&perf_freq);
+      assert (ok);
+
+      nsecs_per_count = 1.0 / perf_freq.QuadPart;
+      nsecs_per_count *= 1000000.0 * 1000.0;   /* sec => nsec */
+
+      assert (nsecs_per_count > 0.0);
+    }
+
+  nsecs = end->perf_count.QuadPart - start->perf_count.QuadPart;       /* counts */
+  nsecs *= nsecs_per_count;    /* counts * (nsecs / count) => nsecs */
+
+  return nsecs;
+}
+#elif defined(HAVE_CLOCK_GETTIME)
+struct nsec_time
+{
+  struct timespec ts;
+};
+
+static void
+get_nsec_time (struct nsec_time *t)
+{
+  int err;
+
+  err = clock_gettime (CLOCK_REALTIME, &t->ts);
+  assert (err == 0);
+}
+
+static double
+get_time_nsec_diff (struct nsec_time *start, struct nsec_time *end)
+{
+  double nsecs;
+
+  nsecs = end->ts.tv_sec - start->ts.tv_sec;
+  nsecs *= 1000000.0 * 1000.0; /* sec => nsec */
+
+  /* This way we don't have to care if tv_nsec unsigned or signed. */
+  if (end->ts.tv_nsec >= start->ts.tv_nsec)
+    nsecs += end->ts.tv_nsec - start->ts.tv_nsec;
+  else
+    nsecs -= start->ts.tv_nsec - end->ts.tv_nsec;
+
+  return nsecs;
+}
+#elif defined(HAVE_GETTIMEOFDAY)
+struct nsec_time
+{
+  struct timeval tv;
+};
+
+static void
+get_nsec_time (struct nsec_time *t)
+{
+  int err;
+
+  err = gettimeofday (&t->tv, NULL);
+  assert (err == 0);
+}
+
+static double
+get_time_nsec_diff (struct nsec_time *start, struct nsec_time *end)
+{
+  double nsecs;
+
+  nsecs = end->tv.tv_sec - start->tv.tv_sec;
+  nsecs *= 1000000;            /* sec => µsec */
+
+  /* This way we don't have to care if tv_usec unsigned or signed. */
+  if (end->tv.tv_usec >= start->tv.tv_usec)
+    nsecs += end->tv.tv_usec - start->tv.tv_usec;
+  else
+    nsecs -= start->tv.tv_usec - end->tv.tv_usec;
+
+  nsecs *= 1000;               /* µsec => nsec */
+
+  return nsecs;
+}
+#else
+#define NO_GET_NSEC_TIME 1
+#endif
+
+
+/* If no high resolution timer found, provide dummy bench-slope.  */
+#ifdef NO_GET_NSEC_TIME
+
+
+int
+main (void)
+{
+  /* No nsec timer => SKIP test. */
+  return 77;
+}
+
+
+#else /* !NO_GET_NSEC_TIME */
+
+
+/********************************************** Slope benchmarking framework. */
+
+struct bench_obj
+{
+  const struct bench_ops *ops;
+
+  unsigned int num_measure_repetitions;
+  unsigned int min_bufsize;
+  unsigned int max_bufsize;
+  unsigned int step_size;
+
+  void *priv;
+};
+
+typedef int (*const bench_initialize_t) (struct bench_obj * obj);
+typedef void (*const bench_finalize_t) (struct bench_obj * obj);
+typedef void (*const bench_do_run_t) (struct bench_obj * obj, void *buffer,
+                                     size_t buflen);
+
+struct bench_ops
+{
+  bench_initialize_t initialize;
+  bench_finalize_t finalize;
+  bench_do_run_t do_run;
+};
+
+
+double
+get_slope (double (*const get_x) (unsigned int idx, void *priv),
+          void *get_x_priv, double y_points[], unsigned int npoints,
+          double *overhead)
+{
+  double sumx, sumy, sumx2, sumy2, sumxy;
+  unsigned int i;
+  double b, a;
+
+  sumx = sumy = sumx2 = sumy2 = sumxy = 0;
+
+  for (i = 0; i < npoints; i++)
+    {
+      double x, y;
+
+      x = get_x (i, get_x_priv);       /* bytes */
+      y = y_points[i];         /* nsecs */
+
+      sumx += x;
+      sumy += y;
+      sumx2 += x * x;
+      //sumy2 += y * y;
+      sumxy += x * y;
+    }
+
+  b = (npoints * sumxy - sumx * sumy) / (npoints * sumx2 - sumx * sumx);
+  a = (sumy - b * sumx) / npoints;
+
+  if (overhead)
+    *overhead = a;             /* nsecs */
+
+  return b;                    /* nsecs per byte */
+}
+
+
+double
+get_bench_obj_point_x (unsigned int idx, void *priv)
+{
+  struct bench_obj *obj = priv;
+  return (double) (obj->min_bufsize + (idx * obj->step_size));
+}
+
+
+unsigned int
+get_num_measurements (struct bench_obj *obj)
+{
+  unsigned int buf_range = obj->max_bufsize - obj->min_bufsize;
+  unsigned int num = buf_range / obj->step_size + 1;
+
+  while (obj->min_bufsize + (num * obj->step_size) > obj->max_bufsize)
+    num--;
+
+  return num + 1;
+}
+
+
+static int
+double_cmp (const void *__a, const void *__b)
+{
+  const double *a, *b;
+
+  a = __a;
+  b = __b;
+
+  if (*a > *b)
+    return 1;
+  if (*a < *b)
+    return -1;
+  return 0;
+}
+
+
+double
+do_bench_obj_measurement (struct bench_obj *obj, void *buffer, size_t buflen,
+                         double *measurement_raw,
+                         unsigned int loop_iterations)
+{
+  const unsigned int num_repetitions = obj->num_measure_repetitions;
+  const bench_do_run_t do_run = obj->ops->do_run;
+  struct nsec_time start, end;
+  unsigned int rep, loop;
+  double res;
+
+  if (num_repetitions < 1 || loop_iterations < 1)
+    return 0.0;
+
+  for (rep = 0; rep < num_repetitions; rep++)
+    {
+      get_nsec_time (&start);
+
+      for (loop = 0; loop < loop_iterations; loop++)
+       do_run (obj, buffer, buflen);
+
+      get_nsec_time (&end);
+
+      measurement_raw[rep] = get_time_nsec_diff (&start, &end);
+    }
+
+  /* Return median of repeated measurements. */
+  qsort (measurement_raw, num_repetitions, sizeof (measurement_raw[0]),
+        double_cmp);
+
+  if (num_repetitions % 2 == 1)
+    return measurement_raw[num_repetitions / 2];
+
+  res = measurement_raw[num_repetitions / 2]
+    + measurement_raw[num_repetitions / 2 - 1];
+  return res / 2;
+}
+
+
+unsigned int
+adjust_loop_iterations_to_timer_accuracy (struct bench_obj *obj, void *buffer,
+                                         double *measurement_raw)
+{
+  const double increase_thres = 3.0;
+  double tmp, nsecs;
+  unsigned int loop_iterations;
+  unsigned int test_bufsize;
+
+  test_bufsize = obj->min_bufsize;
+  if (test_bufsize == 0)
+    test_bufsize += obj->step_size;
+
+  loop_iterations = 0;
+  do
+    {
+      /* Increase loop iterations until we get other results than zero.  */
+      nsecs =
+       do_bench_obj_measurement (obj, buffer, test_bufsize,
+                                 measurement_raw, ++loop_iterations);
+    }
+  while (nsecs < 1.0 - 0.1);
+  do
+    {
+      /* Increase loop iterations until we get reasonable increase for elapsed time.  */
+      tmp =
+       do_bench_obj_measurement (obj, buffer, test_bufsize,
+                                 measurement_raw, ++loop_iterations);
+    }
+  while (tmp < nsecs * (increase_thres - 0.1));
+
+  return loop_iterations;
+}
+
+
+/* Benchmark and return linear regression slope in nanoseconds per byte.  */
+double
+do_slope_benchmark (struct bench_obj *obj)
+{
+  unsigned int num_measurements;
+  double *measurements = NULL;
+  double *measurement_raw = NULL;
+  double slope, overhead;
+  unsigned int loop_iterations, midx, i;
+  unsigned char *real_buffer = NULL;
+  unsigned char *buffer;
+  size_t cur_bufsize;
+  int err;
+
+  err = obj->ops->initialize (obj);
+  if (err < 0)
+    return -1;
+
+  num_measurements = get_num_measurements (obj);
+  measurements = calloc (num_measurements, sizeof (*measurements));
+  if (!measurements)
+    goto err_free;
+
+  measurement_raw =
+    calloc (obj->num_measure_repetitions, sizeof (*measurement_raw));
+  if (!measurement_raw)
+    goto err_free;
+
+  if (num_measurements < 1 || obj->num_measure_repetitions < 1 ||
+      obj->max_bufsize < 1 || obj->min_bufsize > obj->max_bufsize)
+    goto err_free;
+
+  real_buffer = malloc (obj->max_bufsize + 128);
+  if (!real_buffer)
+    goto err_free;
+  /* Get aligned buffer */
+  buffer = real_buffer;
+  buffer += 128 - ((real_buffer - (unsigned char *) 0) & (128 - 1));
+
+  for (i = 0; i < obj->max_bufsize; i++)
+    buffer[i] = 0x55 ^ (-i);
+
+  /* Adjust number of loop iterations up to timer accuracy.  */
+  loop_iterations = adjust_loop_iterations_to_timer_accuracy (obj, buffer,
+                                                             measurement_raw);
+
+  /* Perform measurements */
+  for (midx = 0, cur_bufsize = obj->min_bufsize;
+       cur_bufsize <= obj->max_bufsize; cur_bufsize += obj->step_size, midx++)
+    {
+      measurements[midx] =
+       do_bench_obj_measurement (obj, buffer, cur_bufsize, measurement_raw,
+                                 loop_iterations);
+      measurements[midx] /= loop_iterations;
+    }
+
+  assert (midx == num_measurements);
+
+  slope =
+    get_slope (&get_bench_obj_point_x, obj, measurements, num_measurements,
+              &overhead);
+
+  free (measurement_raw);
+  free (real_buffer);
+  obj->ops->finalize (obj);
+
+  return slope;
+
+err_free:
+  if (measurement_raw)
+    free (measurement_raw);
+  if (measurements)
+    free (measurements);
+  if (real_buffer)
+    free (real_buffer);
+  obj->ops->finalize (obj);
+
+  return -1;
+}
+
+
+/********************************************************** Printing results. */
+
+static void
+double_to_str (char *out, size_t outlen, double value)
+{
+  const char *fmt;
+
+  if (value < 1.0)
+    fmt = "%.3f";
+  else if (value < 100.0)
+    fmt = "%.2f";
+  else
+    fmt = "%.1f";
+
+  snprintf (out, outlen, fmt, value);
+}
+
+static void
+bench_print_result (double nsecs_per_byte)
+{
+  double cycles_per_byte, mbytes_per_sec;
+  char nsecpbyte_buf[16];
+  char mbpsec_buf[16];
+  char cpbyte_buf[16];
+
+  strcpy (cpbyte_buf, "-");
+
+  double_to_str (nsecpbyte_buf, sizeof (nsecpbyte_buf), nsecs_per_byte);
+
+  /* If user didn't provide CPU speed, we cannot show cycles/byte results.  */
+  if (cpu_ghz > 0.0)
+    {
+      cycles_per_byte = nsecs_per_byte * cpu_ghz;
+      double_to_str (cpbyte_buf, sizeof (cpbyte_buf), cycles_per_byte);
+    }
+
+  mbytes_per_sec =
+    (1000.0 * 1000.0 * 1000.0) / (nsecs_per_byte * 1024 * 1024);
+  double_to_str (mbpsec_buf, sizeof (mbpsec_buf), mbytes_per_sec);
+
+  strncat (nsecpbyte_buf, " ns/B", sizeof (nsecpbyte_buf) - 1);
+  strncat (mbpsec_buf, " MiB/s", sizeof (mbpsec_buf) - 1);
+  strncat (cpbyte_buf, " c/B", sizeof (cpbyte_buf) - 1);
+
+  printf ("%14s %15s %13s\n", nsecpbyte_buf, mbpsec_buf, cpbyte_buf);
+}
+
+static void
+bench_print_header (const char *algo_name)
+{
+  printf (" %-14s | ", algo_name);
+  printf ("%14s %15s %13s\n", "nanosecs/byte", "mebibytes/sec",
+         "cycles/byte");
+}
+
+static void
+bench_print_footer (void)
+{
+  printf (" %-14s =\n", "");
+}
+
+
+/********************************************************* Cipher benchmarks. */
+
+struct bench_cipher_mode
+{
+  int mode;
+  const char *name;
+  struct bench_ops *ops;
+
+  int algo;
+};
+
+
+static int
+bench_encrypt_init (struct bench_obj *obj)
+{
+  struct bench_cipher_mode *mode = obj->priv;
+  gcry_cipher_hd_t hd;
+  int err, keylen;
+
+  obj->min_bufsize = BUF_START_SIZE;
+  obj->max_bufsize = BUF_END_SIZE;
+  obj->step_size = BUF_STEP_SIZE;
+  obj->num_measure_repetitions = NUM_MEASUREMENT_REPETITIONS;
+
+  err = gcry_cipher_open (&hd, mode->algo, mode->mode, 0);
+  if (err)
+    {
+      fprintf (stderr, PGM ": error opening cipher `%s'\n",
+              gcry_cipher_algo_name (mode->algo));
+      exit (1);
+    }
+
+  keylen = gcry_cipher_get_algo_keylen (mode->algo);
+  if (keylen)
+    {
+      char key[keylen];
+      int i;
+
+      for (i = 0; i < keylen; i++)
+       key[i] = 0x33 ^ (11 - i);
+
+      err = gcry_cipher_setkey (hd, key, keylen);
+      if (err)
+       {
+         fprintf (stderr, PGM ": gcry_cipher_setkey failed: %s\n",
+                  gpg_strerror (err));
+         gcry_cipher_close (hd);
+         exit (1);
+       }
+    }
+  else
+    {
+      fprintf (stderr, PGM ": failed to get key length for algorithm `%s'\n",
+              gcry_cipher_algo_name (mode->algo));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+
+  obj->priv = hd;
+
+  return 0;
+}
+
+static void
+bench_encrypt_free (struct bench_obj *obj)
+{
+  gcry_cipher_hd_t hd = obj->priv;
+
+  gcry_cipher_close (hd);
+}
+
+static void
+bench_encrypt_do_bench (struct bench_obj *obj, void *buf, size_t buflen)
+{
+  gcry_cipher_hd_t hd = obj->priv;
+  int err;
+
+  err = gcry_cipher_encrypt (hd, buf, buflen, buf, buflen);
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_encrypt failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+}
+
+static void
+bench_decrypt_do_bench (struct bench_obj *obj, void *buf, size_t buflen)
+{
+  gcry_cipher_hd_t hd = obj->priv;
+  int err;
+
+  err = gcry_cipher_decrypt (hd, buf, buflen, buf, buflen);
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_encrypt failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+}
+
+static struct bench_ops encrypt_ops = {
+  &bench_encrypt_init,
+  &bench_encrypt_free,
+  &bench_encrypt_do_bench
+};
+
+static struct bench_ops decrypt_ops = {
+  &bench_encrypt_init,
+  &bench_encrypt_free,
+  &bench_decrypt_do_bench
+};
+
+
+
+static void
+bench_ccm_encrypt_do_bench (struct bench_obj *obj, void *buf, size_t buflen)
+{
+  gcry_cipher_hd_t hd = obj->priv;
+  int err;
+  char tag[8];
+  char nonce[11] = { 0x80, 0x01, };
+  size_t params[3];
+
+  gcry_cipher_setiv (hd, nonce, sizeof (nonce));
+
+  /* Set CCM lengths */
+  params[0] = buflen;
+  params[1] = 0;               /*aadlen */
+  params[2] = sizeof (tag);
+  err =
+    gcry_cipher_ctl (hd, GCRYCTL_SET_CCM_LENGTHS, params, sizeof (params));
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_ctl failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+
+  err = gcry_cipher_encrypt (hd, buf, buflen, buf, buflen);
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_encrypt failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+
+  err = gcry_cipher_gettag (hd, tag, sizeof (tag));
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_gettag failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+}
+
+static void
+bench_ccm_decrypt_do_bench (struct bench_obj *obj, void *buf, size_t buflen)
+{
+  gcry_cipher_hd_t hd = obj->priv;
+  int err;
+  char tag[8] = { 0, };
+  char nonce[11] = { 0x80, 0x01, };
+  size_t params[3];
+
+  gcry_cipher_setiv (hd, nonce, sizeof (nonce));
+
+  /* Set CCM lengths */
+  params[0] = buflen;
+  params[1] = 0;               /*aadlen */
+  params[2] = sizeof (tag);
+  err =
+    gcry_cipher_ctl (hd, GCRYCTL_SET_CCM_LENGTHS, params, sizeof (params));
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_ctl failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+
+  err = gcry_cipher_decrypt (hd, buf, buflen, buf, buflen);
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_encrypt failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+
+  err = gcry_cipher_checktag (hd, tag, sizeof (tag));
+  if (gpg_err_code (err) == GPG_ERR_CHECKSUM)
+    err = gpg_error (GPG_ERR_NO_ERROR);
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_gettag failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+}
+
+static void
+bench_ccm_authenticate_do_bench (struct bench_obj *obj, void *buf,
+                                size_t buflen)
+{
+  gcry_cipher_hd_t hd = obj->priv;
+  int err;
+  char tag[8] = { 0, };
+  char nonce[11] = { 0x80, 0x01, };
+  size_t params[3];
+  char data = 0xff;
+
+  gcry_cipher_setiv (hd, nonce, sizeof (nonce));
+
+  /* Set CCM lengths */
+  params[0] = sizeof (data);   /*datalen */
+  params[1] = buflen;          /*aadlen */
+  params[2] = sizeof (tag);
+  err =
+    gcry_cipher_ctl (hd, GCRYCTL_SET_CCM_LENGTHS, params, sizeof (params));
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_ctl failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+
+  err = gcry_cipher_authenticate (hd, buf, buflen);
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_authenticate failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+
+  err = gcry_cipher_encrypt (hd, &data, sizeof (data), &data, sizeof (data));
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_encrypt failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+
+  err = gcry_cipher_gettag (hd, tag, sizeof (tag));
+  if (err)
+    {
+      fprintf (stderr, PGM ": gcry_cipher_gettag failed: %s\n",
+              gpg_strerror (err));
+      gcry_cipher_close (hd);
+      exit (1);
+    }
+}
+
+static struct bench_ops ccm_encrypt_ops = {
+  &bench_encrypt_init,
+  &bench_encrypt_free,
+  &bench_ccm_encrypt_do_bench
+};
+
+static struct bench_ops ccm_decrypt_ops = {
+  &bench_encrypt_init,
+  &bench_encrypt_free,
+  &bench_ccm_decrypt_do_bench
+};
+
+static struct bench_ops ccm_authenticate_ops = {
+  &bench_encrypt_init,
+  &bench_encrypt_free,
+  &bench_ccm_authenticate_do_bench
+};
+
+
+static struct bench_cipher_mode cipher_modes[] = {
+  {GCRY_CIPHER_MODE_ECB, "ECB enc", &encrypt_ops},
+  {GCRY_CIPHER_MODE_ECB, "ECB dec", &decrypt_ops},
+  {GCRY_CIPHER_MODE_CBC, "CBC enc", &encrypt_ops},
+  {GCRY_CIPHER_MODE_CBC, "CBC dec", &decrypt_ops},
+  {GCRY_CIPHER_MODE_CFB, "CFB enc", &encrypt_ops},
+  {GCRY_CIPHER_MODE_CFB, "CFB dec", &decrypt_ops},
+  {GCRY_CIPHER_MODE_OFB, "OFB enc", &encrypt_ops},
+  {GCRY_CIPHER_MODE_OFB, "OFB dec", &decrypt_ops},
+  {GCRY_CIPHER_MODE_CTR, "CTR enc", &encrypt_ops},
+  {GCRY_CIPHER_MODE_CTR, "CTR dec", &decrypt_ops},
+  {GCRY_CIPHER_MODE_CCM, "CCM enc", &ccm_encrypt_ops},
+  {GCRY_CIPHER_MODE_CCM, "CCM dec", &ccm_decrypt_ops},
+  {GCRY_CIPHER_MODE_CCM, "CCM auth", &ccm_authenticate_ops},
+  {0},
+};
+
+
+static void
+cipher_bench_one (int algo, struct bench_cipher_mode *pmode)
+{
+  struct bench_cipher_mode mode = *pmode;
+  struct bench_obj obj = { 0 };
+  double result;
+  unsigned int blklen;
+
+  mode.algo = algo;
+
+  /* Check if this mode is ok */
+  blklen = gcry_cipher_get_algo_blklen (algo);
+  if (!blklen)
+    return;
+
+  /* Stream cipher? Only test with ECB. */
+  if (blklen == 1 && mode.mode != GCRY_CIPHER_MODE_ECB)
+    return;
+  if (blklen == 1 && mode.mode == GCRY_CIPHER_MODE_ECB)
+    {
+      mode.mode = GCRY_CIPHER_MODE_STREAM;
+      mode.name = mode.ops == &encrypt_ops ? "STREAM enc" : "STREAM dec";
+    }
+
+  /* CCM has restrictions for block-size */
+  if (mode.mode == GCRY_CIPHER_MODE_CCM && blklen != GCRY_CCM_BLOCK_LEN)
+    return;
+
+  printf (" %14s | ", mode.name);
+  fflush (stdout);
+
+  obj.ops = mode.ops;
+  obj.priv = &mode;
+
+  result = do_slope_benchmark (&obj);
+
+  bench_print_result (result);
+}
+
+
+static void
+__cipher_bench (int algo)
+{
+  const char *algoname;
+  int i;
+
+  algoname = gcry_cipher_algo_name (algo);
+
+  bench_print_header (algoname);
+
+  for (i = 0; cipher_modes[i].mode; i++)
+    cipher_bench_one (algo, &cipher_modes[i]);
+
+  bench_print_footer ();
+}
+
+
+void
+cipher_bench (char **argv, int argc)
+{
+  int i, algo;
+
+  printf ("Cipher:\n");
+
+  if (argv && argc)
+    {
+      for (i = 0; i < argc; i++)
+       {
+         algo = gcry_cipher_map_name (argv[i]);
+         if (algo)
+           __cipher_bench (algo);
+       }
+    }
+  else
+    {
+      for (i = 1; i < 400; i++)
+       if (!gcry_cipher_test_algo (i))
+         __cipher_bench (i);
+    }
+}
+
+
+/*********************************************************** Hash benchmarks. */
+
+struct bench_hash_mode
+{
+  const char *name;
+  struct bench_ops *ops;
+
+  int algo;
+};
+
+
+static int
+bench_hash_init (struct bench_obj *obj)
+{
+  struct bench_hash_mode *mode = obj->priv;
+  gcry_md_hd_t hd;
+  int err;
+
+  obj->min_bufsize = BUF_START_SIZE;
+  obj->max_bufsize = BUF_END_SIZE;
+  obj->step_size = BUF_STEP_SIZE;
+  obj->num_measure_repetitions = NUM_MEASUREMENT_REPETITIONS;
+
+  err = gcry_md_open (&hd, mode->algo, 0);
+  if (err)
+    {
+      fprintf (stderr, PGM ": error opening hash `%s'\n",
+              gcry_md_algo_name (mode->algo));
+      exit (1);
+    }
+
+  obj->priv = hd;
+
+  return 0;
+}
+
+static void
+bench_hash_free (struct bench_obj *obj)
+{
+  gcry_md_hd_t hd = obj->priv;
+
+  gcry_md_close (hd);
+}
+
+static void
+bench_hash_do_bench (struct bench_obj *obj, void *buf, size_t buflen)
+{
+  gcry_md_hd_t hd = obj->priv;
+
+  gcry_md_write (hd, buf, buflen);
+  gcry_md_final (hd);
+}
+
+static struct bench_ops hash_ops = {
+  &bench_hash_init,
+  &bench_hash_free,
+  &bench_hash_do_bench
+};
+
+
+static struct bench_hash_mode hash_modes[] = {
+  {"", &hash_ops},
+  {0},
+};
+
+
+static void
+hash_bench_one (int algo, struct bench_hash_mode *pmode)
+{
+  struct bench_hash_mode mode = *pmode;
+  struct bench_obj obj = { 0 };
+  double result;
+
+  mode.algo = algo;
+
+  if (mode.name[0] == '\0')
+    printf (" %-14s | ", gcry_md_algo_name (algo));
+  else
+    printf (" %14s | ", mode.name);
+  fflush (stdout);
+
+  obj.ops = mode.ops;
+  obj.priv = &mode;
+
+  result = do_slope_benchmark (&obj);
+
+  bench_print_result (result);
+}
+
+static void
+__hash_bench (int algo)
+{
+  int i;
+
+  for (i = 0; hash_modes[i].name; i++)
+    hash_bench_one (algo, &hash_modes[i]);
+}
+
+void
+hash_bench (char **argv, int argc)
+{
+  int i, algo;
+
+  printf ("Hash:\n");
+
+  bench_print_header ("");
+
+  if (argv && argc)
+    {
+      for (i = 0; i < argc; i++)
+       {
+         algo = gcry_md_map_name (argv[i]);
+         if (algo)
+           __hash_bench (algo);
+       }
+    }
+  else
+    {
+      for (i = 1; i < 400; i++)
+       if (!gcry_md_test_algo (i))
+         __hash_bench (i);
+    }
+
+  bench_print_footer ();
+}
+
+
+/************************************************************** Main program. */
+
+void
+print_help (void)
+{
+  static const char *help_lines[] = {
+    "usage: bench-slope [options] [hash|cipher [algonames]]",
+    "",
+    " options:",
+    "     --cpu-mhz <mhz>           Set CPU speed for calculating cycles per bytes",
+    "                               results.",
+    "     --disable-hwf <features>  Disable hardware acceleration feature(s) for",
+    "                               benchmarking.",
+    NULL
+  };
+  const char **line;
+
+  for (line = help_lines; *line; line++)
+    fprintf (stdout, "%s\n", *line);
+}
+
+
+/* Warm up CPU.  */
+static void
+warm_up_cpu (void)
+{
+  struct nsec_time start, end;
+
+  get_nsec_time (&start);
+  do
+    {
+      get_nsec_time (&end);
+    }
+  while (get_time_nsec_diff (&start, &end) < 1000.0 * 1000.0 * 1000.0);
+}
+
+
+int
+main (int argc, char **argv)
+{
+  int last_argc = -1;
+  int debug = 0;
+
+  if (argc)
+    {
+      argc--;
+      argv++;
+    }
+
+  while (argc && last_argc != argc)
+    {
+      last_argc = argc;
+
+      if (!strcmp (*argv, "--"))
+       {
+         argc--;
+         argv++;
+         break;
+       }
+      else if (!strcmp (*argv, "--help"))
+       {
+         print_help ();
+         exit (0);
+       }
+      else if (!strcmp (*argv, "--verbose"))
+       {
+         verbose++;
+         argc--;
+         argv++;
+       }
+      else if (!strcmp (*argv, "--debug"))
+       {
+         verbose += 2;
+         debug++;
+         argc--;
+         argv++;
+       }
+      else if (!strcmp (*argv, "--disable-hwf"))
+       {
+         argc--;
+         argv++;
+         if (argc)
+           {
+             if (gcry_control (GCRYCTL_DISABLE_HWF, *argv, NULL))
+               fprintf (stderr,
+                        PGM
+                        ": unknown hardware feature `%s' - option ignored\n",
+                        *argv);
+             argc--;
+             argv++;
+           }
+       }
+      else if (!strcmp (*argv, "--cpu-mhz"))
+       {
+         argc--;
+         argv++;
+         if (argc)
+           {
+             cpu_ghz = atof (*argv);
+             cpu_ghz /= 1000;  /* Mhz => Ghz */
+
+             argc--;
+             argv++;
+           }
+       }
+    }
+
+  gcry_control (GCRYCTL_SET_VERBOSITY, (int) verbose);
+
+  if (!gcry_check_version (GCRYPT_VERSION))
+    {
+      fprintf (stderr, PGM ": version mismatch; pgm=%s, library=%s\n",
+              GCRYPT_VERSION, gcry_check_version (NULL));
+      exit (1);
+    }
+
+  if (debug)
+    gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1u, 0);
+
+  gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
+  gcry_control (GCRYCTL_INITIALIZATION_FINISHED, 0);
+  gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
+
+  if (!argc)
+    {
+      warm_up_cpu ();
+      hash_bench (NULL, 0);
+      cipher_bench (NULL, 0);
+    }
+  else if (!strcmp (*argv, "hash"))
+    {
+      argc--;
+      argv++;
+
+      warm_up_cpu ();
+      hash_bench ((argc == 0) ? NULL : argv, argc);
+    }
+  else if (!strcmp (*argv, "cipher"))
+    {
+      argc--;
+      argv++;
+
+      warm_up_cpu ();
+      cipher_bench ((argc == 0) ? NULL : argv, argc);
+    }
+  else
+    {
+      fprintf (stderr, PGM ": unknown argument: %s\n", *argv);
+      print_help ();
+    }
+
+  return 0;
+}
+
+#endif /* !NO_GET_NSEC_TIME */