/*
 * fcyc.c - Estimate the time (in CPU cycles) used by a function f
 *
 * Copyright (c) 2002, R. Bryant and D. O'Hallaron, All rights reserved.
 * May not be used, modified, or copied without permission.
 *
 * Uses the cycle timer routines in clock.c to estimate the
 * the time in CPU cycles for a function f.
 */
#include <stdlib.h>
#include <time.h>
#include <stdio.h>

#include "fcyc.h"
#include "clock.h"

 /* Default values */
#define K 3                  /* Value of K in K-best scheme */
#define MAXSAMPLES 20        /* Give up after MAXSAMPLES */
#define EPSILON 0.01         /* K samples should be EPSILON of each other*/
#define CLEAR_CACHE 0        /* Clear cache before running test function */
#define CACHE_BYTES (1<<19)  /* Max cache size in bytes */
#define CACHE_BLOCK 32       /* Cache block size in bytes */

static int kbest = K;
static int maxsamples = MAXSAMPLES;
static double epsilon = EPSILON;
static int clear_cache = CLEAR_CACHE;
static int cache_bytes = CACHE_BYTES;
static int cache_block = CACHE_BLOCK;

static int* cache_buf = NULL;

static double* values = NULL;
static int samplecount = 0;

/* for debugging only */
#define KEEP_VALS 0
#define KEEP_SAMPLES 0

#if KEEP_SAMPLES
static double* samples = NULL;
#endif

/*
 * init_sampler - Start new sampling process
 */
static void init_sampler(void)
{
	if (values)
		free(values);
	values = calloc(kbest, sizeof(double));
#if KEEP_SAMPLES
	if (samples)
		free(samples);
	/* Allocate extra for wraparound analysis */
	samples = calloc(maxsamples + kbest, sizeof(double));
#endif
	samplecount = 0;
}

/*
 * add_sample - Add new sample
 */
static void add_sample(double val)
{
	int pos = 0;
	if (samplecount < kbest) {
		pos = samplecount;
		values[pos] = val;
	}
	else if (val < values[kbest - 1]) {
		pos = kbest - 1;
		values[pos] = val;
	}
#if KEEP_SAMPLES
	samples[samplecount] = val;
#endif
	samplecount++;
	/* Insertion sort */
	while (pos > 0 && values[pos - 1] > values[pos]) {
		double temp = values[pos - 1];
		values[pos - 1] = values[pos];
		values[pos] = temp;
		pos--;
	}
}

/*
 * has_converged- Have kbest minimum measurements converged within epsilon?
 */
static int has_converged(void)
{
	return
		(samplecount >= kbest) &&
		((1 + epsilon) * values[0] >= values[kbest - 1]);
}

/*
 * clear - Code to clear cache
 */
static volatile int sink = 0;

static void clear(void)
{
	int x = sink;
	int* cptr, * cend;
	int incr = cache_block / sizeof(int);
	if (!cache_buf) {
		cache_buf = malloc(cache_bytes);
		if (!cache_buf) {
			printf("[%s]致命错误：当试图清除cache时，malloc返回null\n", __func__);
			exit(1);
		}
	}
	cptr = (int*)cache_buf;
	cend = cptr + cache_bytes / sizeof(int);
	while (cptr < cend) {
		x += *cptr;
		cptr += incr;
	}
	sink = x;
}

/*
 * fcyc - Use K-best scheme to estimate the running time of function f
 */
double fcyc(test_funct f, void* argp)
{
	double result;
	init_sampler();
	make_CPU_busy();
	do {
		double cyc;
		if (clear_cache)
			clear();
		start_counter();
		f(argp);
		cyc = get_counter();
		add_sample(cyc);
	} while (!has_converged() && samplecount < maxsamples);
#ifdef DEBUG
	{
		int i;
		printf(" %d smallest values: [", kbest);
		for (i = 0; i < kbest; i++)
			printf("%.0f%s", values[i], i == kbest - 1 ? "]\n" : ", ");
	}
#endif
	result = values[0];
#if !KEEP_VALS
	free(values);
	values = NULL;
#endif
	return result;
}


/*************************************************************
 * Set the various parameters used by the measurement routines
 ************************************************************/

 /*
  * set_fcyc_clear_cache - When set, will run code to clear cache
  *     before each measurement.
  *     Default = 0
  */
void set_fcyc_clear_cache(int clear)
{
	clear_cache = clear;
}

/*
 * set_fcyc_cache_size - Set size of cache to use when clearing cache
 *     Default = 1<<19 (512KB)
 */
void set_fcyc_cache_size(int bytes)
{
	if (bytes != cache_bytes) {
		cache_bytes = bytes;
		if (cache_buf) {
			free(cache_buf);
			cache_buf = NULL;
		}
	}
}

/*
 * set_fcyc_cache_block - Set size of cache block
 *     Default = 32
 */
void set_fcyc_cache_block(int bytes) {
	cache_block = bytes;
}



/*
 * set_fcyc_k - Value of K in K-best measurement scheme
 *     Default = 3
 */
void set_fcyc_k(int k)
{
	kbest = k;
}

/*
 * set_fcyc_maxsamples - Maximum number of samples attempting to find
 *     K-best within some tolerance.
 *     When exceeded, just return best sample found.
 *     Default = 20
 */
void set_fcyc_maxsamples(int maxsamples_arg)
{
	maxsamples = maxsamples_arg;
}

/*
 * set_fcyc_epsilon - Tolerance required for K-best
 *     Default = 0.01
 */
void set_fcyc_epsilon(double epsilon_arg)
{
	epsilon = epsilon_arg;
}





