Avoiding calling memchr2() for fields less than 16 bytes,
results in a 40% performance boost.

  $ yes $(yes eeeaae | head -n9 | paste -s -d' ') | head -n1M > asw.in

  $ time LC_ALL=C src/cut-before -f10 -w asw.in >/dev/null
  real  0m0.254s

  $ time LC_ALL=C src/cut-after -f10 -w asw.in >/dev/null
  real  0m0.151s

* src/cut.c (find_c_blank): A wrapper around memchr2()
to avoid its significant setup cost.
---
 src/cut.c | 19 ++++++++++++++++++-
 1 file changed, 18 insertions(+), 1 deletion(-)

diff --git a/src/cut.c b/src/cut.c
index 2e6d8bf82..aa38e72c9 100644
--- a/src/cut.c
+++ b/src/cut.c
@@ -658,6 +658,23 @@ find_field_delim (char *buf, size_t len)
 #endif
 }
 
+/* Return a pointer to the next TAB or SPACE in BUF, searching LEN bytes.
+   Return NULL if none is found.
+   It works for all unibyte locales, i.e., assumes they match c_isblank.  */
+
+ATTRIBUTE_PURE
+static char *
+find_c_blank (char *buf, size_t len)
+{
+  /* Avoid memchr2 setup for short fields.  */
+  idx_t n = MIN (len, 16);
+  for (idx_t i = 0; i < n; i++)
+    if (c_isblank (buf[i]))
+      return buf + i;
+
+  return memchr2 (buf + n, ' ', '\t', len - n);
+}
+
 /* Return the number of trailing bytes in BUF that could be the initial
    bytes of a delimiter split across buffers.  */
 
@@ -711,7 +728,7 @@ find_field_terminator (char *buf, idx_t len,
   idx_t field_len = ctx->line_end ? ctx->line_end - buf : len;
 
   char *field_end = (ctx->blank_delimited
-                     ? memchr2 (buf, ' ', '\t', field_len)
+                     ? find_c_blank (buf, field_len)
                      : find_field_delim (buf, field_len));
 
   if (field_end)
-- 
2.55.0


Reply via email to