@matheusbsbs / matheus-bagliano / commits / 5b594f457a
Fork 0

Threaded grep

Make git grep use threads when it is available. The results below are best of five runs in the Linux repository (on a box with two cores). With the patch: git grep qwerty 1.58user 0.55system 0:01.16elapsed 183%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+800outputs (0major+5774minor)pagefaults 0swaps Without: git grep qwerty 1.59user 0.43system 0:02.02elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+800outputs (0major+3716minor)pagefaults 0swaps And with a pattern with quite a few matches: With the patch: $ /usr/bin/time git grep void 5.61user 0.56system 0:03.44elapsed 179%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+800outputs (0major+5587minor)pagefaults 0swaps Without: $ /usr/bin/time git grep void 5.36user 0.51system 0:05.87elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k 0inputs+800outputs (0major+3693minor)pagefaults 0swaps In either case we gain about 40% by the threading. Signed-off-by: Fredrik Kuivinen <frekui@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Fredrik Kuivinen committed Jan 25, 2010 at 23:51 UTC 5b594f457afcf2cdddb3c79de8bcc571c775aa55
3 files changed +468 -49
builtin-grep.c
+374 -31
@@ -16,11 +16,274 @@
16 #include "quote.h"
17 #include "dir.h"
18
19 +#ifndef NO_PTHREADS
20 +#include "thread-utils.h"
21 +#include <pthread.h>
22 +#endif
23 +
24 static char const * const grep_usage[] = {
25 "git grep [options] [-e] <pattern> [<rev>...] [[--] path...]",
26 NULL
27 };
28
29 +static int use_threads = 1;
30 +
31 +#ifndef NO_PTHREADS
32 +#define THREADS 8
33 +static pthread_t threads[THREADS];
34 +
35 +static void *load_sha1(const unsigned char *sha1, unsigned long *size,
36 + const char *name);
37 +static void *load_file(const char *filename, size_t *sz);
38 +
39 +enum work_type {WORK_SHA1, WORK_FILE};
40 +
41 +/* We use one producer thread and THREADS consumer
42 + * threads. The producer adds struct work_items to 'todo' and the
43 + * consumers pick work items from the same array.
44 + */
45 +struct work_item
46 +{
47 + enum work_type type;
48 + char *name;
49 +
50 + /* if type == WORK_SHA1, then 'identifier' is a SHA1,
51 + * otherwise type == WORK_FILE, and 'identifier' is a NUL
52 + * terminated filename.
53 + */
54 + void *identifier;
55 + char done;
56 + struct strbuf out;
57 +};
58 +
59 +/* In the range [todo_done, todo_start) in 'todo' we have work_items
60 + * that have been or are processed by a consumer thread. We haven't
61 + * written the result for these to stdout yet.
62 + *
63 + * The work_items in [todo_start, todo_end) are waiting to be picked
64 + * up by a consumer thread.
65 + *
66 + * The ranges are modulo TODO_SIZE.
67 + */
68 +#define TODO_SIZE 128
69 +static struct work_item todo[TODO_SIZE];
70 +static int todo_start;
71 +static int todo_end;
72 +static int todo_done;
73 +
74 +/* Has all work items been added? */
75 +static int all_work_added;
76 +
77 +/* This lock protects all the variables above. */
78 +static pthread_mutex_t grep_mutex;
79 +
80 +/* Used to serialize calls to read_sha1_file. */
81 +static pthread_mutex_t read_sha1_mutex;
82 +
83 +#define grep_lock() pthread_mutex_lock(&grep_mutex)
84 +#define grep_unlock() pthread_mutex_unlock(&grep_mutex)
85 +#define read_sha1_lock() pthread_mutex_lock(&read_sha1_mutex)
86 +#define read_sha1_unlock() pthread_mutex_unlock(&read_sha1_mutex)
87 +
88 +/* Signalled when a new work_item is added to todo. */
89 +static pthread_cond_t cond_add;
90 +
91 +/* Signalled when the result from one work_item is written to
92 + * stdout.
93 + */
94 +static pthread_cond_t cond_write;
95 +
96 +/* Signalled when we are finished with everything. */
97 +static pthread_cond_t cond_result;
98 +
99 +static void add_work(enum work_type type, char *name, void *id)
100 +{
101 + grep_lock();
102 +
103 + while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) {
104 + pthread_cond_wait(&cond_write, &grep_mutex);
105 + }
106 +
107 + todo[todo_end].type = type;
108 + todo[todo_end].name = name;
109 + todo[todo_end].identifier = id;
110 + todo[todo_end].done = 0;
111 + strbuf_reset(&todo[todo_end].out);
112 + todo_end = (todo_end + 1) % ARRAY_SIZE(todo);
113 +
114 + pthread_cond_signal(&cond_add);
115 + grep_unlock();
116 +}
117 +
118 +static struct work_item *get_work(void)
119 +{
120 + struct work_item *ret;
121 +
122 + grep_lock();
123 + while (todo_start == todo_end && !all_work_added) {
124 + pthread_cond_wait(&cond_add, &grep_mutex);
125 + }
126 +
127 + if (todo_start == todo_end && all_work_added) {
128 + ret = NULL;
129 + } else {
130 + ret = &todo[todo_start];
131 + todo_start = (todo_start + 1) % ARRAY_SIZE(todo);
132 + }
133 + grep_unlock();
134 + return ret;
135 +}
136 +
137 +static void grep_sha1_async(struct grep_opt *opt, char *name,
138 + const unsigned char *sha1)
139 +{
140 + unsigned char *s;
141 + s = xmalloc(20);
142 + memcpy(s, sha1, 20);
143 + add_work(WORK_SHA1, name, s);
144 +}
145 +
146 +static void grep_file_async(struct grep_opt *opt, char *name,
147 + const char *filename)
148 +{
149 + add_work(WORK_FILE, name, xstrdup(filename));
150 +}
151 +
152 +static void work_done(struct work_item *w)
153 +{
154 + int old_done;
155 +
156 + grep_lock();
157 + w->done = 1;
158 + old_done = todo_done;
159 + for(; todo[todo_done].done && todo_done != todo_start;
160 + todo_done = (todo_done+1) % ARRAY_SIZE(todo)) {
161 + w = &todo[todo_done];
162 + write_or_die(1, w->out.buf, w->out.len);
163 + free(w->name);
164 + free(w->identifier);
165 + }
166 +
167 + if (old_done != todo_done)
168 + pthread_cond_signal(&cond_write);
169 +
170 + if (all_work_added && todo_done == todo_end)
171 + pthread_cond_signal(&cond_result);
172 +
173 + grep_unlock();
174 +}
175 +
176 +static void *run(void *arg)
177 +{
178 + int hit = 0;
179 + struct grep_opt *opt = arg;
180 +
181 + while (1) {
182 + struct work_item *w = get_work();
183 + if (!w)
184 + break;
185 +
186 + opt->output_priv = w;
187 + if (w->type == WORK_SHA1) {
188 + unsigned long sz;
189 + void* data = load_sha1(w->identifier, &sz, w->name);
190 +
191 + if (data) {
192 + hit |= grep_buffer(opt, w->name, data, sz);
193 + free(data);
194 + }
195 + } else if (w->type == WORK_FILE) {
196 + size_t sz;
197 + void* data = load_file(w->identifier, &sz);
198 + if (data) {
199 + hit |= grep_buffer(opt, w->name, data, sz);
200 + free(data);
201 + }
202 + } else {
203 + assert(0);
204 + }
205 +
206 + work_done(w);
207 + }
208 +
209 + return (void*) (intptr_t) hit;
210 +}
211 +
212 +static void strbuf_out(struct grep_opt *opt, const void *buf, size_t size)
213 +{
214 + struct work_item *w = opt->output_priv;
215 + strbuf_add(&w->out, buf, size);
216 +}
217 +
218 +static void start_threads(struct grep_opt *opt)
219 +{
220 + int i;
221 +
222 + pthread_mutex_init(&grep_mutex, NULL);
223 + pthread_mutex_init(&read_sha1_mutex, NULL);
224 + pthread_cond_init(&cond_add, NULL);
225 + pthread_cond_init(&cond_write, NULL);
226 + pthread_cond_init(&cond_result, NULL);
227 +
228 + for (i = 0; i < ARRAY_SIZE(todo); i++) {
229 + strbuf_init(&todo[i].out, 0);
230 + }
231 +
232 + for (i = 0; i < ARRAY_SIZE(threads); i++) {
233 + int err;
234 + struct grep_opt *o = grep_opt_dup(opt);
235 + o->output = strbuf_out;
236 + compile_grep_patterns(o);
237 + err = pthread_create(&threads[i], NULL, run, o);
238 +
239 + if (err)
240 + die("grep: failed to create thread: %s",
241 + strerror(err));
242 + }
243 +}
244 +
245 +static int wait_all(void)
246 +{
247 + int hit = 0;
248 + int i;
249 +
250 + grep_lock();
251 + all_work_added = 1;
252 +
253 + /* Wait until all work is done. */
254 + while (todo_done != todo_end)
255 + pthread_cond_wait(&cond_result, &grep_mutex);
256 +
257 + /* Wake up all the consumer threads so they can see that there
258 + * is no more work to do.
259 + */
260 + pthread_cond_broadcast(&cond_add);
261 + grep_unlock();
262 +
263 + for (i = 0; i < ARRAY_SIZE(threads); i++) {
264 + void *h;
265 + pthread_join(threads[i], &h);
266 + hit |= (int) (intptr_t) h;
267 + }
268 +
269 + pthread_mutex_destroy(&grep_mutex);
270 + pthread_mutex_destroy(&read_sha1_mutex);
271 + pthread_cond_destroy(&cond_add);
272 + pthread_cond_destroy(&cond_write);
273 + pthread_cond_destroy(&cond_result);
274 +
275 + return hit;
276 +}
277 +#else /* !NO_PTHREADS */
278 +#define read_sha1_lock()
279 +#define read_sha1_unlock()
280 +
281 +static int wait_all(void)
282 +{
283 + return 0;
284 +}
285 +#endif
286 +
287 static int grep_config(const char *var, const char *value, void *cb)
288 {
289 struct grep_opt *opt = cb;
@@ -144,37 +407,64 @@ static int pathspec_matches(const char **paths, const char *name, int max_depth)
407 return 0;
408 }
409
147 -static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len)
410 +static void *load_sha1(const unsigned char *sha1, unsigned long *size,
411 + const char *name)
412 {
149 - unsigned long size;
150 - char *data;
413 enum object_type type;
152 - int hit;
153 - struct strbuf pathbuf = STRBUF_INIT;
414 + char *data;
415
155 - data = read_sha1_file(sha1, &type, &size);
156 - if (!data) {
416 + read_sha1_lock();
417 + data = read_sha1_file(sha1, &type, size);
418 + read_sha1_unlock();
419 +
420 + if (!data)
421 error("'%s': unable to read %s", name, sha1_to_hex(sha1));
158 - return 0;
159 - }
422 +
423 + return data;
424 +}
425 +
426 +static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
427 + const char *filename, int tree_name_len)
428 +{
429 + struct strbuf pathbuf = STRBUF_INIT;
430 + char *name;
431 +
432 if (opt->relative && opt->prefix_length) {
161 - quote_path_relative(name + tree_name_len, -1, &pathbuf, opt->prefix);
162 - strbuf_insert(&pathbuf, 0, name, tree_name_len);
163 - name = pathbuf.buf;
433 + quote_path_relative(filename + tree_name_len, -1, &pathbuf,
434 + opt->prefix);
435 + strbuf_insert(&pathbuf, 0, filename, tree_name_len);
436 + } else {
437 + strbuf_addstr(&pathbuf, filename);
438 + }
439 +
440 + name = strbuf_detach(&pathbuf, NULL);
441 +
442 +#ifndef NO_PTHREADS
443 + if (use_threads) {
444 + grep_sha1_async(opt, name, sha1);
445 + return 0;
446 + } else
447 +#endif
448 + {
449 + int hit;
450 + unsigned long sz;
451 + void *data = load_sha1(sha1, &sz, name);
452 + if (!data)
453 + hit = 0;
454 + else
455 + hit = grep_buffer(opt, name, data, sz);
456 +
457 + free(data);
458 + free(name);
459 + return hit;
460 }
165 - hit = grep_buffer(opt, name, data, size);
166 - strbuf_release(&pathbuf);
167 - free(data);
168 - return hit;
461 }
462
171 -static int grep_file(struct grep_opt *opt, const char *filename)
463 +static void *load_file(const char *filename, size_t *sz)
464 {
465 struct stat st;
174 - int i;
466 char *data;
176 - size_t sz;
177 - struct strbuf buf = STRBUF_INIT;
467 + int i;
468
469 if (lstat(filename, &st) < 0) {
470 err_ret:
@@ -184,25 +474,52 @@ static int grep_file(struct grep_opt *opt, const char *filename)
474 }
475 if (!S_ISREG(st.st_mode))
476 return 0;
187 - sz = xsize_t(st.st_size);
477 + *sz = xsize_t(st.st_size);
478 i = open(filename, O_RDONLY);
479 if (i < 0)
480 goto err_ret;
191 - data = xmalloc(sz + 1);
192 - if (st.st_size != read_in_full(i, data, sz)) {
481 + data = xmalloc(*sz + 1);
482 + if (st.st_size != read_in_full(i, data, *sz)) {
483 error("'%s': short read %s", filename, strerror(errno));
484 close(i);
485 free(data);
486 return 0;
487 }
488 close(i);
199 - data[sz] = 0;
489 + data[*sz] = 0;
490 + return data;
491 +}
492 +
493 +static int grep_file(struct grep_opt *opt, const char *filename)
494 +{
495 + struct strbuf buf = STRBUF_INIT;
496 + char *name;
497 +
498 if (opt->relative && opt->prefix_length)
201 - filename = quote_path_relative(filename, -1, &buf, opt->prefix);
202 - i = grep_buffer(opt, filename, data, sz);
203 - strbuf_release(&buf);
204 - free(data);
205 - return i;
499 + quote_path_relative(filename, -1, &buf, opt->prefix);
500 + else
501 + strbuf_addstr(&buf, filename);
502 + name = strbuf_detach(&buf, NULL);
503 +
504 +#ifndef NO_PTHREADS
505 + if (use_threads) {
506 + grep_file_async(opt, name, filename);
507 + return 0;
508 + } else
509 +#endif
510 + {
511 + int hit;
512 + size_t sz;
513 + void *data = load_file(filename, &sz);
514 + if (!data)
515 + hit = 0;
516 + else
517 + hit = grep_buffer(opt, name, data, sz);
518 +
519 + free(data);
520 + free(name);
521 + return hit;
522 + }
523 }
524
525 static int grep_cache(struct grep_opt *opt, const char **paths, int cached)
@@ -287,7 +604,10 @@ static int grep_tree(struct grep_opt *opt, const char **paths,
604 void *data;
605 unsigned long size;
606
607 + read_sha1_lock();
608 data = read_sha1_file(entry.sha1, &type, &size);
609 + read_sha1_unlock();
610 +
611 if (!data)
612 die("unable to read tree (%s)",
613 sha1_to_hex(entry.sha1));
@@ -581,6 +901,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
901 opt.regflags |= REG_ICASE;
902 if ((opt.regflags != REG_NEWLINE) && opt.fixed)
903 die("cannot mix --fixed-strings and regexp");
904 +
905 +#ifndef NO_PTHREADS
906 + if (online_cpus() == 1 || !grep_threads_ok(&opt))
907 + use_threads = 0;
908 +
909 + if (use_threads)
910 + start_threads(&opt);
911 +#else
912 + use_threads = 0;
913 +#endif
914 +
915 compile_grep_patterns(&opt);
916
917 /* Check revs and then paths */
@@ -618,17 +949,26 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
949 }
950
951 if (!use_index) {
952 + int hit;
953 if (cached)
954 die("--cached cannot be used with --no-index.");
955 if (list.nr)
956 die("--no-index cannot be used with revs.");
625 - return !grep_directory(&opt, paths);
957 + hit = grep_directory(&opt, paths);
958 + if (use_threads)
959 + hit |= wait_all();
960 + return !hit;
961 }
962
963 if (!list.nr) {
964 + int hit;
965 if (!cached)
966 setup_work_tree();
631 - return !grep_cache(&opt, paths, cached);
967 +
968 + hit = grep_cache(&opt, paths, cached);
969 + if (use_threads)
970 + hit |= wait_all();
971 + return !hit;
972 }
973
974 if (cached)
@@ -643,6 +983,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix)
983 break;
984 }
985 }
986 +
987 + if (use_threads)
988 + hit |= wait_all();
989 free_grep_patterns(&opt);
990 return !hit;
991 }
grep.c
+88 -18
@@ -29,6 +29,28 @@ void append_grep_pattern(struct grep_opt *opt, const char *pat,
29 p->next = NULL;
30 }
31
32 +struct grep_opt *grep_opt_dup(const struct grep_opt *opt)
33 +{
34 + struct grep_pat *pat;
35 + struct grep_opt *ret = xmalloc(sizeof(struct grep_opt));
36 + *ret = *opt;
37 +
38 + ret->pattern_list = NULL;
39 + ret->pattern_tail = &ret->pattern_list;
40 +
41 + for(pat = opt->pattern_list; pat != NULL; pat = pat->next)
42 + {
43 + if(pat->token == GREP_PATTERN_HEAD)
44 + append_header_grep_pattern(ret, pat->field,
45 + pat->pattern);
46 + else
47 + append_grep_pattern(ret, pat->pattern, pat->origin,
48 + pat->no, pat->token);
49 + }
50 +
51 + return ret;
52 +}
53 +
54 static void compile_regexp(struct grep_pat *p, struct grep_opt *opt)
55 {
56 int err;
@@ -253,7 +275,8 @@ static int word_char(char ch)
275
276 static void show_name(struct grep_opt *opt, const char *name)
277 {
256 - printf("%s%c", name, opt->null_following_name ? '\0' : '\n');
278 + opt->output(opt, name, strlen(name));
279 + opt->output(opt, opt->null_following_name ? "\0" : "\n", 1);
280 }
281
282
@@ -490,24 +513,32 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
513 const char *name, unsigned lno, char sign)
514 {
515 int rest = eol - bol;
516 + char sign_str[1];
517
518 + sign_str[0] = sign;
519 if (opt->pre_context || opt->post_context) {
520 if (opt->last_shown == 0) {
521 if (opt->show_hunk_mark)
497 - fputs("--\n", stdout);
522 + opt->output(opt, "--\n", 3);
523 else
524 opt->show_hunk_mark = 1;
525 } else if (lno > opt->last_shown + 1)
501 - fputs("--\n", stdout);
526 + opt->output(opt, "--\n", 3);
527 }
528 opt->last_shown = lno;
529
530 if (opt->null_following_name)
506 - sign = '\0';
507 - if (opt->pathname)
508 - printf("%s%c", name, sign);
509 - if (opt->linenum)
510 - printf("%d%c", lno, sign);
531 + sign_str[0] = '\0';
532 + if (opt->pathname) {
533 + opt->output(opt, name, strlen(name));
534 + opt->output(opt, sign_str, 1);
535 + }
536 + if (opt->linenum) {
537 + char buf[32];
538 + snprintf(buf, sizeof(buf), "%d", lno);
539 + opt->output(opt, buf, strlen(buf));
540 + opt->output(opt, sign_str, 1);
541 + }
542 if (opt->color) {
543 regmatch_t match;
544 enum grep_context ctx = GREP_CONTEXT_BODY;
@@ -518,18 +549,22 @@ static void show_line(struct grep_opt *opt, char *bol, char *eol,
549 while (next_match(opt, bol, eol, ctx, &match, eflags)) {
550 if (match.rm_so == match.rm_eo)
551 break;
521 - printf("%.*s%s%.*s%s",
522 - (int)match.rm_so, bol,
523 - opt->color_match,
524 - (int)(match.rm_eo - match.rm_so), bol + match.rm_so,
525 - GIT_COLOR_RESET);
552 +
553 + opt->output(opt, bol, match.rm_so);
554 + opt->output(opt, opt->color_match,
555 + strlen(opt->color_match));
556 + opt->output(opt, bol + match.rm_so,
557 + (int)(match.rm_eo - match.rm_so));
558 + opt->output(opt, GIT_COLOR_RESET,
559 + strlen(GIT_COLOR_RESET));
560 bol += match.rm_eo;
561 rest -= match.rm_eo;
562 eflags = REG_NOTBOL;
563 }
564 *eol = ch;
565 }
532 - printf("%.*s\n", rest, bol);
566 + opt->output(opt, bol, rest);
567 + opt->output(opt, "\n", 1);
568 }
569
570 static int match_funcname(struct grep_opt *opt, char *bol, char *eol)
@@ -667,6 +702,32 @@ static int look_ahead(struct grep_opt *opt,
702 return 0;
703 }
704
705 +int grep_threads_ok(const struct grep_opt *opt)
706 +{
707 + /* If this condition is true, then we may use the attribute
708 + * machinery in grep_buffer_1. The attribute code is not
709 + * thread safe, so we disable the use of threads.
710 + */
711 + if (opt->funcname && !opt->unmatch_name_only && !opt->status_only &&
712 + !opt->name_only)
713 + return 0;
714 +
715 + /* If we are showing hunk marks, we should not do it for the
716 + * first match. The synchronization problem we get for this
717 + * constraint is not yet solved, so we disable threading in
718 + * this case.
719 + */
720 + if (opt->pre_context || opt->post_context)
721 + return 0;
722 +
723 + return 1;
724 +}
725 +
726 +static void std_output(struct grep_opt *opt, const void *buf, size_t size)
727 +{
728 + fwrite(buf, size, 1, stdout);
729 +}
730 +
731 static int grep_buffer_1(struct grep_opt *opt, const char *name,
732 char *buf, unsigned long size, int collect_hits)
733 {
@@ -682,6 +743,9 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
743
744 opt->last_shown = 0;
745
746 + if (!opt->output)
747 + opt->output = std_output;
748 +
749 if (buffer_is_binary(buf, size)) {
750 switch (opt->binary) {
751 case GREP_BINARY_DEFAULT:
@@ -754,7 +818,9 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
818 if (opt->status_only)
819 return 1;
820 if (binary_match_only) {
757 - printf("Binary file %s matches\n", name);
821 + opt->output(opt, "Binary file ", 12);
822 + opt->output(opt, name, strlen(name));
823 + opt->output(opt, " matches\n", 9);
824 return 1;
825 }
826 if (opt->name_only) {
@@ -810,9 +876,13 @@ static int grep_buffer_1(struct grep_opt *opt, const char *name,
876 * which feels mostly useless but sometimes useful. Maybe
877 * make it another option? For now suppress them.
878 */
813 - if (opt->count && count)
814 - printf("%s%c%u\n", name,
815 - opt->null_following_name ? '\0' : ':', count);
879 + if (opt->count && count) {
880 + char buf[32];
881 + opt->output(opt, name, strlen(name));
882 + snprintf(buf, sizeof(buf), "%c%u\n",
883 + opt->null_following_name ? '\0' : ':', count);
884 + opt->output(opt, buf, strlen(buf));
885 + }
886 return !!last_hit;
887 }
888
grep.h
+6
@@ -91,6 +91,9 @@ struct grep_opt {
91 unsigned last_shown;
92 int show_hunk_mark;
93 void *priv;
94 +
95 + void (*output)(struct grep_opt *opt, const void *data, size_t size);
96 + void *output_priv;
97 };
98
99 extern void append_grep_pattern(struct grep_opt *opt, const char *pat, const char *origin, int no, enum grep_pat_token t);
@@ -99,4 +102,7 @@ extern void compile_grep_patterns(struct grep_opt *opt);
102 extern void free_grep_patterns(struct grep_opt *opt);
103 extern int grep_buffer(struct grep_opt *opt, const char *name, char *buf, unsigned long size);
104
105 +extern struct grep_opt *grep_opt_dup(const struct grep_opt *opt);
106 +extern int grep_threads_ok(const struct grep_opt *opt);
107 +
108 #endif