Re: [LAU] Simple, easy multithreaded circular buffer library for Linux?

From: Olivier Guilyardi <ml@email-addr-hidden>
Date: Fri Oct 17 2008 - 15:13:02 EEST

Olivier Guilyardi wrote:
> Paul Davis wrote:
>> On Wed, 2008-10-15 at 13:02 +0200, Olivier Guilyardi wrote:
>>> I'm no memory barrier expert, but from what I read this looks like a complex
>>> topic. Adding them to jack's ring buffer may be hazardous without carefully
>>> crafted test cases. Bugs related to missing barriers may or may not happen
>>> according to various hardware-related issues.
>> the point of memory barriers is really to remove the need for test
>> cases :)
>
> I meant Test First, not putting a memory barrier between us and past bugs ;)
>
> My concern is: people around say memory barriers are missing, but could anyone
> write a unit test that actually turns this assumption into a bug?

Okay, I wrote such a test. It fails with Jack's ringbuffer (jack1 r3004) but
succeeds with Portaudio's one (r1240).

It's a very simple test. I'm starting two threads, writing incremented numbers
into a ringbuffer, and checking that they follow each other on the reading end.

The failure frequency depends on the chunk size / buffer size ratio. With
relative short runs, I'm getting about 2 failures / million, for both 256 / 512
and 256 / 1024. I'm around 0.5 failure / million with a 256 / 4096 ratio. But
longer runs would be necessary for exact statistics.

There is no such failure with Portaudio's ringbuffer, even if I deactivate its
memory barriers. But I just can't say if this bug is caused by the lack of
memory barrier or not in Jack, because the two implementations are different,
and all of this seems to depend on the CPU architecture, the movements of the
clouds, the color of my coffee, etc.. ;)

I'm running these tests on a Intel Core2 Quad Q6600 CPU, and making sure the two
threads run on a different CPU by looking at the program startup output. I
didn't test on a single-cpu architecture.

The Portaudio code looks more and more robust to me. It's also surprisingly
short. Maybe that the best would be to replace jack's ringbuffer with it? I
think it should be possible to keep the jack_ringbuffer api unchanged.

My test code is below. It expects the buffer size as first argument. Try with
512, 1024, 2048,... With a buffer size of 4096 bytes, I sometimes need to wait a
minute or so for the first failure to appear.

When saved in jack1 svn working dir, I compile it with something like:
gcc -I. -o testrb -Wall testrb.c libjack/ringbuffer.c -lpthread

#include <unistd.h>
#include <jack/ringbuffer.h>
#include <pthread.h>
#include <stdio.h>
#include <utmpx.h>

#define ARRAY_SIZE 64
#define MAX_VALUE 0x10000

jack_ringbuffer_t *rb;

static int
fill_int_array (int *array, int start, int count)
{
  int i, j = start;
  for (i = 0; i < count; i++)
  {
    array[i] = j;
    j = (j + 1) % MAX_VALUE;
  }
  return j;
}

static int
cmp_array (int *array1, int *array2, int count)
{
  int i;
  for (i = 0; i < count; i++)
    if (array1[i] != array2[i])
    {
      printf("%d != %d at offset %d\n", array1[i], array2[i], i);
      return 0;
    }

  return 1;
}

static void *
reader_start (void * arg)
{
  int i = 0, a[ARRAY_SIZE], b[ARRAY_SIZE];
  unsigned long j = 0, nfailures = 0;
  printf("reader started on cpu %d\n", sched_getcpu());
  i = fill_int_array (a, i, ARRAY_SIZE);
  while (1)
  {
    if (jack_ringbuffer_read_space (rb) >= ARRAY_SIZE * sizeof (int))
    {
      if (jack_ringbuffer_read (rb, (char *) b, ARRAY_SIZE * sizeof (int)))
      {
        if (!cmp_array (a, b, ARRAY_SIZE))
        {
          nfailures++;
          printf("failure in chunk %lu - probability: %lu/%lu = %.3f per
million\n",
                 j, nfailures, j, (float) nfailures / (j + 1) * 1000000);
          i = (b[0] + ARRAY_SIZE) % MAX_VALUE;
        }
        i = fill_int_array (a, i, ARRAY_SIZE);
        j++;
      }
    }
  }

  return NULL;
}

static void *
writer_start (void * arg)
{
  int i = 0, a[ARRAY_SIZE];
  printf("writer started on cpu: %d\n", sched_getcpu());

  i = fill_int_array (a, i, ARRAY_SIZE);

  while (1)
  {
    if (jack_ringbuffer_write_space (rb) >= ARRAY_SIZE * sizeof (int))
    {
      if (jack_ringbuffer_write (rb, (char *) a, ARRAY_SIZE * sizeof (int)))
      {
        i = fill_int_array (a, i, ARRAY_SIZE);
      }
    }
  }

  return NULL;
}

int main(int argc, char *argv[])
{
  int size;
  printf("starting ringbuffer stress test\n");
  sscanf(argv[1], "%d", &size);
  printf("buffer size (bytes): %d\n", size);
  printf("array size (bytes): %d\n", sizeof(int) * ARRAY_SIZE);
  rb = jack_ringbuffer_create(size);
  pthread_t reader_thread, writer_thread;
  pthread_create (&reader_thread, NULL, reader_start, NULL);
  pthread_create (&writer_thread, NULL, writer_start, NULL);
  while (1)
    sleep(1);
  return 0;
}

-- 
  Olivier Guilyardi / Samalyse
_______________________________________________
Linux-audio-user mailing list
Linux-audio-user@email-addr-hidden
http://lists.linuxaudio.org/mailman/listinfo/linux-audio-user
Received on Fri Oct 17 16:15:01 2008

This archive was generated by hypermail 2.1.8 : Fri Oct 17 2008 - 16:15:02 EEST