Skip to content

Commit c55c60f

Browse files
jeffhostetlerdscho
authored andcommitted
name-hash: add test-lazy-init-name-hash
Add t/helper/test-lazy-init-name-hash.c test code to demonstrate performance times for lazy_init_name_hash() using the original single-threaded and the new multi-threaded code paths. Includes a --dump option to dump the created hashmaps to stdout. You can use this to run both code paths and confirm that they generate the same hashmaps. Includes a --analyze option to analyze performance of both code paths over a range of index sizes to help you find a lower bound for the LAZY_THREAD_COST in name-hash.c. For example, passing "-a 4000" will set "istate.cache_nr" to 4000 and then try the multi-threaded code -- probably giving 2 threads with 2000 entries each. It will then run both the single-threaded (1x4000) and the multi-threaded (2x2000) and compare the times. It will then repeat the test with 8000, 12000, and etc. so that you can see the cross over. Signed-off-by: Jeff Hostetler <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 75b57b3 commit c55c60f

File tree

3 files changed

+266
-0
lines changed

3 files changed

+266
-0
lines changed

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ TEST_PROGRAMS_NEED_X += test-fake-ssh
614614
TEST_PROGRAMS_NEED_X += test-genrandom
615615
TEST_PROGRAMS_NEED_X += test-hashmap
616616
TEST_PROGRAMS_NEED_X += test-index-version
617+
TEST_PROGRAMS_NEED_X += test-lazy-init-name-hash
617618
TEST_PROGRAMS_NEED_X += test-line-buffer
618619
TEST_PROGRAMS_NEED_X += test-match-trees
619620
TEST_PROGRAMS_NEED_X += test-mergesort

