Grand Central Dispatch: First Look

September 6, 2009 | Filed Under Apple | No Comments

Mac OS X Grand Central DispatchIn the last years I’ve always used a “parallel task” approach foreach loops that I’ve in the code, not always to speedup but even to clean-up the code. How to do it? Wrapping threads and Thread Pool like in this C# Parallel Forech Code.

Snow Leopard has introduced a new BSD-level infrastructure, with simple and efficient API to do this job. Here a little usage preview.

Block objects are a C-based language feature that you can use in your C, Objective-C, and C++ code. Blocks make it easy to define a self-contained unit of work. Blocks are something like Actions (delegate {}) in C#. Very useful to embed function in loops.

Blocks looks like a “private” function pointer, but you can access to the “parent” vars. (If you’re a Python coder, you’ve exactly the same thing).

/* Blocks in Python...
 * def main():
 *    a = 10
 *    def test(k):
 *        print a, k
 *    test(128)
 */
int main (int argc, const char *argv[]) {
  int a = 12;

  void (^test_block) (int) = ^(int k) {
    printf("A Block: PARENT(%d) ARG(%d)\n", a, k);
  };

  test_block(128);

  return 0;
}

The GCD queue API provides dispatch queues from which threads take tasks to be executed. Because the threads are managed by GCD, Mac OS X can optimize the number of threads based upon available memory, number of currently active CPU cores, and so on. This shifts a great deal of the burden of power and resource management to the operating system itself, freeing your application to focus on the actual work to be accomplished.

#include <dispatch/dispatch.h>
#include <stdlib.h>
#include <stdio.h>

#define ITEM_VMIN       (1)
#define ITEM_VMAX       (200)
#define NR_ITEMS        (100)

static void __fill_item (void *items, size_t n) {
  int *i_items = (int *)items;
  i_items[n] = (ITEM_VMIN + (int)(ITEM_VMAX * ((double)rand() / RAND_MAX)));
}

static void __work_on_item (void *items, size_t n) {
  int *i_items = (int *)items;
  i_items[n] *= 100;  /* Do some Computation on this Item */
}

int main (int argc, const char *argv[]) {
  dispatch_queue_t queue;
  int data[NR_ITEMS];

  /* Get Global Dispatch Queue */
  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

  /* Initialize data Elements, and run computation on each element */
  dispatch_apply_f(NR_ITEMS, queue, data, __fill_item);
  dispatch_apply_f(NR_ITEMS, queue, data, __work_on_item);

  /* Brief review of the items */
  dispatch_apply(NR_ITEMS, queue, ^(size_t n) {
    printf("Results: Item %lu = %d\n", n, data[n]);
  });

  return 0;
}

C# Parallel Foreach using Thread Pool

December 26, 2008 | Filed Under C# | 1 Comment

public void Foreach(T[] array, Action action)
{
   int cpus = 2 * Environment.ProcessorCount;
   int nitems = array.Length;
   int chunk = nitems / cpus;
   int counter = cpus;

   using (AutoResetEvent signal = new AutoResetEvent(false))
   {
      for (int i = 1; i <= cpus; i++)
      {
         ThreadPool.QueueUserWorkItem(delegate(object o)
         {
            int unit = (int)o;

            for (int j = (unit - 1) * chunk;
                  j < (unit == cpus ? nitems : unit * chunk);
                  j++)
            {
               action(array[j]);
            }

            if (Interlocked.Decrement(ref counter) == 0)
            signal.Set();
         }, i);
      }

      signal.WaitOne();
   }
}

public void ForeachWithResults(T[] array, T1[] results, Action action)
{
   int cpus = 2 * Environment.ProcessorCount;
   int nitems = array.Length;
   int chunk = nitems / cpus;
   int counter = cpus;

   using (AutoResetEvent signal = new AutoResetEvent(false))
   {
      for (int i = 1; i <= cpus; i++)
      {
         ThreadPool.QueueUserWorkItem(delegate(object o)
         {
            int unit = (int)o;

            for (int j = (unit - 1) * chunk;
                  j < (unit == cpus ? nitems : unit * chunk);
                  j++)
            {
               action(j, array[j], results);
            }

            if (Interlocked.Decrement(ref counter) == 0)
               signal.Set();
         }, i);
      }

      signal.WaitOne();
   }
}