/*
  Copyright 2010 Kristian Nielsen

  This is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 2 of the License, or
  (at your option) any later version.

  The program 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 General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/*
  Micro benchmark for pthread_cond_broadcast() vs. individual pthread_cond_signal().

  Motivation: MWL#116 review discussions.

  gcc -O3 -lpthread -o pthread_cond_broadcast pthread_cond_broadcast.c
*/

#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <time.h>
#include <string.h>
#include <unistd.h>


double
gettime(void)
{
  struct timeval tv;
  if (gettimeofday(&tv, NULL))
  {
    perror("gettimeofday()");
    exit(1);
  }
  return (double)tv.tv_sec + (double)tv.tv_usec*1e-6;
}

struct thread_data
{
  pthread_mutex_t i_mutex;
  pthread_cond_t i_cond;
  int i_wakeup;
  pthread_t thread_id;
  pthread_mutex_t *mutex;
  pthread_cond_t *cond;
  double timestamp;
  int ready;
  int *wakeup;
};

void *
my_thread(void *arg)
{
  struct thread_data *d= arg;
  pthread_mutex_lock(d->mutex);
  d->ready= 1;
  while (!*(d->wakeup))
    pthread_cond_wait(d->cond, d->mutex);
  pthread_mutex_unlock(d->mutex);
  d->timestamp= gettime();
  return d;
}

int
main(int argc, char **argv)
{
  int use_broadcast;
  int N;
  int i;
  struct thread_data *d;
  int index;
  int all_ready;
  double starttime, last_timestamp;

  if (argc != 3)
  {
    fprintf(stderr, "Usage: %s <number of threads> { signal | broadcast }\n", argv[0]);
    exit(1);
  }

  N= atoi(argv[1]);
  use_broadcast= (0 == strcmp("broadcast", argv[2]));

  d= malloc(sizeof(*d) * N);

  for (i= 0; i < N; i++)
  {
    if (i == 0 || !use_broadcast)
    {
      pthread_mutex_init(&d[i].i_mutex, NULL);
      pthread_cond_init(&d[i].i_cond, NULL);
      d[i].i_wakeup= 0;
    }
    if (use_broadcast)
      index= 0;
    else
      index= i;
    d[i].mutex= &d[index].i_mutex;
    d[i].cond= &d[index].i_cond;
    d[i].wakeup= &d[index].i_wakeup;
    pthread_create(&d[i].thread_id, NULL, my_thread, &d[i]);
  }

  do
  {
    sleep(1);
    all_ready= 1;
    for (i= 0; i < N; i++)
    {
      if (!d[i].ready)
      {
        all_ready= 0;
        break;
      }
    }
  } while (!all_ready);

  starttime= gettime();

  if (use_broadcast)
  {
    pthread_mutex_lock(&d[0].i_mutex);
    d[0].i_wakeup= 1;
    pthread_cond_broadcast(&d[0].i_cond);
    pthread_mutex_unlock(&d[0].i_mutex);
  }
  else
  {
    for (i= 0; i < N; i++)
    {
      pthread_mutex_lock(&d[i].i_mutex);
      d[i].i_wakeup= 1;
      pthread_cond_signal(&d[i].i_cond);
      pthread_mutex_unlock(&d[i].i_mutex);
    }
  }

  for (i= 0; i < N; i++)
  {
    void *dummy;
    pthread_join(d[i].thread_id, &dummy);
    if (i == 0 || d[i].timestamp > last_timestamp)
      last_timestamp= d[i].timestamp;
  }

  printf("Time for wakeup all using %s: %g seconds\n",
         (use_broadcast ? "broadcast" : "signal"),
         last_timestamp - starttime);

  free(d);

  return 0;
}