cache.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ struct index_state {
343343
extern struct index_state the_index;
344344

345345
/* Name hashing */
346+
extern int test_lazy_init_name_hash(struct index_state *istate, int try_threaded);
346347
extern void add_name_hash(struct index_state *istate, struct cache_entry *ce);
347348
extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
348349
extern void free_name_hash(struct index_state *istate);

t/helper/test-lazy-init-name-hash.c

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
#include "cache.h"
2+
#include "parse-options.h"
3+
4+
static int single;
5+
static int multi;
6+
static int count = 1;
7+
static int dump;
8+
static int perf;
9+
static int analyze;
10+
static int analyze_step;
11+
12+
/*
13+
* Dump the contents of the "dir" and "name" hash tables to stdout.
14+
* If you sort the result, you can compare it with the other type
15+
* mode and verify that both single and multi produce the same set.
16+
*/
17+
static void dump_run(void)
18+
{
19+
struct hashmap_iter iter_dir;
20+
struct hashmap_iter iter_cache;
21+
22+
/* Stolen from name-hash.c */
23+
struct dir_entry {
24+
struct hashmap_entry ent;
25+
struct dir_entry *parent;
26+
int nr;
27+
unsigned int namelen;
28+
char name[FLEX_ARRAY];
29+
};
30+
31+
struct dir_entry *dir;
32+
struct cache_entry *ce;
33+
34+
read_cache();
35+
if (single) {
36+
test_lazy_init_name_hash(&the_index, 0);
37+
} else {
38+
int nr_threads_used = test_lazy_init_name_hash(&the_index, 1);
39+
if (!nr_threads_used)
40+
die("non-threaded code path used");
41+
}
42+
43+
dir = hashmap_iter_first(&the_index.dir_hash, &iter_dir);
44+
while (dir) {
45+
printf("dir %08x %7d %s\n", dir->ent.hash, dir->nr, dir->name);
46+
dir = hashmap_iter_next(&iter_dir);
47+
}
48+
49+
ce = hashmap_iter_first(&the_index.name_hash, &iter_cache);
50+
while (ce) {
51+
printf("name %08x %s\n", ce->ent.hash, ce->name);
52+
ce = hashmap_iter_next(&iter_cache);
53+
}
54+
55+
discard_cache();
56+
}
57+
58+
/*
59+
* Run the single or multi threaded version "count" times and
60+
* report on the time taken.
61+
*/
62+
static uint64_t time_runs(int try_threaded)
63+
{
64+
uint64_t t0, t1, t2;
65+
uint64_t sum = 0;
66+
uint64_t avg;
67+
int nr_threads_used;
68+
int i;
69+
70+
for (i = 0; i < count; i++) {
71+
t0 = getnanotime();
72+
read_cache();
73+
t1 = getnanotime();
74+
nr_threads_used = test_lazy_init_name_hash(&the_index, try_threaded);
75+
t2 = getnanotime();
76+
77+
sum += (t2 - t1);
78+
79+
if (try_threaded && !nr_threads_used)
80+
die("non-threaded code path used");
81+
82+
if (nr_threads_used)
83+
printf("%f %f %d multi %d\n",
84+
((double)(t1 - t0))/1000000000,
85+
((double)(t2 - t1))/1000000000,
86+
the_index.cache_nr,
87+
nr_threads_used);
88+
else
89+
printf("%f %f %d single\n",
90+
((double)(t1 - t0))/1000000000,
91+
((double)(t2 - t1))/1000000000,
92+
the_index.cache_nr);
93+
fflush(stdout);
94+
95+
discard_cache();
96+
}
97+
98+
avg = sum / count;
99+
if (count > 1)
100+
printf("avg %f %s\n",
101+
(double)avg/1000000000,
102+
(try_threaded) ? "multi" : "single");
103+
104+
return avg;
105+
}
106+
107+
/*
108+
* Try a series of runs varying the "istate->cache_nr" and
109+
* try to find a good value for the multi-threaded criteria.
110+
*/
111+
static void analyze_run(void)
112+
{
113+
uint64_t t1s, t1m, t2s, t2m;
114+
int cache_nr_limit;
115+
int nr_threads_used;
116+
int i;
117+
int nr;
118+
119+
read_cache();
120+
cache_nr_limit = the_index.cache_nr;
121+
discard_cache();
122+
123+
nr = analyze;
124+
while (1) {
125+
uint64_t sum_single = 0;
126+
uint64_t sum_multi = 0;
127+
uint64_t avg_single;
128+
uint64_t avg_multi;
129+
130+
if (nr > cache_nr_limit)
131+
nr = cache_nr_limit;
132+
133+
for (i = 0; i < count; i++) {
134+
read_cache();
135+
the_index.cache_nr = nr; /* cheap truncate of index */
136+
t1s = getnanotime();
137+
test_lazy_init_name_hash(&the_index, 0);
138+
t2s = getnanotime();
139+
sum_single += (t2s - t1s);
140+
the_index.cache_nr = cache_nr_limit;
141+
discard_cache();
142+
143+
read_cache();
144+
the_index.cache_nr = nr; /* cheap truncate of index */
145+
t1m = getnanotime();
146+
nr_threads_used = test_lazy_init_name_hash(&the_index, 1);
147+
t2m = getnanotime();
148+
sum_multi += (t2m - t1m);
149+
the_index.cache_nr = cache_nr_limit;
150+
discard_cache();
151+
152+
if (!nr_threads_used)
153+
printf(" [size %8d] [single %f] non-threaded code path used\n",
154+
nr, ((double)(t2s - t1s))/1000000000);
155+
else
156+
printf(" [size %8d] [single %f] %c [multi %f %d]\n",
157+
nr,
158+
((double)(t2s - t1s))/1000000000,
159+
(((t2s - t1s) < (t2m - t1m)) ? '<' : '>'),
160+
((double)(t2m - t1m))/1000000000,
161+
nr_threads_used);
162+
fflush(stdout);
163+
}
164+
if (count > 1) {
165+
avg_single = sum_single / count;
166+
avg_multi = sum_multi / count;
167+
if (!nr_threads_used)
168+
printf("avg [size %8d] [single %f]\n",
169+
nr,
170+
(double)avg_single/1000000000);
171+
else
172+
printf("avg [size %8d] [single %f] %c [multi %f %d]\n",
173+
nr,
174+
(double)avg_single/1000000000,
175+
(avg_single < avg_multi ? '<' : '>'),
176+
(double)avg_multi/1000000000,
177+
nr_threads_used);
178+
fflush(stdout);
179+
}
180+
181+
if (nr >= cache_nr_limit)
182+
return;
183+
nr += analyze_step;
184+
}
185+
}
186+
187+
int cmd_main(int argc, const char **argv)
188+
{
189+
const char *usage[] = {
190+
"test-lazy-init-name-hash -d (-s | -m)",
191+
"test-lazy-init-name-hash -p [-c c]",
192+
"test-lazy-init-name-hash -a a [--step s] [-c c]",
193+
"test-lazy-init-name-hash (-s | -m) [-c c]",
194+
"test-lazy-init-name-hash -s -m [-c c]",
195+
NULL
196+
};
197+
struct option options[] = {
198+
OPT_BOOL('s', "single", &single, "run single-threaded code"),
199+
OPT_BOOL('m', "multi", &multi, "run multi-threaded code"),
200+
OPT_INTEGER('c', "count", &count, "number of passes"),
201+
OPT_BOOL('d', "dump", &dump, "dump hash tables"),
202+
OPT_BOOL('p', "perf", &perf, "compare single vs multi"),
203+
OPT_INTEGER('a', "analyze", &analyze, "analyze different multi sizes"),
204+
OPT_INTEGER(0, "step", &analyze_step, "analyze step factor"),
205+
OPT_END(),
206+
};
207+
const char *prefix;
208+
uint64_t avg_single, avg_multi;
209+
210+
prefix = setup_git_directory();
211+
212+
argc = parse_options(argc, argv, prefix, options, usage, 0);
213+
214+
/*
215+
* istate->dir_hash is only created when ignore_case is set.
216+
*/
217+
ignore_case = 1;
218+
219+
if (dump) {
220+
if (perf || analyze > 0)
221+
die("cannot combine dump, perf, or analyze");
222+
if (count > 1)
223+
die("count not valid with dump");
224+
if (single && multi)
225+
die("cannot use both single and multi with dump");
226+
if (!single && !multi)
227+
die("dump requires either single or multi");
228+
dump_run();
229+
return 0;
230+
}
231+
232+
if (perf) {
233+
if (analyze > 0)
234+
die("cannot combine dump, perf, or analyze");
235+
if (single || multi)
236+
die("cannot use single or multi with perf");
237+
avg_single = time_runs(0);
238+
avg_multi = time_runs(1);
239+
if (avg_multi > avg_single)
240+
die("multi is slower");
241+
return 0;
242+
}
243+
244+
if (analyze) {
245+
if (analyze < 500)
246+
die("analyze must be at least 500");
247+
if (!analyze_step)
248+
analyze_step = analyze;
249+
if (single || multi)
250+
die("cannot use single or multi with analyze");
251+
analyze_run();
252+
return 0;
253+
}
254+
255+
if (!single && !multi)
256+
die("require either -s or -m or both");
257+
258+
if (single)
259+
time_runs(0);
260+
if (multi)
261+
time_runs(1);
262+
263+
return 0;
264+
}

0 commit comments

Comments
 (0)