{"thread":{"id":"22187","subject":"[PATCH v2] Threaded grep","startedAt":"2010-01-12T23:44:41Z","lastAt":"2010-01-12T23:44:41Z","messageCount":1,"participants":["Fredrik Kuivinen"],"isPatch":true,"patchVersion":2,"patchTotal":null},"messages":[{"id":"131412","messageId":"20100112234441.GA19578@fredrik-laptop","threadId":"22187","inReplyTo":null,"subject":"[PATCH v2] Threaded grep","fromName":"Fredrik Kuivinen","fromEmail":"frekui@gmail.com","sentAt":"2010-01-12T23:44:41Z","receivedAt":"2010-01-12T23:44:41Z","isPatch":true,"sender":{"key":"frekui@gmail.com","avatar":"https://avatars.githubusercontent.com/u/13770967?v=4"},"body":"This the second version of the threaded grep patch. It applies cleanly\non top of pu.\n\nTwo environment variables can be used to control the threading. If\nNO_THREADS is set, then the old non-threaded code will be used.\nTHREADS can be set to an integer to specify the number of threads that\nshould be used. If neither NO_THREADS nor THREADS is set, 8 threads\nare used.\n\nThe results below are best of five runs in the Linux repository on a\nbox with two cores.\n\ngit grep --no-ext-grep qwerty\n\n4.38user 0.57system 0:02.69elapsed 183%CPU (0avgtext+0avgdata\n0maxresident)k 0inputs+784outputs (0major+6206minor)pagefaults 0swaps\n\nNO_THREADS=1 git grep --no-ext-grep qwerty\n4.33user 0.48system 0:04.82elapsed 99%CPU (0avgtext+0avgdata\n0maxresident)k 0inputs+784outputs (0major+3523minor)pagefaults 0swaps\n\n\nAnd with a pattern with quite a few matches:\n\ngit grep --no-ext-grep function\n6.28user 0.63system 0:03.97elapsed 173%CPU (0avgtext+0avgdata\n0maxresident)k 0inputs+784outputs (0major+8101minor)pagefaults 0swaps\n\nNO_THREADS=1 git grep --no-ext-grep function\n6.21user 0.50system 0:06.71elapsed 100%CPU (0avgtext+0avgdata\n0maxresident)k 0inputs+784outputs (0major+3523minor)pagefaults 0swaps\n\n\nIn either case we gain about 40% by the threading. More testing is, as\nalways, greatly appreciated.\n\nThe patch needs some further clean-ups before it can be considered for\napplication.\n\n---\nMajor changes since v1:\n\n* Buffer the output from the threads instead of waiting until the\n  result can be written to stdout.\n\n* Limit the maximum number of pending work_items.\n\n\n builtin-grep.c |  340 +++++++++++++++++++++++++++++++++++++++++++++++++++++---\n grep.c         |   85 ++++++++++++--\n grep.h         |    6 +\n strbuf.c       |   19 ++-\n strbuf.h       |    1 \n 5 files changed, 413 insertions(+), 38 deletions(-)\n\ndiff --git a/builtin-grep.c b/builtin-grep.c\nindex 59c4b12..ebeede9 100644\n--- a/builtin-grep.c\n+++ b/builtin-grep.c\n@@ -15,6 +15,13 @@\n #include \"grep.h\"\n #include \"quote.h\"\n \n+#ifdef THREADED_DELTA_SEARCH\n+#include \"thread-utils.h\"\n+#include <pthread.h>\n+#include <sys/types.h>\n+#include <sys/syscall.h>\n+#endif\n+\n #ifndef NO_EXTERNAL_GREP\n #ifdef __unix__\n #define NO_EXTERNAL_GREP 0\n@@ -23,6 +30,262 @@\n #endif\n #endif\n \n+static int use_threads = 1;\n+\n+#ifdef THREADED_DELTA_SEARCH\n+\n+static void* load_file(const char *filename, size_t *sz);\n+\n+enum work_type {WORK_BUF, WORK_FILE};\n+\n+/* We use one producer thread and number_of_threads consumer\n+   threads. The producer adds struct work_items to 'todo' and the\n+   consumers pick work items from the same array. */\n+struct work_item\n+{\n+\tenum work_type type;\n+\tchar *name;\n+\n+\t/* if type == WORK_BUF, then 'buf' points to a buffer of size\n+\t   'size' otherwise type == WORK_FILE and 'buf' is a nul\n+\t   terminated filename. */\n+\tchar *buf;\n+\tsize_t size;\n+\tchar done;\n+\tstruct strbuf out;\n+};\n+\n+/* Number of consumer threads. */\n+static int number_of_threads;\n+static pthread_t *threads;\n+\n+/* In the range [todo_done, todo_start) in 'todo' we have work_items\n+   that have been or are processed by a consumer thread. We haven't\n+   written the result for these to stdout yet.\n+\n+   The work_items in [todo_start, todo_end) are waiting to be picked\n+   up by a consumer thread.\n+\n+   The ranges are modulo todo_alloc.\n+*/\n+static struct work_item *todo = NULL;\n+static int todo_alloc = 0;\n+static int todo_start = 0;\n+static int todo_end = 0;\n+static int todo_done = 0;\n+\n+/* Has all work items been added? */\n+static int all_work_added = 0;\n+\n+/* This lock protects all the variables above. */\n+static pthread_mutex_t grep_lock = PTHREAD_MUTEX_INITIALIZER;\n+\n+/* Signalled when a new work_item is added to todo. */\n+static pthread_cond_t cond_add = PTHREAD_COND_INITIALIZER;\n+\n+/* Signalled when the result from one work_item is written to\n+   stdout. */\n+static pthread_cond_t cond_write = PTHREAD_COND_INITIALIZER;\n+\n+/* Signalled when we are finished with everything. */\n+static pthread_cond_t cond_result = PTHREAD_COND_INITIALIZER;\n+\n+static void add_work(enum work_type type, char *name, char *buf, size_t size)\n+{\n+\tpthread_mutex_lock(&grep_lock);\n+\n+\twhile ((todo_end+1) % todo_alloc == todo_done) {\n+\t\tpthread_cond_wait(&cond_write, &grep_lock);\n+\t}\n+\n+\ttodo[todo_end].type = type;\n+\ttodo[todo_end].name = name;\n+\ttodo[todo_end].buf = buf;\n+\ttodo[todo_end].size = size;\n+\ttodo[todo_end].done = 0;\n+\tstrbuf_reset(&todo[todo_end].out);\n+\ttodo_end = (todo_end + 1) % todo_alloc;\n+\n+\tpthread_mutex_unlock(&grep_lock);\n+\tpthread_cond_signal(&cond_add);\n+}\n+\n+static struct work_item* get_work()\n+{\n+\tstruct work_item* ret;\n+\n+\tpthread_mutex_lock(&grep_lock);\n+\twhile (todo_start == todo_end && !all_work_added) {\n+\t\tpthread_cond_wait(&cond_add, &grep_lock);\n+\t}\n+\n+\tif (todo_start == todo_end && all_work_added) {\n+\t\tret = NULL;\n+\t} else {\n+\t\tret = &todo[todo_start];\n+\t\ttodo_start = (todo_start + 1) % todo_alloc;\n+\t}\n+\tpthread_mutex_unlock(&grep_lock);\n+\treturn ret;\n+}\n+\n+/* This function takes ownership of 'name' and 'buf'. They will be\n+   deallocated with free. */\n+static int grep_buffer_async(struct grep_opt *opt, char *name, char *buf,\n+\t\t\t     size_t size)\n+{\n+\tadd_work(WORK_BUF, name, buf, size);\n+\treturn 0;\n+}\n+\n+static int grep_file_async(struct grep_opt *opt, char *name,\n+\t\t\t   const char *filename)\n+{\n+\tadd_work(WORK_FILE, name, (char*) filename, 0);\n+\treturn 0;\n+}\n+\n+static void work_done(struct work_item* w)\n+{\n+\tint old_done;\n+\n+\tpthread_mutex_lock(&grep_lock);\n+\tw->done = 1;\n+\told_done = todo_done;\n+\tfor(; todo[todo_done].done && todo_done != todo_start;\n+\t    todo_done = (todo_done+1) % todo_alloc) {\n+\t\tw = &todo[todo_done];\n+\t\twrite_or_die(1, w->out.buf, w->out.len);\n+\t\tif (w->type == WORK_BUF)\n+\t\t\tfree(w->buf);\n+\n+\t\tfree(w->name);\n+\t}\n+\n+\tif (old_done != todo_done)\n+\t\tpthread_cond_signal(&cond_write);\n+\n+\tif (all_work_added && todo_done == todo_end)\n+\t\tpthread_cond_signal(&cond_result);\n+\n+\tpthread_mutex_unlock(&grep_lock);\n+}\n+\n+static void* run(void *arg)\n+{\n+\tint hit = 0;\n+\tstruct grep_opt *opt = arg;\n+\n+\twhile (1) {\n+\t\tstruct work_item *w = get_work();\n+\t\tif (!w)\n+\t\t\tbreak;\n+\n+\t\topt->output_priv = w;\n+\t\tif (w->type == WORK_BUF) {\n+\t\t\thit |= grep_buffer(opt, w->name, w->buf, w->size);\n+\t\t} else {\n+\t\t\tsize_t sz;\n+\t\t\tvoid* data = load_file(w->buf, &sz);\n+\t\t\tif (data) {\n+\t\t\t\thit |= grep_buffer(opt, w->name, data, sz);\n+\t\t\t\tfree(data);\n+\t\t\t}\n+\t\t}\n+\n+\t\twork_done(w);\n+\t}\n+\n+\treturn (void*) (intptr_t) hit;\n+}\n+\n+static void strbuf_out(struct grep_opt *opt, const char *fmt, ...)\n+{\n+\tstruct work_item *w = opt->output_priv;\n+\tva_list ap;\n+\n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(&w->out, fmt, ap);\n+\tva_end(ap);\n+}\n+\n+static void start_threads(struct grep_opt *opt)\n+{\n+\tint i;\n+\n+\tif (getenv(\"THREADS\"))\n+\t\tnumber_of_threads = atoi(getenv(\"THREADS\"));\n+\telse\n+\t\tnumber_of_threads = 8;\n+\n+\ttodo_alloc = 100;\n+\ttodo = xmalloc(sizeof(struct work_item)*todo_alloc);\n+\tfor (i = 0; i < todo_alloc; i++) {\n+\t\tstrbuf_init(&todo[i].out, 0);\n+\t}\n+\n+\tthreads = xmalloc(sizeof(pthread_t)*number_of_threads);\n+\tfor (i = 0; i < number_of_threads; i++) {\n+\t\tstruct grep_opt *o = grep_opt_dup(opt);\n+\t\to->show_hunk_mark = 1;\n+\t\to->output = strbuf_out;\n+\t\tcompile_grep_patterns(o);\n+\t\tint err = pthread_create(&threads[i], NULL, run, o);\n+\n+\t\tif (err)\n+\t\t\tdie(\"grep: failed to create thread: %s\", strerror(err));\n+\t}\n+}\n+#endif /* THREADED_DELTA_SEARCH */\n+\n+#ifdef THREADED_DELTA_SEARCH\n+static int wait_all()\n+{\n+\tint hit = 0;\n+\tint i;\n+\n+\tpthread_mutex_lock(&grep_lock);\n+\tall_work_added = 1;\n+\n+\t/* Wait until all work is done. */\n+\twhile (todo_done != todo_end)\n+\t\tpthread_cond_wait(&cond_result, &grep_lock);\n+\n+\t/* Wake up all the consumer threads so they can see that there\n+\t   is no more work to do. */\n+\tpthread_cond_broadcast(&cond_add);\n+\tpthread_mutex_unlock(&grep_lock);\n+\n+\tfor (i = 0; i < number_of_threads; i++) {\n+\t\tvoid *h;\n+\t\tpthread_join(threads[i], &h);\n+\t\thit |= (int) (intptr_t) h;\n+\t}\n+\n+\treturn hit;\n+}\n+#else\n+static int wait_all()\n+{\n+\treturn 0;\n+}\n+#endif\n+\n+static int grep_buffer_internal(struct grep_opt *opt, char *name, char *buf,\n+\t\t\t\tsize_t size)\n+{\n+#ifdef THREADED_DELTA_SEARCH\n+\tif (use_threads)\n+\t\treturn grep_buffer_async(opt, name, buf, size);\n+#endif\n+\t{\n+\t\tint hit = grep_buffer(opt, name, buf, size);\n+\t\tfree(name);\n+\t\tfree(buf);\n+\t\treturn hit;\n+\t}\n+}\n+\n static char const * const grep_usage[] = {\n \t\"git grep [options] [-e] <pattern> [<rev>...] [[--] path...]\",\n \tNULL\n@@ -169,21 +432,21 @@ static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char\n \tif (opt->relative && opt->prefix_length) {\n \t\tquote_path_relative(name + tree_name_len, -1, &pathbuf, opt->prefix);\n \t\tstrbuf_insert(&pathbuf, 0, name, tree_name_len);\n-\t\tname = pathbuf.buf;\n+\t} else {\n+\t\tstrbuf_addstr(&pathbuf, name);\n \t}\n-\thit = grep_buffer(opt, name, data, size);\n-\tstrbuf_release(&pathbuf);\n-\tfree(data);\n+\n+\thit = grep_buffer_internal(opt, strbuf_detach(&pathbuf, NULL),\n+\t\t\t\t   data, size);\n+\n \treturn hit;\n }\n \n-static int grep_file(struct grep_opt *opt, const char *filename)\n+static void* load_file(const char *filename, size_t *sz)\n {\n \tstruct stat st;\n+\tchar* data;\n \tint i;\n-\tchar *data;\n-\tsize_t sz;\n-\tstruct strbuf buf = STRBUF_INIT;\n \n \tif (lstat(filename, &st) < 0) {\n \terr_ret:\n@@ -195,24 +458,47 @@ static int grep_file(struct grep_opt *opt, const char *filename)\n \t\treturn 0; /* empty file -- no grep hit */\n \tif (!S_ISREG(st.st_mode))\n \t\treturn 0;\n-\tsz = xsize_t(st.st_size);\n+\t*sz = xsize_t(st.st_size);\n \ti = open(filename, O_RDONLY);\n \tif (i < 0)\n \t\tgoto err_ret;\n-\tdata = xmalloc(sz + 1);\n-\tif (st.st_size != read_in_full(i, data, sz)) {\n+\tdata = xmalloc(*sz + 1);\n+\tdata[*sz] = 0;\n+\tif (st.st_size != read_in_full(i, data, *sz)) {\n \t\terror(\"'%s': short read %s\", filename, strerror(errno));\n \t\tclose(i);\n \t\tfree(data);\n \t\treturn 0;\n \t}\n \tclose(i);\n+\treturn data;\n+}\n+\n+static int grep_file(struct grep_opt *opt, const char *filename)\n+{\n+\tchar *data;\n+\tsize_t sz;\n+\tstruct strbuf buf = STRBUF_INIT;\n+\tchar *name;\n+\n \tif (opt->relative && opt->prefix_length)\n-\t\tfilename = quote_path_relative(filename, -1, &buf, opt->prefix);\n-\ti = grep_buffer(opt, filename, data, sz);\n-\tstrbuf_release(&buf);\n-\tfree(data);\n-\treturn i;\n+\t\tquote_path_relative(filename, -1, &buf, opt->prefix);\n+\telse\n+\t\tstrbuf_addstr(&buf, filename);\n+\tname = strbuf_detach(&buf, NULL);\n+\n+#ifdef THREADED_DELTA_SEARCH\n+\tif (use_threads) {\n+\t\treturn grep_file_async(opt, name, filename);\n+\t} else\n+#endif\n+\t{\n+\t\tdata = load_file(filename, &sz);\n+\t\tif (!data)\n+\t\t\treturn 0;\n+\n+\t\treturn grep_buffer_internal(opt, name, data, sz);\n+\t}\n }\n \n #if !NO_EXTERNAL_GREP\n@@ -809,6 +1095,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \n+\tuse_threads = getenv(\"NO_THREADS\") == NULL;\n+\n \t/*\n \t * 'git grep -h', unlike 'git grep -h <pattern>', is a request\n \t * to show usage information and exit.\n@@ -862,6 +1150,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\topt.regflags |= REG_ICASE;\n \tif ((opt.regflags != REG_NEWLINE) && opt.fixed)\n \t\tdie(\"cannot mix --fixed-strings and regexp\");\n+\n+#ifdef THREADED_DELTA_SEARCH\n+\tif (!grep_threads_ok(&opt))\n+\t\tuse_threads = 0;\n+\n+\tif (use_threads)\n+\t\tstart_threads(&opt);\n+#else\n+\tuse_threads = 0;\n+#endif\n+\n \tcompile_grep_patterns(&opt);\n \n \t/* Check revs and then paths */\n@@ -899,9 +1198,13 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t}\n \n \tif (!list.nr) {\n+\t\tint hit;\n \t\tif (!cached)\n \t\t\tsetup_work_tree();\n-\t\treturn !grep_cache(&opt, paths, cached, external_grep_allowed);\n+\t\thit = grep_cache(&opt, paths, cached, external_grep_allowed);\n+\t\tif (use_threads)\n+\t\t\thit |= wait_all();\n+\t\treturn !hit;\n \t}\n \n \tif (cached)\n@@ -913,6 +1216,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tif (grep_object(&opt, paths, real_obj, list.objects[i].name))\n \t\t\thit = 1;\n \t}\n+\n+\tif (use_threads)\n+\t\thit |= wait_all();\n \tfree_grep_patterns(&opt);\n \treturn !hit;\n }\ndiff --git a/grep.c b/grep.c\nindex 62723da..3dd3918 100644\n--- a/grep.c\n+++ b/grep.c\n@@ -29,6 +29,28 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat,\n \tp->next = NULL;\n }\n \n+struct grep_opt* grep_opt_dup(const struct grep_opt *opt)\n+{\n+\tstruct grep_pat *pat;\n+\tstruct grep_opt *ret = xmalloc(sizeof(struct grep_opt));\n+\t*ret = *opt;\n+\n+\tret->pattern_list = NULL;\n+\tret->pattern_tail = &ret->pattern_list;\n+\n+\tfor(pat = opt->pattern_list; pat != NULL; pat = pat->next)\n+\t{\n+\t\tif(pat->token == GREP_PATTERN_HEAD)\n+\t\t\tappend_header_grep_pattern(ret, pat->field,\n+\t\t\t\t\t\t   pat->pattern);\n+\t\telse\n+\t\t\tappend_grep_pattern(ret, pat->pattern, pat->origin,\n+\t\t\t\t\t    pat->no, pat->token);\n+\t}\n+\n+\treturn ret;\n+}\n+\n static int is_fixed(const char *s)\n {\n \twhile (*s && !is_regex_special(*s))\n@@ -260,7 +282,7 @@ static int word_char(char ch)\n \n static void show_name(struct grep_opt *opt, const char *name)\n {\n-\tprintf(\"%s%c\", name, opt->null_following_name ? '\\0' : '\\n');\n+\topt->output(opt, \"%s%c\", name, opt->null_following_name ? '\\0' : '\\n');\n }\n \n \n@@ -501,20 +523,20 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \tif (opt->pre_context || opt->post_context) {\n \t\tif (opt->last_shown == 0) {\n \t\t\tif (opt->show_hunk_mark)\n-\t\t\t\tfputs(\"--\\n\", stdout);\n+\t\t\t\topt->output(opt, \"--\\n\");\n \t\t\telse\n \t\t\t\topt->show_hunk_mark = 1;\n \t\t} else if (lno > opt->last_shown + 1)\n-\t\t\tfputs(\"--\\n\", stdout);\n+\t\t\topt->output(opt, \"--\\n\");\n \t}\n \topt->last_shown = lno;\n \n \tif (opt->null_following_name)\n \t\tsign = '\\0';\n \tif (opt->pathname)\n-\t\tprintf(\"%s%c\", name, sign);\n+\t\topt->output(opt, \"%s%c\", name, sign);\n \tif (opt->linenum)\n-\t\tprintf(\"%d%c\", lno, sign);\n+\t\topt->output(opt, \"%d%c\", lno, sign);\n \tif (opt->color) {\n \t\tregmatch_t match;\n \t\tenum grep_context ctx = GREP_CONTEXT_BODY;\n@@ -525,18 +547,19 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,\n \t\twhile (next_match(opt, bol, eol, ctx, &match, eflags)) {\n \t\t\tif (match.rm_so == match.rm_eo)\n \t\t\t\tbreak;\n-\t\t\tprintf(\"%.*s%s%.*s%s\",\n-\t\t\t       (int)match.rm_so, bol,\n-\t\t\t       opt->color_match,\n-\t\t\t       (int)(match.rm_eo - match.rm_so), bol + match.rm_so,\n-\t\t\t       GIT_COLOR_RESET);\n+\t\t\topt->output(opt, \"%.*s%s%.*s%s\",\n+\t\t\t\t    (int)match.rm_so, bol,\n+\t\t\t\t    opt->color_match,\n+\t\t\t\t    (int)(match.rm_eo - match.rm_so),\n+\t\t\t\t    bol + match.rm_so,\n+\t\t\t\t    GIT_COLOR_RESET);\n \t\t\tbol += match.rm_eo;\n \t\t\trest -= match.rm_eo;\n \t\t\teflags = REG_NOTBOL;\n \t\t}\n \t\t*eol = ch;\n \t}\n-\tprintf(\"%.*s\\n\", rest, bol);\n+\topt->output(opt, \"%.*s\\n\", rest, bol);\n }\n \n static int match_funcname(struct grep_opt *opt, char *bol, char *eol)\n@@ -674,6 +697,33 @@ static int look_ahead(struct grep_opt *opt,\n \treturn 0;\n }\n \n+int grep_threads_ok(const struct grep_opt *opt)\n+{\n+\t/* If this condition is true, then we may use the attribute\n+\t   machinery in grep_buffer_1. The attribute code is not\n+\t   thread safe, so we disable the use of threads. */\n+\tif (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&\n+\t    !opt->name_only)\n+\t\treturn 0;\n+\n+\t/* If we are showing hunk marks, we should not do it for the\n+\t   first match. The synchronization problem we get for this\n+\t   constraint is not yet solved, so we disable threading in\n+\t   this case. */\n+\tif (opt->pre_context || opt->post_context)\n+\t\treturn 0;\n+\t\n+\treturn 1;\n+}\n+\n+static void std_output(struct grep_opt *opt, const char* fmt, ...)\n+{\n+\tva_list ap;\n+\tva_start(ap, fmt);\n+\tvprintf(fmt, ap);\n+\tva_end(ap);\n+}\n+\n static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \t\t\t char *buf, unsigned long size, int collect_hits)\n {\n@@ -689,6 +739,9 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \n \topt->last_shown = 0;\n \n+\tif (!opt->output)\n+\t\topt->output = std_output;\n+\n \tif (buffer_is_binary(buf, size)) {\n \t\tswitch (opt->binary) {\n \t\tcase GREP_BINARY_DEFAULT:\n@@ -761,7 +814,8 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \t\t\tif (opt->status_only)\n \t\t\t\treturn 1;\n \t\t\tif (binary_match_only) {\n-\t\t\t\tprintf(\"Binary file %s matches\\n\", name);\n+\t\t\t\topt->output(opt,\n+\t\t\t\t\t    \"Binary file %s matches\\n\", name);\n \t\t\t\treturn 1;\n \t\t\t}\n \t\t\tif (opt->name_only) {\n@@ -817,9 +871,10 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,\n \t * which feels mostly useless but sometimes useful.  Maybe\n \t * make it another option?  For now suppress them.\n \t */\n-\tif (opt->count && count)\n-\t\tprintf(\"%s%c%u\\n\", name,\n-\t\t       opt->null_following_name ? '\\0' : ':', count);\n+\tif (opt->count && count) {\n+\t\topt->output(opt, \"%s%c%u\\n\", name,\n+\t\t\t    opt->null_following_name ? '\\0' : ':', count);\n+\t}\n \treturn !!last_hit;\n }\n \ndiff --git a/grep.h b/grep.h\nindex 75370f6..f5031c7 100644\n--- a/grep.h\n+++ b/grep.h\n@@ -92,6 +92,9 @@ struct grep_opt {\n \tunsigned last_shown;\n \tint show_hunk_mark;\n \tvoid *priv;\n+\n+\tvoid (*output)(struct grep_opt*, const char*, ...);\n+\tvoid *output_priv;\n };\n \n extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);\n@@ -100,4 +103,7 @@ extern void compile_grep_patterns(struct grep_opt *opt);\n extern void free_grep_patterns(struct grep_opt *opt);\n extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);\n \n+extern struct grep_opt* grep_opt_dup(const struct grep_opt *opt);\n+extern int grep_threads_ok(const struct grep_opt *opt);\n+\n #endif\ndiff --git a/strbuf.c b/strbuf.c\nindex 4017476..a41f0ea 100644\n--- a/strbuf.c\n+++ b/strbuf.c\n@@ -194,21 +194,28 @@ void strbuf_adddup(struct strbuf *sb, size_t pos, size_t len)\n \n void strbuf_addf(struct strbuf *sb, const char *fmt, ...)\n {\n-\tint len;\n \tva_list ap;\n \n+\tva_start(ap, fmt);\n+\tstrbuf_vaddf(sb, fmt, ap);\n+\tva_end(ap);\n+}\n+\n+void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap)\n+{\n+\tint len;\n+\tva_list ap2;\n+\n+\tva_copy(ap2, ap);\n \tif (!strbuf_avail(sb))\n \t\tstrbuf_grow(sb, 64);\n-\tva_start(ap, fmt);\n \tlen = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);\n-\tva_end(ap);\n \tif (len < 0)\n \t\tdie(\"your vsnprintf is broken\");\n \tif (len > strbuf_avail(sb)) {\n \t\tstrbuf_grow(sb, len);\n-\t\tva_start(ap, fmt);\n-\t\tlen = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);\n-\t\tva_end(ap);\n+\t\tlen = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap2);\n+\t\tva_end(ap2);\n \t\tif (len > strbuf_avail(sb)) {\n \t\t\tdie(\"this should not happen, your snprintf is broken\");\n \t\t}\ndiff --git a/strbuf.h b/strbuf.h\nindex b37f06a..20129ad 100644\n--- a/strbuf.h\n+++ b/strbuf.h\n@@ -118,6 +118,7 @@ extern size_t strbuf_expand_dict_cb(struct strbuf *sb, const char *placeholder,\n \n __attribute__((format (printf,2,3)))\n extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);\n+extern void strbuf_vaddf(struct strbuf *sb, const char *fmt, va_list ap);\n \n extern size_t strbuf_fread(struct strbuf *, size_t, FILE *);\n /* XXX: if read fails, any partial read is undone */\n"}]}