Subject: Re: [bug-diffutils] Multithreading support?

From: David Balažic <[email-masked]>
Date: Mon, 2 Sep 2013 21:34:30 +0200

Additional tests, using the command from the patch (same router):

cmp -n 8GiB /dev/full /dev/zero

v3.3: 23 sec
size_t patch: 20 sec
size_t+rawmemchr: 8 secs

cmp -n 8GiB /dev/full /dev/zero

v3.3: 4.7 sec
size_t patch: 5.3 sec
size_t+rawmemchr: 3.7 secs

On 1 September 2013 22:57, David Balažic <[email-masked]> wrote:
> Hi!
>
> I finally tested the patches on my Netgear WNDR3700v2 router running
> OpenWRT 12.09 "Attitude Ajdustment".
>
> I tested the "builtin" cmp (part of busybox), and versions of cmp
> compiled by me:
>  - unmodified diffutils-3.3 ("./cmp")
>  - patch # define word size_t ("./cmpp1")
>  - additionally patch tune by using rawmemchr ("./cmpp2")
>
> I did two tests:
>
>  - test file with random contents:
> dd if=/dev/urandom of=test bs=1M count=17
> time cmp test test # busybox cmp reads the inputs even if they are the same file
> 1.22 seconds
>
> time cat test | ./cmp test  -
> real    0m 0.90s
>
> time cat test | ./cmpp1 test  -
> real    0m 0.80s
>
> time cat test | ./cmpp2 test  -
> real    0m 0.60s
>
> (average of multiple runs)
>
>  - test with /dev/zero
>
> # dd if=/dev/zero bs=1M count=64 | time ./cmp - /dev/zero
> 64+0 records in
> 64+0 records out
> cmp: EOF on -
> Command exited with non-zero status 1
> real    0m 2.79s
> user    0m 1.12s
> sys     0m 0.94s
>
>
> # dd if=/dev/zero bs=1M count=64 | time ./cmpp1 - /dev/zero
> 64+0 records in
> 64+0 records out
> cmp: EOF on -
> Command exited with non-zero status 1
> real    0m 2.53s
> user    0m 1.13s
> sys     0m 0.80s
>
> # dd if=/dev/zero bs=1M count=64 | time ./cmpp2 - /dev/zero
> 64+0 records in
> 64+0 records out
> cmp: EOF on -
> Command exited with non-zero status 1
> real    0m 1.80s
> user    0m 0.32s
> sys     0m 0.69s
>
>
> So both patches seem to improve speed on this machine, especially the second.
>
> Regards,
> David
>
>
> On 23 August 2013 00:59, Paul Eggert <[email-masked]> wrote:
>> On 08/13/13 08:48, David Balažic wrote:
>>> I'll try to benchmark it on my ARM based router, when time permits.
>>
>> Thanks.  While you're at it, could you also benchmark the following
>> patch, which I just now pushed to the diffutils git master on
>> Savannah?
>>
>> From 6749fe1ddd6805828b526c73b2f1a579eb0d9f63 Mon Sep 17 00:00:00 2001
>> From: Paul Eggert <[email-masked]>
>> Date: Thu, 22 Aug 2013 15:45:56 -0700
>> Subject: [PATCH] cmp, diff, sdiff: tune by using rawmemchr
>>
>> On my platform (AMD Phenom II X4 910e, Fedora 17 x86-64), this sped up
>> 'cmp -n 8GiB /dev/full /dev/zero' by a factor of 3.8, and
>> 'cmp -sn 8GiB /dev/full /dev/zero' by a factor of 1.8.
>> * bootstrap.conf (gnulib_modules): Add rawmemchr.
>> * src/cmp.c (cmp): Optimize the common case where buffers are the same,
>> by using count_newlines rather than block_compare_and_count.
>> (block_compare_and_count): Remove.
>> (count_newlines): New function.
>> * src/cmp.c (count_newlines):
>> * src/io.c (prepare_text):
>> * src/sdiff.c (lf_copy, lf_skip, lf_snarf):
>> Use rawmemchr instead of memchr, for speed.
>> ---
>>  bootstrap.conf |  1 +
>>  src/cmp.c      | 88 +++++++++++++++++++---------------------------------------
>>  src/io.c       | 24 ++++++++++------
>>  src/sdiff.c    | 10 +++----
>>  4 files changed, 51 insertions(+), 72 deletions(-)
>>
>> diff --git a/bootstrap.conf b/bootstrap.conf
>> index 240754b..ac85adf 100644
>> --- a/bootstrap.conf
>> +++ b/bootstrap.conf
>> @@ -56,6 +56,7 @@ mkstemp
>>  mktime
>>  progname
>>  propername
>> +rawmemchr
>>  readme-release
>>  regex
>>  sh-quote
>> diff --git a/src/cmp.c b/src/cmp.c
>> index 97473c9..9e35b07 100644
>> --- a/src/cmp.c
>> +++ b/src/cmp.c
>> @@ -52,7 +52,7 @@
>>  static int cmp (void);
>>  static off_t file_position (int);
>>  static size_t block_compare (word const *, word const *) _GL_ATTRIBUTE_PURE;
>> -static size_t block_compare_and_count (word const *, word const *, off_t *);
>> +static size_t count_newlines (char *, size_t);
>>  static void sprintc (char *, unsigned char);
>>
>>  /* Filenames of the compared files.  */
>> @@ -448,20 +448,23 @@ cmp (void)
>>        if (read1 == SIZE_MAX)
>>         error (EXIT_TROUBLE, errno, "%s", file[1]);
>>
>> -      /* Insert sentinels for the block compare.  */
>> +      smaller = MIN (read0, read1);
>>
>> -      buf0[read0] = ~buf1[read0];
>> -      buf1[read1] = ~buf0[read1];
>> +      /* Optimize the common case where the buffers are the same.  */
>> +      if (memcmp (buf0, buf1, smaller) == 0)
>> +       first_diff = smaller;
>> +      else
>> +       {
>> +         /* Insert sentinels for the block compare.  */
>> +         buf0[read0] = ~buf1[read0];
>> +         buf1[read1] = ~buf0[read1];
>>
>> -      /* If the line number should be written for differing files,
>> -        compare the blocks and count the number of newlines
>> -        simultaneously.  */
>> -      first_diff = (comparison_type == type_first_diff
>> -                   ? block_compare_and_count (buffer0, buffer1, &line_number)
>> -                   : block_compare (buffer0, buffer1));
>> +         first_diff = block_compare (buffer0, buffer1);
>> +       }
>>
>>        byte_number += first_diff;
>> -      smaller = MIN (read0, read1);
>> +      if (comparison_type == type_first_diff)
>> +       line_number += count_newlines (buf0, first_diff);
>>
>>        if (first_diff < smaller)
>>         {
>> @@ -567,54 +570,6 @@ cmp (void)
>>    return differing == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
>>  }
>>
>> -/* Compare two blocks of memory P0 and P1 until they differ,
>> -   and count the number of '\n' occurrences in the common
>> -   part of P0 and P1.
>> -   If the blocks are not guaranteed to be different, put sentinels at the ends
>> -   of the blocks before calling this function.
>> -
>> -   Return the offset of the first byte that differs.
>> -   Increment *COUNT by the count of '\n' occurrences.  */
>> -
>> -static size_t
>> -block_compare_and_count (word const *p0, word const *p1, off_t *count)
>> -{
>> -  word l;              /* One word from first buffer. */
>> -  word const *l0, *l1; /* Pointers into each buffer. */
>> -  char const *c0, *c1; /* Pointers for finding exact address. */
>> -  size_t cnt = 0;      /* Number of '\n' occurrences. */
>> -  word nnnn;           /* Newline, sizeof (word) times.  */
>> -  int i;
>> -
>> -  nnnn = 0;
>> -  for (i = 0; i < sizeof nnnn; i++)
>> -    nnnn = (nnnn << CHAR_BIT) | '\n';
>> -
>> -  /* Find the rough position of the first difference by reading words,
>> -     not bytes.  */
>> -
>> -  for (l0 = p0, l1 = p1;  (l = *l0) == *l1;  l0++, l1++)
>> -    {
>> -      l ^= nnnn;
>> -      for (i = 0; i < sizeof l; i++)
>> -       {
>> -         unsigned char uc = l;
>> -         cnt += ! uc;
>> -         l >>= CHAR_BIT;
>> -       }
>> -    }
>> -
>> -  /* Find the exact differing position (endianness independent).  */
>> -
>> -  for (c0 = (char const *) l0, c1 = (char const *) l1;
>> -       *c0 == *c1;
>> -       c0++, c1++)
>> -    cnt += *c0 == '\n';
>> -
>> -  *count += cnt;
>> -  return c0 - (char const *) p0;
>> -}
>> -
>>  /* Compare two blocks of memory P0 and P1 until they differ.
>>     If the blocks are not guaranteed to be different, put sentinels at the ends
>>     of the blocks before calling this function.
>> @@ -643,6 +598,21 @@ block_compare (word const *p0, word const *p1)
>>    return c0 - (char const *) p0;
>>  }
>>
>> +/* Return the number of newlines in BUF, of size BUFSIZE,
>> +   where BUF[NBYTES] is available for use as a sentinel.  */
>> +
>> +static size_t
>> +count_newlines (char *buf, size_t bufsize)
>> +{
>> +  size_t count = 0;
>> +  char *p;
>> +  char *lim = buf + bufsize;
>> +  *lim = '\n';
>> +  for (p = buf; (p = rawmemchr (p, '\n')) != lim; p++)
>> +    count++;
>> +  return count;
>> +}
>> +
>>  /* Put into BUF the unsigned char C, making unprintable bytes
>>     visible by quoting like cat -t does.  */
>>
>> diff --git a/src/io.c b/src/io.c
>> index 463ee35..05a898c 100644
>> --- a/src/io.c
>> +++ b/src/io.c
>> @@ -474,7 +474,6 @@ prepare_text (struct file_data *current)
>>  {
>>    size_t buffered = current->buffered;
>>    char *p = FILE_BUFFER (current);
>> -  char *dst;
>>
>>    if (buffered == 0 || p[buffered - 1] == '\n')
>>      current->missing_newline = false;
>> @@ -490,16 +489,25 @@ prepare_text (struct file_data *current)
>>    /* Don't use uninitialized storage when planting or using sentinels.  */
>>    memset (p + buffered, 0, sizeof (word));
>>
>> -  if (strip_trailing_cr && (dst = memchr (p, '\r', buffered)))
>> +  if (strip_trailing_cr)
>>      {
>> -      char const *src = dst;
>> -      char const *srclim = p + buffered;
>> +      char *dst;
>> +      char *srclim = p + buffered;
>> +      *srclim = '\r';
>> +      dst = rawmemchr (p, '\r');
>>
>> -      do
>> -       dst += ! ((*dst = *src++) == '\r' && *src == '\n');
>> -      while (src < srclim);
>> +      if (dst != srclim)
>> +       {
>> +         char const *src = dst;
>> +         do
>> +           {
>> +             *dst = *src++;
>> +             dst += ! (*dst == '\r' && *src == '\n');
>> +           }
>> +         while (src < srclim);
>>
>> -      buffered -= src - dst;
>> +         buffered -= src - dst;
>> +       }
>>      }
>>
>>    current->buffered = buffered;
>> diff --git a/src/sdiff.c b/src/sdiff.c
>> index b7f9f6a..e7bc657 100644
>> --- a/src/sdiff.c
>> +++ b/src/sdiff.c
>> @@ -379,8 +379,8 @@ lf_copy (struct line_filter *lf, lin lines, FILE *outfile)
>>
>>    while (lines)
>>      {
>> -      lf->bufpos = (char *) memchr (lf->bufpos, '\n', lf->buflim - lf->bufpos);
>> -      if (! lf->bufpos)
>> +      lf->bufpos = rawmemchr (lf->bufpos, '\n');
>> +      if (lf->bufpos == lf->buflim)
>>         {
>>           ck_fwrite (start, lf->buflim - start, outfile);
>>           if (! lf_refill (lf))
>> @@ -403,8 +403,8 @@ lf_skip (struct line_filter *lf, lin lines)
>>  {
>>    while (lines)
>>      {
>> -      lf->bufpos = (char *) memchr (lf->bufpos, '\n', lf->buflim - lf->bufpos);
>> -      if (! lf->bufpos)
>> +      lf->bufpos = rawmemchr (lf->bufpos, '\n');
>> +      if (lf->bufpos == lf->buflim)
>>         {
>>           if (! lf_refill (lf))
>>             break;
>> @@ -424,7 +424,7 @@ lf_snarf (struct line_filter *lf, char *buffer, size_t bufsize)
>>    for (;;)
>>      {
>>        char *start = lf->bufpos;
>> -      char *next = (char *) memchr (start, '\n', lf->buflim + 1 - start);
>> +      char *next = rawmemchr (start, '\n');
>>        size_t s = next - start;
>>        if (bufsize <= s)
>>         return 0;
>> --
>> 1.7.11.7
>>
>>