/* Build with gcc clear-pointer.c `pkg-config gio-2.0 --cflags --libs` */

#include <glib.h>
#include <gio/gio.h>

#include <sys/times.h>

#define MAX_THREADS 4
#define N_ITERS 10000000

#define _g_clear_pointer(pp, destroy) \
  G_STMT_START {                                                               \
    G_STATIC_ASSERT (sizeof *(pp) == sizeof (gpointer));                       \
    /* Only one access, please */                                              \
    gpointer *_pp = (gpointer *) (pp);                                         \
    gpointer _p;                                                               \
                                                                               \
    (void) (0 ? (gpointer) *(pp) : 0);                                         \
    do                                                                         \
      _p = g_atomic_pointer_get (_pp);                                         \
    while G_UNLIKELY (!g_atomic_pointer_compare_and_exchange (_pp, _p, NULL)); \
                                                                               \
    if (_p)                                                                    \
      ((GDestroyNotify) (destroy)) (_p);                                       \
  } G_STMT_END

typedef gpointer (*NewFunc) (void);
typedef struct
{
  const gchar *name;
  NewFunc new;
  GDestroyNotify destroy;
  gboolean clear;
} Func;

static gpointer
new_object (void)
{
  return g_file_new_for_path ("/tmp");
}

static gpointer
new_hash (void)
{
  return g_hash_table_new (NULL, NULL);
}

static gpointer
new_string (void)
{
  return g_strdup ("badger");
}

static gpointer
run_in_thread_func (gpointer user_data)
{
  Func *f = user_data;
  gpointer ptr;
  guint i;

  for (i = 0; i < N_ITERS; i++)
    {
      ptr = f->new ();

      if (f->clear)
        _g_clear_pointer (&ptr, f->destroy);
      else
        f->destroy (ptr);
    }
}

int
main (int argc,
  char **argv)
{
  Func funcs[] = {
    { "GObject", new_object, g_object_unref },
    { "GHashTable", new_hash, (GDestroyNotify) g_hash_table_unref },
    { "String", new_string, g_free },
  };
  GThread *threads[MAX_THREADS];
  guint n_threads, f, i, clear;

  g_type_init ();

  for (n_threads = 1; n_threads <= MAX_THREADS; n_threads++)
    {
      for (f = 0; f < G_N_ELEMENTS (funcs); f++)
        {
          double baseline_real, baseline_user, baseline_sys;
          double clear_real, clear_user, clear_sys;

          for (clear = 0; clear < 2; clear++)
            {
              clock_t real_before, real_after;
              struct tms tms_before, tms_after;

              g_print ("Profile %s destruction:\n", funcs[f].name);
              g_print ("  threads: %d\n", n_threads);
              g_print ("  g_clear_pointer: %s\n", clear ? "yes" : "no");

              funcs[f].clear = clear;

              real_before = times (&tms_before);

              for (i = 0; i < n_threads; i++)
                {
                  static guint thread_count = 0;
                  gchar *name;

                  name = g_strdup_printf ("clear-pointer-%d", thread_count++);
                  threads[i] = g_thread_new (name, run_in_thread_func,
                      &funcs[f]);
                  g_free (name);
                }

              for (i = 0; i < n_threads; i++)
                  g_thread_join (threads[i]);

              real_after = times (&tms_after);

              g_print ("  Real: %ld\n", (long) (real_after - real_before));
              g_print ("  User: %ld\n",
                  (long) (tms_after.tms_utime - tms_before.tms_utime));
              g_print ("  Sys : %ld\n",
                  (long) (tms_after.tms_stime - tms_before.tms_stime));

              if (!clear)
                {
                  baseline_real = real_after - real_before;
                  baseline_user = tms_after.tms_utime - tms_before.tms_utime;
                  baseline_sys = tms_after.tms_stime - tms_before.tms_stime;
                }
              else
                {
                  clear_real = real_after - real_before;
                  clear_user = tms_after.tms_utime - tms_before.tms_utime;
                  clear_sys = tms_after.tms_stime - tms_before.tms_stime;

                  g_print ("  Real: %2.2f%%\n",
                      clear_real / baseline_real * 100.0);
                  g_print ("  User: %2.2f%%\n",
                      clear_user / baseline_user * 100.0);
                  g_print ("  Sys : %2.2f%%\n",
                      clear_sys / baseline_sys * 100.0);
                }

              g_print ("-----\n\n");
            }
        }
    }

  return 0;
}
