git/t/helper/test-lazy-init-name-hash.c
Eric Wong 23dee69f53 OFFSETOF_VAR macro to simplify hashmap iterators
While we cannot rely on a `__typeof__' operator being portable
to use with `offsetof'; we can calculate the pointer offset
using an existing pointer and the address of a member using
pointer arithmetic for compilers without `__typeof__'.

This allows us to simplify usage of hashmap iterator macros
by not having to specify a type when a pointer of that type
is already given.

In the future, list iterator macros (e.g. list_for_each_entry)
may also be implemented using OFFSETOF_VAR to save hackers the
trouble of using container_of/list_entry macros and without
relying on non-portable `__typeof__'.

v3: use `__typeof__' to avoid clang warnings

Signed-off-by: Eric Wong <e@80x24.org>
Reviewed-by: Derrick Stolee <stolee@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2019-10-07 10:20:11 +09:00

262 lines
6.2 KiB
C

#include "test-tool.h"
#include "cache.h"
#include "parse-options.h"
static int single;
static int multi;
static int count = 1;
static int dump;
static int perf;
static int analyze;
static int analyze_step;
/*
* Dump the contents of the "dir" and "name" hash tables to stdout.
* If you sort the result, you can compare it with the other type
* mode and verify that both single and multi produce the same set.
*/
static void dump_run(void)
{
struct hashmap_iter iter_dir;
struct hashmap_iter iter_cache;
/* Stolen from name-hash.c */
struct dir_entry {
struct hashmap_entry ent;
struct dir_entry *parent;
int nr;
unsigned int namelen;
char name[FLEX_ARRAY];
};
struct dir_entry *dir;
struct cache_entry *ce;
read_cache();
if (single) {
test_lazy_init_name_hash(&the_index, 0);
} else {
int nr_threads_used = test_lazy_init_name_hash(&the_index, 1);
if (!nr_threads_used)
die("non-threaded code path used");
}
hashmap_for_each_entry(&the_index.dir_hash, &iter_dir, dir,
ent /* member name */)
printf("dir %08x %7d %s\n", dir->ent.hash, dir->nr, dir->name);
hashmap_for_each_entry(&the_index.name_hash, &iter_cache, ce,
ent /* member name */)
printf("name %08x %s\n", ce->ent.hash, ce->name);
discard_cache();
}
/*
* Run the single or multi threaded version "count" times and
* report on the time taken.
*/
static uint64_t time_runs(int try_threaded)
{
uint64_t t0, t1, t2;
uint64_t sum = 0;
uint64_t avg;
int nr_threads_used;
int i;
for (i = 0; i < count; i++) {
t0 = getnanotime();
read_cache();
t1 = getnanotime();
nr_threads_used = test_lazy_init_name_hash(&the_index, try_threaded);
t2 = getnanotime();
sum += (t2 - t1);
if (try_threaded && !nr_threads_used)
die("non-threaded code path used");
if (nr_threads_used)
printf("%f %f %d multi %d\n",
((double)(t1 - t0))/1000000000,
((double)(t2 - t1))/1000000000,
the_index.cache_nr,
nr_threads_used);
else
printf("%f %f %d single\n",
((double)(t1 - t0))/1000000000,
((double)(t2 - t1))/1000000000,
the_index.cache_nr);
fflush(stdout);
discard_cache();
}
avg = sum / count;
if (count > 1)
printf("avg %f %s\n",
(double)avg/1000000000,
(try_threaded) ? "multi" : "single");
return avg;
}
/*
* Try a series of runs varying the "istate->cache_nr" and
* try to find a good value for the multi-threaded criteria.
*/
static void analyze_run(void)
{
uint64_t t1s, t1m, t2s, t2m;
int cache_nr_limit;
int nr_threads_used = 0;
int i;
int nr;
read_cache();
cache_nr_limit = the_index.cache_nr;
discard_cache();
nr = analyze;
while (1) {
uint64_t sum_single = 0;
uint64_t sum_multi = 0;
uint64_t avg_single;
uint64_t avg_multi;
if (nr > cache_nr_limit)
nr = cache_nr_limit;
for (i = 0; i < count; i++) {
read_cache();
the_index.cache_nr = nr; /* cheap truncate of index */
t1s = getnanotime();
test_lazy_init_name_hash(&the_index, 0);
t2s = getnanotime();
sum_single += (t2s - t1s);
the_index.cache_nr = cache_nr_limit;
discard_cache();
read_cache();
the_index.cache_nr = nr; /* cheap truncate of index */
t1m = getnanotime();
nr_threads_used = test_lazy_init_name_hash(&the_index, 1);
t2m = getnanotime();
sum_multi += (t2m - t1m);
the_index.cache_nr = cache_nr_limit;
discard_cache();
if (!nr_threads_used)
printf(" [size %8d] [single %f] non-threaded code path used\n",
nr, ((double)(t2s - t1s))/1000000000);
else
printf(" [size %8d] [single %f] %c [multi %f %d]\n",
nr,
((double)(t2s - t1s))/1000000000,
(((t2s - t1s) < (t2m - t1m)) ? '<' : '>'),
((double)(t2m - t1m))/1000000000,
nr_threads_used);
fflush(stdout);
}
if (count > 1) {
avg_single = sum_single / count;
avg_multi = sum_multi / count;
if (!nr_threads_used)
printf("avg [size %8d] [single %f]\n",
nr,
(double)avg_single/1000000000);
else
printf("avg [size %8d] [single %f] %c [multi %f %d]\n",
nr,
(double)avg_single/1000000000,
(avg_single < avg_multi ? '<' : '>'),
(double)avg_multi/1000000000,
nr_threads_used);
fflush(stdout);
}
if (nr >= cache_nr_limit)
return;
nr += analyze_step;
}
}
int cmd__lazy_init_name_hash(int argc, const char **argv)
{
const char *usage[] = {
"test-tool lazy-init-name-hash -d (-s | -m)",
"test-tool lazy-init-name-hash -p [-c c]",
"test-tool lazy-init-name-hash -a a [--step s] [-c c]",
"test-tool lazy-init-name-hash (-s | -m) [-c c]",
"test-tool lazy-init-name-hash -s -m [-c c]",
NULL
};
struct option options[] = {
OPT_BOOL('s', "single", &single, "run single-threaded code"),
OPT_BOOL('m', "multi", &multi, "run multi-threaded code"),
OPT_INTEGER('c', "count", &count, "number of passes"),
OPT_BOOL('d', "dump", &dump, "dump hash tables"),
OPT_BOOL('p', "perf", &perf, "compare single vs multi"),
OPT_INTEGER('a', "analyze", &analyze, "analyze different multi sizes"),
OPT_INTEGER(0, "step", &analyze_step, "analyze step factor"),
OPT_END(),
};
const char *prefix;
uint64_t avg_single, avg_multi;
prefix = setup_git_directory();
argc = parse_options(argc, argv, prefix, options, usage, 0);
/*
* istate->dir_hash is only created when ignore_case is set.
*/
ignore_case = 1;
if (dump) {
if (perf || analyze > 0)
die("cannot combine dump, perf, or analyze");
if (count > 1)
die("count not valid with dump");
if (single && multi)
die("cannot use both single and multi with dump");
if (!single && !multi)
die("dump requires either single or multi");
dump_run();
return 0;
}
if (perf) {
if (analyze > 0)
die("cannot combine dump, perf, or analyze");
if (single || multi)
die("cannot use single or multi with perf");
avg_single = time_runs(0);
avg_multi = time_runs(1);
if (avg_multi > avg_single)
die("multi is slower");
return 0;
}
if (analyze) {
if (analyze < 500)
die("analyze must be at least 500");
if (!analyze_step)
analyze_step = analyze;
if (single || multi)
die("cannot use single or multi with analyze");
analyze_run();
return 0;
}
if (!single && !multi)
die("require either -s or -m or both");
if (single)
time_runs(0);
if (multi)
time_runs(1);
return 0;
}