risorse | duff's device

Duff's Device

Introduzione

Il Duff's device è un costrutto C/C++ che effettua l'unroll di un ciclo tramite una combinazione piuttosto «creativa» di uno switch e un while. La formulazione originale è la seguente (cfr. [4]):

/*
 * loop standard
 */
register short *to, *from;
register count;
{
  do
    *to = *from++;
  while (--count > 0);
}


/*
 * duff's device equivalente
 */
register short *to, *from;
register count;
{
  register n = (count + 7) / 8;
  switch(count % 8) {
    case 0: do {  *to = *from++;
    case 7:       *to = *from++;
    case 6:       *to = *from++;
    case 5:       *to = *from++;
    case 4:       *to = *from++;
    case 3:       *to = *from++;
    case 2:       *to = *from++;
    case 1:       *to = *from++;
               } while (--n > 0);
  }
}

Per quanto esoterico, il codice è perfettamente legale. In rete si trovano diverse risorse che ne spiegano il funzionamento[2][3]. Di seguito, il mio tentativo.

Ricostruzione del Duff's Device

Passo 0: ciclo while

La versione iniziale del loop:

#include <iostream>

int main() {
  int i = 0;
  int n = 19;

  while (n > 0) {
    std::cout << ++i << " ";
    --n;
  }

  std::cout << std::endl;
  return 0;
}

/* output:
 *
 * 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 */

Passo 1: unroll esplicito con ciclo for

Versione con unroll di ampiezza 8; si noti il secondo ciclo for, che si occupa di effettuare le operazioni non coperte dal ciclo “srotolato” nel caso il numero di ripetizioni richieste non sia un multiplo di 8:

  // ...

  const int unrolled_loops = n / 8;

  for (int j = 0; j < unrolled_loops; ++j) {
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
  }

  const int uncovered_by_unrolled_looping = n % 8;
  for (int j = 0; j < uncovered_by_unrolled_looping; j++) {
    std::cout << ++i << " ";
  }

  // ...

/* output
 *
 * 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 */

Passo 3: goto

Nulla cambia se il secondo ciclo for viene eseguito prima dell'unrolling:

  // ...

  const int uncovered_by_unrolled_looping = n % 8;
  for (int j = 0; j < uncovered_by_unrolled_looping; j++) {
    std::cout << ++i << " ";
  }

  const int unrolled_loops = n / 8;

  for (int j = 0; j < unrolled_loops; ++j) {
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
  }

  // ...

/* output
 *
 * 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 */

L'idea che apre la porta al Duff's Device è che il primo ciclo si può evitare, se si ha l'accortezza di entrare nella posizione corretta del ciclo “srotolato”:

  // ...
  int loop = (n + 7) / 8;

  if (n % 8 == 1) goto one;
  if (n % 8 == 2) goto two;
  if (n % 8 == 3) goto three;
  if (n % 8 == 4) goto four;
  if (n % 8 == 5) goto five;
  if (n % 8 == 6) goto six;
  if (n % 8 == 7) goto seven;

  while (loop > 0) {
        std::cout << ++i << " ";
seven:  std::cout << ++i << " ";
six:    std::cout << ++i << " ";
five:   std::cout << ++i << " ";
four:   std::cout << ++i << " ";
three:  std::cout << ++i << " ";
two:    std::cout << ++i << " ";
one:    std::cout << ++i << " ";
        --loop;
  }

  // ...

/* output
 *
 * 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 */

Passo 4: sostituzione if/switch

La sequenza di if può essere sostituita da uno switch:

  // ...
  int loop = (n + 7) / 8;

  switch (n % 8)
  {
    case 1: goto one;
    case 2: goto two;
    case 3: goto three;
    case 4: goto four;
    case 5: goto five;
    case 6: goto six;
    case 7: goto seven;
  }

  while (loop > 0) {
        std::cout << ++i << " ";
seven:  std::cout << ++i << " ";
six:    std::cout << ++i << " ";
five:   std::cout << ++i << " ";
four:   std::cout << ++i << " ";
three:  std::cout << ++i << " ";
two:    std::cout << ++i << " ";
one:    std::cout << ++i << " ";
        --loop;
  }

  // ...

/* output
 *
 * 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 */

Passo 5: eliminazione dei goto

La sequenza di goto può essere eliminata sfruttando il fall-thriugh:

  // ...
  int loop = (n + 7) / 8;

  switch (n % 8)
  {
    case 7: std::cout << ++i << " ";
    case 6: std::cout << ++i << " ";
    case 5: std::cout << ++i << " ";
    case 4: std::cout << ++i << " ";
    case 3: std::cout << ++i << " ";
    case 2: std::cout << ++i << " ";
    case 1: std::cout << ++i << " ";
            --loop;
  }

  while (loop > 0) {
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    std::cout << ++i << " ";
    --loop;
  }

  // ...

/* output
 *
 * 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 */

Passo 6: il Duff's Device

I blocchi dello switch e del ciclo while hanno una struttura molto simile; in particolare, il primo contiene un numero di “srotolamenti” inferiore di una unità rispetto al secondo. Ciò tuttavia non impedisce di fondere i due cicli, sfruttando una particolarità del linguaggio C (e di conseguenza del C++) che richiede, relativamente all'istruzione switch, solamente che il suo corpo sia sintatticamente corretto e consente che le etichette case siano poste a prefisso di una qualunque sua istruzione. Il seguente codice è dunque perfettamente legale:

  // ...
  int loop = (n + 7) / 8;

  switch (n % 8)
  {
    case 0:
             while (loop > 0) {
               std::cout << ++i << " ";
    case 7:    std::cout << ++i << " ";
    case 6:    std::cout << ++i << " ";
    case 5:    std::cout << ++i << " ";
    case 4:    std::cout << ++i << " ";
    case 3:    std::cout << ++i << " ";
    case 2:    std::cout << ++i << " ";
    case 1:    std::cout << ++i << " ";
               --loop;
             }
  }

  // ...

/* output
 *
 * 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 */

Rispetto alla versione originale, questa implementazione non richiede un valore strettamente positivo per la variabile n.

Riferimenti

  1. Holly, R. "A Reusable Duff Device". Dr.Dobb's. <http://www.drdobbs.com/a-reusable-duff-device/184406208>. Visitato il 20 settembre 2012.
  2. "Duff's device". Wikipedia. <http://en.wikipedia.org/wiki/Duff%27s_device>. Visitato il 20 Settembre 2012.
  3. "How does Duff's device work?". Stack Overflow. <http://stackoverflow.com/questions/514118/how-does-duffs-device-work>. Visitato il 25 Settembre 2012.
  4. "Tom Duff on Duff's device". www.lysator.liu.se. <http://www.lysator.liu.se/c/duffs-device.html>. Visitato il 24 Settembre 2012.

Pagina modificata il 25/09/2012