strbuf_getwholeline: use getdelim if it is available

We spend a lot of time in strbuf_getwholeline in a tight loop reading characters from a stdio handle into a buffer. The libc getdelim() function can do this for us with less overhead. It's in POSIX.1-2008, and was a GNU extension before that. Therefore we can't rely on it, but can fall back to the existing getc loop when it is not available. The HAVE_GETDELIM knob is turned on automatically for Linux, where we have glibc. We don't need to set any new feature-test macros, because we already define _GNU_SOURCE. Other systems that implement getdelim may need to other macros (probably _POSIX_C_SOURCE >= 200809L), but we can address that along with setting the Makefile knob after testing the feature on those systems. Running "git rev-parse refs/heads/does-not-exist" on a repo with an extremely large (1.6GB) packed-refs file went from (best-of-5): real 0m8.601s user 0m8.084s sys 0m0.524s to: real 0m6.768s user 0m6.340s sys 0m0.432s for a wall-clock speedup of 21%. Based on a patch from Rasmus Villemoes <rv@rasmusvillemoes.dk>. Signed-off-by: Jeff King <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Apr 16, 2015 at 05:01 UTC 0cc30e0e842a25846e76e09f62a1d425a25ee556
3 files changed +49
Makefile
+6
@@ -359,6 +359,8 @@ all::
359 # compiler is detected to support it.
360 #
361 # Define HAVE_BSD_SYSCTL if your platform has a BSD-compatible sysctl function.
362 +#
363 +# Define HAVE_GETDELIM if your system has the getdelim() function.
364
365 GIT-VERSION-FILE: FORCE
366 @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -1437,6 +1439,10 @@ ifdef HAVE_BSD_SYSCTL
1439 BASIC_CFLAGS += -DHAVE_BSD_SYSCTL
1440 endif
1441
1442 +ifdef HAVE_GETDELIM
1443 + BASIC_CFLAGS += -DHAVE_GETDELIM
1444 +endif
1445 +
1446 ifeq ($(TCLTK_PATH),)
1447 NO_TCLTK = NoThanks
1448 endif
config.mak.uname
+1
@@ -36,6 +36,7 @@ ifeq ($(uname_S),Linux)
36 HAVE_DEV_TTY = YesPlease
37 HAVE_CLOCK_GETTIME = YesPlease
38 HAVE_CLOCK_MONOTONIC = YesPlease
39 + HAVE_GETDELIM = YesPlease
40 endif
41 ifeq ($(uname_S),GNU/kFreeBSD)
42 HAVE_ALLOCA_H = YesPlease
strbuf.c
+42
@@ -435,6 +435,47 @@ int strbuf_getcwd(struct strbuf *sb)
435 return -1;
436 }
437
438 +#ifdef HAVE_GETDELIM
439 +int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
440 +{
441 + ssize_t r;
442 +
443 + if (feof(fp))
444 + return EOF;
445 +
446 + strbuf_reset(sb);
447 +
448 + /* Translate slopbuf to NULL, as we cannot call realloc on it */
449 + if (!sb->alloc)
450 + sb->buf = NULL;
451 + r = getdelim(&sb->buf, &sb->alloc, term, fp);
452 +
453 + if (r > 0) {
454 + sb->len = r;
455 + return 0;
456 + }
457 + assert(r == -1);
458 +
459 + /*
460 + * Normally we would have called xrealloc, which will try to free
461 + * memory and recover. But we have no way to tell getdelim() to do so.
462 + * Worse, we cannot try to recover ENOMEM ourselves, because we have
463 + * no idea how many bytes were read by getdelim.
464 + *
465 + * Dying here is reasonable. It mirrors what xrealloc would do on
466 + * catastrophic memory failure. We skip the opportunity to free pack
467 + * memory and retry, but that's unlikely to help for a malloc small
468 + * enough to hold a single line of input, anyway.
469 + */
470 + if (errno == ENOMEM)
471 + die("Out of memory, getdelim failed");
472 +
473 + /* Restore slopbuf that we moved out of the way before */
474 + if (!sb->buf)
475 + strbuf_init(sb, 0);
476 + return EOF;
477 +}
478 +#else
479 int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
480 {
481 int ch;
@@ -458,6 +499,7 @@ int strbuf_getwholeline(struct strbuf *sb, FILE *fp, int term)
499 sb->buf[sb->len] = '\0';
500 return 0;
501 }
502 +#endif
503
504 int strbuf_getline(struct strbuf *sb, FILE *fp, int term)
505 {