You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
565 lines
12 KiB
565 lines
12 KiB
/*
|
|
* Ebizzy - replicate a large ebusiness type of workload.
|
|
*
|
|
* Written by Valerie Henson <val@nmt.edu>
|
|
*
|
|
* Copyright 2006 - 2007 Intel Corporation
|
|
* Copyright 2007 Valerie Henson <val@nmt.edu>
|
|
*
|
|
* Rodrigo Rubira Branco <rrbranco@br.ibm.com> - HP/BSD/Solaris port and some
|
|
* new features
|
|
*
|
|
* This program 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; version 2 of the License.
|
|
*
|
|
* This 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, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
* USA
|
|
*
|
|
*/
|
|
|
|
/*
|
|
* This program is designed to replicate a common web search app
|
|
* workload. A lot of search applications have the basic pattern: Get
|
|
* a request to find a certain record, index into the chunk of memory
|
|
* that contains it, copy it into another chunk, then look it up via
|
|
* binary search. The interesting parts of this workload are:
|
|
*
|
|
* Large working set
|
|
* Data alloc/copy/free cycle
|
|
* Unpredictable data access patterns
|
|
*
|
|
* Fiddle with the command line options until you get something
|
|
* resembling the kind of workload you want to investigate.
|
|
*
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <sys/mman.h>
|
|
#include <pthread.h>
|
|
#include <string.h>
|
|
#include <time.h>
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#include <malloc.h>
|
|
|
|
/*
|
|
* Command line options
|
|
*/
|
|
|
|
static unsigned int always_mmap;
|
|
static unsigned int never_mmap;
|
|
static uint64_t chunks;
|
|
static unsigned int use_permissions;
|
|
static unsigned int use_holes;
|
|
static unsigned int random_size;
|
|
static uint64_t chunk_size;
|
|
static uint64_t seconds;
|
|
static unsigned int threads;
|
|
static unsigned int verbose;
|
|
static unsigned int linear;
|
|
static unsigned int touch_pages;
|
|
static unsigned int no_lib_memcpy;
|
|
|
|
/*
|
|
* Other global variables
|
|
*/
|
|
|
|
typedef size_t record_t;
|
|
static unsigned int record_size = sizeof (record_t);
|
|
static char *cmd;
|
|
static record_t **mem;
|
|
static char **hole_mem;
|
|
static unsigned int page_size;
|
|
static volatile int threads_go;
|
|
static uint64_t records_read;
|
|
|
|
/* Global lock to serialize records aggregation */
|
|
pthread_mutex_t records_count_lock;
|
|
|
|
static void
|
|
usage(void)
|
|
{
|
|
fprintf(stderr, "Usage: %s [options]\n"
|
|
"-T\t\t Just 'touch' the allocated pages\n"
|
|
"-l\t\t Don't use library memcpy\n"
|
|
"-m\t\t Always use mmap instead of malloc\n"
|
|
"-M\t\t Never use mmap\n"
|
|
"-n <num>\t Number of memory chunks to allocate\n"
|
|
"-p \t\t Prevent mmap coalescing using permissions\n"
|
|
"-P \t\t Prevent mmap coalescing using holes\n"
|
|
"-R\t\t Randomize size of memory to copy and search\n"
|
|
"-s <size>\t Size of memory chunks, in bytes\n"
|
|
"-S <seconds>\t Number of seconds to run\n"
|
|
"-t <num>\t Number of threads (2 * number cpus by default)\n"
|
|
"-v[v[v]]\t Be verbose (more v's for more verbose)\n"
|
|
"-z\t\t Linear search instead of binary search\n",
|
|
cmd);
|
|
exit(1);
|
|
}
|
|
|
|
/*
|
|
* Read options, check them, and set some defaults.
|
|
*/
|
|
|
|
static void
|
|
read_options(int argc, char *argv[])
|
|
{
|
|
int c;
|
|
|
|
/* page_size = getpagesize(); */
|
|
page_size = 4096; // Hard-code it for now.
|
|
|
|
/*
|
|
* Set some defaults. These are currently tuned to run in a
|
|
* reasonable amount of time on my laptop.
|
|
*
|
|
* We could set the static defaults in the declarations, but
|
|
* then the defaults would be split between here and the top
|
|
* of the file, which is annoying.
|
|
*/
|
|
|
|
/* threads = 2 * sysconf(_SC_NPROCESSORS_ONLN); */
|
|
threads = 1;
|
|
chunks = 10;
|
|
chunk_size = record_size * 64 * 1024;
|
|
seconds = 10;
|
|
linear = 1; // Force linear search for now.
|
|
|
|
/* On to option processing */
|
|
|
|
cmd = argv[0];
|
|
|
|
while ((c = getopt(argc, argv, "lmMn:pPRs:S:t:vzT")) != -1) {
|
|
switch (c) {
|
|
case 'l':
|
|
no_lib_memcpy = 1;
|
|
break;
|
|
case 'm':
|
|
always_mmap = 1;
|
|
break;
|
|
case 'M':
|
|
never_mmap = 1;
|
|
break;
|
|
case 'n':
|
|
chunks = strtoul(optarg, NULL, 0);
|
|
if (chunks == 0)
|
|
usage();
|
|
break;
|
|
case 'p':
|
|
use_permissions = 1;
|
|
break;
|
|
case 'P':
|
|
use_holes = 1;
|
|
break;
|
|
case 'R':
|
|
random_size = 1;
|
|
break;
|
|
case 's':
|
|
chunk_size = strtoul(optarg, NULL, 0);
|
|
if (chunk_size == 0)
|
|
usage();
|
|
break;
|
|
case 'S':
|
|
seconds = strtoul(optarg, NULL, 0);
|
|
if (seconds == 0)
|
|
usage();
|
|
break;
|
|
case 't':
|
|
threads = atoi(optarg);
|
|
if (threads == 0)
|
|
usage();
|
|
break;
|
|
case 'T':
|
|
touch_pages = 1;
|
|
break;
|
|
case 'v':
|
|
verbose++;
|
|
break;
|
|
case 'z':
|
|
linear = 1;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
if (verbose)
|
|
printf("ebizzy 0.2\n"
|
|
"(C) 2006-7 Intel Corporation\n"
|
|
"(C) 2007 Valerie Henson <val@nmt.edu>\n");
|
|
|
|
if (verbose) {
|
|
printf("always_mmap %u\n", always_mmap);
|
|
printf("never_mmap %u\n", never_mmap);
|
|
printf("chunks %lu\n", chunks);
|
|
printf("prevent coalescing using permissions %u\n",
|
|
use_permissions);
|
|
printf("prevent coalescing using holes %u\n", use_holes);
|
|
printf("random_size %u\n", random_size);
|
|
printf("chunk_size %lu\n", chunk_size);
|
|
printf("seconds %lu\n", seconds);
|
|
printf("threads %u\n", threads);
|
|
printf("verbose %u\n", verbose);
|
|
printf("linear %u\n", linear);
|
|
printf("touch_pages %u\n", touch_pages);
|
|
printf("page size %d\n", page_size);
|
|
}
|
|
|
|
/* Check for incompatible options */
|
|
|
|
if (always_mmap && never_mmap) {
|
|
fprintf(stderr, "Both -m \"always mmap\" and -M "
|
|
"\"never mmap\" option specified\n");
|
|
usage();
|
|
}
|
|
|
|
#if 0
|
|
if (never_mmap)
|
|
mallopt(M_MMAP_MAX, 0);
|
|
#endif
|
|
|
|
if (chunk_size < record_size) {
|
|
fprintf(stderr, "Chunk size %lu smaller than record size %u\n",
|
|
chunk_size, record_size);
|
|
usage();
|
|
}
|
|
}
|
|
|
|
static void
|
|
touch_mem(char *dest, size_t size)
|
|
{
|
|
int i;
|
|
if (touch_pages) {
|
|
for (i = 0; i < size; i += page_size)
|
|
*(dest + i) = 0xff;
|
|
}
|
|
}
|
|
|
|
static void *
|
|
alloc_mem(size_t size)
|
|
{
|
|
char *p;
|
|
int err = 0;
|
|
|
|
if (always_mmap) {
|
|
p = mmap((void *) 0, size, (PROT_READ | PROT_WRITE),
|
|
(MAP_PRIVATE | MAP_ANONYMOUS), -1, 0);
|
|
if (p == MAP_FAILED)
|
|
err = 1;
|
|
} else {
|
|
p = malloc(size);
|
|
if (p == NULL)
|
|
err = 1;
|
|
}
|
|
|
|
if (err) {
|
|
fprintf(stderr, "Couldn't allocate %zu bytes, try smaller "
|
|
"chunks or size options\n"
|
|
"Using -n %lu chunks and -s %lu size\n",
|
|
size, chunks, chunk_size);
|
|
exit(1);
|
|
}
|
|
|
|
return (p);
|
|
}
|
|
|
|
static void
|
|
free_mem(void *p, size_t size)
|
|
{
|
|
if (always_mmap)
|
|
munmap(p, size);
|
|
else
|
|
free(p);
|
|
}
|
|
|
|
/*
|
|
* Factor out differences in memcpy implementation by optionally using
|
|
* our own simple memcpy implementation.
|
|
*/
|
|
|
|
static void
|
|
my_memcpy(void *dest, void *src, size_t len)
|
|
{
|
|
char *d = (char *) dest;
|
|
char *s = (char *) src;
|
|
int i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
d[i] = s[i];
|
|
return;
|
|
}
|
|
|
|
static void
|
|
allocate(void)
|
|
{
|
|
int i;
|
|
|
|
mem = alloc_mem(chunks * sizeof (record_t *));
|
|
|
|
if (use_holes)
|
|
hole_mem = alloc_mem(chunks * sizeof (record_t *));
|
|
|
|
|
|
for (i = 0; i < chunks; i++) {
|
|
mem[i] = (record_t *) alloc_mem(chunk_size);
|
|
/* Prevent coalescing using holes */
|
|
if (use_holes)
|
|
hole_mem[i] = alloc_mem(page_size);
|
|
}
|
|
|
|
/* Free hole memory */
|
|
if (use_holes)
|
|
for (i = 0; i < chunks; i++)
|
|
free_mem(hole_mem[i], page_size);
|
|
|
|
if (verbose)
|
|
printf("Allocated memory\n");
|
|
}
|
|
|
|
static void
|
|
write_pattern(void)
|
|
{
|
|
int i, j;
|
|
|
|
for (i = 0; i < chunks; i++) {
|
|
for(j = 0; j < chunk_size / record_size; j++)
|
|
mem[i][j] = (record_t) j;
|
|
|
|
#if 0 /* Biscuit doesn't support mprotect yet. */
|
|
/* Prevent coalescing by alternating permissions */
|
|
if (use_permissions && (i % 2) == 0)
|
|
mprotect((void *) mem[i], chunk_size, PROT_READ);
|
|
#endif
|
|
|
|
}
|
|
if (verbose)
|
|
printf("Wrote memory\n");
|
|
}
|
|
|
|
static void *
|
|
linear_search(record_t key, record_t *base, size_t size)
|
|
{
|
|
record_t *p;
|
|
record_t *end = base + (size / record_size);
|
|
|
|
for(p = base; p < end; p++)
|
|
if (*p == key)
|
|
return p;
|
|
return NULL;
|
|
}
|
|
|
|
#if 0
|
|
static int
|
|
compare(const void *p1, const void *p2)
|
|
{
|
|
return (* (record_t *) p1 - * (record_t *) p2);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Stupid ranged random number function. We don't care about quality.
|
|
*
|
|
* Inline because it's starting to be a scaling issue.
|
|
*/
|
|
|
|
static inline unsigned int
|
|
rand_num(unsigned int max, unsigned int *state)
|
|
{
|
|
*state = *state * 1103515245 + 12345;
|
|
return ((*state/65536) % max);
|
|
}
|
|
|
|
/*
|
|
* This function is the meat of the program; the rest is just support.
|
|
*
|
|
* In this function, we randomly select a memory chunk, copy it into a
|
|
* newly allocated buffer, randomly select a search key, look it up,
|
|
* then free the memory. An option tells us to allocate and copy a
|
|
* randomly sized chunk of the memory instead of the whole thing.
|
|
*
|
|
* Linear search provided for sanity checking.
|
|
*
|
|
*/
|
|
|
|
static unsigned int
|
|
search_mem(void)
|
|
{
|
|
record_t key, *found;
|
|
record_t *src, *copy;
|
|
unsigned int chunk;
|
|
size_t copy_size = chunk_size;
|
|
uint64_t i;
|
|
unsigned int state = 0x0c0ffee0;
|
|
|
|
for (i = 0; threads_go == 1; i++) {
|
|
chunk = rand_num(chunks, &state);
|
|
src = mem[chunk];
|
|
/*
|
|
* If we're doing random sizes, we need a non-zero
|
|
* multiple of record size.
|
|
*/
|
|
if (random_size)
|
|
copy_size = (rand_num(chunk_size / record_size, &state)
|
|
+ 1) * record_size;
|
|
copy = alloc_mem(copy_size);
|
|
|
|
if ( touch_pages ) {
|
|
touch_mem((char *) copy, copy_size);
|
|
} else {
|
|
|
|
if (no_lib_memcpy)
|
|
my_memcpy(copy, src, copy_size);
|
|
else
|
|
memcpy(copy, src, copy_size);
|
|
|
|
key = rand_num(copy_size / record_size, &state);
|
|
|
|
if (verbose > 2)
|
|
printf("[%lx] Search key %zu, copy size %zu\n",
|
|
pthread_self(), key, copy_size);
|
|
|
|
#if 1 // Force linear search for now.
|
|
found = linear_search(key, copy, copy_size);
|
|
#else
|
|
if (linear)
|
|
found = linear_search(key, copy, copy_size);
|
|
else
|
|
found = bsearch(&key, copy, copy_size / record_size,
|
|
record_size, compare);
|
|
#endif
|
|
|
|
/* Below check is mainly for memory corruption or other bugs */
|
|
if (found == NULL) {
|
|
fprintf(stderr, "Couldn't find key %zd\n", key);
|
|
exit(1);
|
|
}
|
|
} /* end if ! touch_pages */
|
|
|
|
free_mem(copy, copy_size);
|
|
}
|
|
|
|
return (i);
|
|
}
|
|
|
|
static void *
|
|
thread_run(void *arg)
|
|
{
|
|
uint64_t records_local;
|
|
|
|
if (verbose > 1)
|
|
printf("[%lx] Thread started\n", pthread_self());
|
|
|
|
/* Wait for the start signal */
|
|
|
|
while (threads_go == 0);
|
|
|
|
records_local = search_mem();
|
|
|
|
pthread_mutex_lock(&records_count_lock);
|
|
records_read += records_local;
|
|
pthread_mutex_unlock(&records_count_lock);
|
|
|
|
if (verbose > 1)
|
|
printf("[%lx] Thread finished, processed %lu records\n",
|
|
pthread_self(), records_local);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct timeval
|
|
difftimeval(struct timeval *end, struct timeval *start)
|
|
{
|
|
struct timeval diff;
|
|
diff.tv_sec = end->tv_sec - start->tv_sec;
|
|
diff.tv_usec = end->tv_usec - start->tv_usec;
|
|
return diff;
|
|
}
|
|
|
|
static void
|
|
start_threads(void)
|
|
{
|
|
pthread_t thread_array[threads];
|
|
double elapsed;
|
|
unsigned int i;
|
|
struct rusage start_ru, end_ru;
|
|
struct timeval start_time, end_time, usr_time, sys_time, elapsed_time;
|
|
int err;
|
|
|
|
if (verbose)
|
|
printf("Threads starting\n");
|
|
|
|
pthread_mutex_init(&records_count_lock, NULL);
|
|
|
|
for (i = 0; i < threads; i++) {
|
|
err = pthread_create(&thread_array[i], NULL, thread_run, NULL);
|
|
if (err) {
|
|
fprintf(stderr, "Error creating thread %d\n", i);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Begin accounting - this is when we actually do the things
|
|
* we want to measure. */
|
|
|
|
getrusage(RUSAGE_SELF, &start_ru);
|
|
gettimeofday(&start_time, NULL);
|
|
threads_go = 1;
|
|
sleep(seconds);
|
|
threads_go = 0;
|
|
gettimeofday(&end_time, NULL);
|
|
getrusage(RUSAGE_SELF, &end_ru);
|
|
|
|
/*
|
|
* The rest is just clean up.
|
|
*/
|
|
|
|
for (i = 0; i < threads; i++) {
|
|
err = pthread_join(thread_array[i], NULL);
|
|
if (err) {
|
|
fprintf(stderr, "Error joining thread %d\n", i);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
elapsed_time = difftimeval(&end_time, &start_time);
|
|
usr_time = difftimeval(&end_ru.ru_utime, &start_ru.ru_utime);
|
|
sys_time = difftimeval(&end_ru.ru_stime, &start_ru.ru_stime);
|
|
|
|
elapsed = elapsed_time.tv_sec + elapsed_time.tv_usec/1e6;
|
|
|
|
if (verbose)
|
|
printf("Threads finished\n");
|
|
|
|
printf("%lu records/s\n",
|
|
(uint64_t) (((double) records_read)/elapsed));
|
|
|
|
printf("real %f s\n", elapsed);
|
|
printf("user %f s\n", usr_time.tv_sec + usr_time.tv_usec/1e6);
|
|
printf("sys %f s\n", sys_time.tv_sec + sys_time.tv_usec/1e6);
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
read_options(argc, argv);
|
|
|
|
allocate();
|
|
|
|
write_pattern();
|
|
|
|
start_threads();
|
|
|
|
return 0;
|
|
}
|