ダフのデバイスはどのように機能しますか?

他にも良い説明がいくつかありますが、試してみてください。 (これはホワイトボードの方がはるかに簡単です!) ウィキペディアの例といくつかの表記法を次に示します。

20バイトをコピーしているとしましょう。最初のパスのプログラムのフロー制御は次のとおりです:

int count;                        // Set to 20
{
    int n = (count + 7) / 8;      // n is now 3.  (The "while" is going
                                  //              to be run three times.)

    switch (count % 8) {          // The remainder is 4 (20 modulo 8) so
                                  // jump to the case 4

    case 0:                       // [skipped]
             do {                 // [skipped]
                 *to = *from++;   // [skipped]
    case 7:      *to = *from++;   // [skipped]
    case 6:      *to = *from++;   // [skipped]
    case 5:      *to = *from++;   // [skipped]
    case 4:      *to = *from++;   // Start here.  Copy 1 byte  (total 1)
    case 3:      *to = *from++;   // Copy 1 byte (total 2)
    case 2:      *to = *from++;   // Copy 1 byte (total 3)
    case 1:      *to = *from++;   // Copy 1 byte (total 4)
           } while (--n > 0);     // N = 3 Reduce N by 1, then jump up
                                  //       to the "do" if it's still
    }                             //        greater than 0 (and it is)
}

次に、2 番目のパスを開始します。指定されたコードだけを実行します。

int count;                        //
{
    int n = (count + 7) / 8;      //
                                  //

    switch (count % 8) {          //
                                  //

    case 0:                       //
             do {                 // The while jumps to here.
                 *to = *from++;   // Copy 1 byte (total 5)
    case 7:      *to = *from++;   // Copy 1 byte (total 6)
    case 6:      *to = *from++;   // Copy 1 byte (total 7)
    case 5:      *to = *from++;   // Copy 1 byte (total 8)
    case 4:      *to = *from++;   // Copy 1 byte (total 9)
    case 3:      *to = *from++;   // Copy 1 byte (total 10)
    case 2:      *to = *from++;   // Copy 1 byte (total 11)
    case 1:      *to = *from++;   // Copy 1 byte (total 12)
           } while (--n > 0);     // N = 2 Reduce N by 1, then jump up
                                  //       to the "do" if it's still
    }                             //       greater than 0 (and it is)
}

次に、3 番目のパスを開始します。

int count;                        //
{
    int n = (count + 7) / 8;      //
                                  //

    switch (count % 8) {          //
                                  //

    case 0:                       //
             do {                 // The while jumps to here.
                 *to = *from++;   // Copy 1 byte (total 13)
    case 7:      *to = *from++;   // Copy 1 byte (total 14)
    case 6:      *to = *from++;   // Copy 1 byte (total 15)
    case 5:      *to = *from++;   // Copy 1 byte (total 16)
    case 4:      *to = *from++;   // Copy 1 byte (total 17)
    case 3:      *to = *from++;   // Copy 1 byte (total 18)
    case 2:      *to = *from++;   // Copy 1 byte (total 19)
    case 1:      *to = *from++;   // Copy 1 byte (total 20)
           } while (--n > 0);     // N = 1  Reduce N by 1, then jump up
                                  //       to the "do" if it's still
    }                             //       greater than 0 (and it's not, so bail)
}                                 // continue here...

20 バイトがコピーされました。

注:元の Duff のデバイス (上に表示) は、to で I/O デバイスにコピーされます 住所。したがって、ポインター *to をインクリメントする必要はありませんでした。 . 2 つのメモリ バッファ間でコピーする場合は、 *to++ を使用する必要があります .


Dr. Dobb's Journal の説明は、このトピックに関して私が見つけた最高のものです。

これは私の AHA の瞬間です:

for (i = 0; i < len; ++i) {
    HAL_IO_PORT = *pSource++;
}

になります:

int n = len / 8;
for (i = 0; i < n; ++i) {
    HAL_IO_PORT = *pSource++;
    HAL_IO_PORT = *pSource++;
    HAL_IO_PORT = *pSource++;
    HAL_IO_PORT = *pSource++;
    HAL_IO_PORT = *pSource++;
    HAL_IO_PORT = *pSource++;
    HAL_IO_PORT = *pSource++;
    HAL_IO_PORT = *pSource++;
}

n = len % 8;
for (i = 0; i < n; ++i) {
    HAL_IO_PORT = *pSource++;
}

になります:

int n = (len + 8 - 1) / 8;
switch (len % 8) {
    case 0: do { HAL_IO_PORT = *pSource++;
    case 7: HAL_IO_PORT = *pSource++;
    case 6: HAL_IO_PORT = *pSource++;
    case 5: HAL_IO_PORT = *pSource++;
    case 4: HAL_IO_PORT = *pSource++;
    case 3: HAL_IO_PORT = *pSource++;
    case 2: HAL_IO_PORT = *pSource++;
    case 1: HAL_IO_PORT = *pSource++;
               } while (--n > 0);
}

ダフのデバイスには 2 つの重要な要素があります。まず、これは理解しやすい部分だと思いますが、ループが展開されます。これは、ループが終了したかどうかを確認してループの先頭に戻ることに伴うオーバーヘッドの一部を回避することで、より大きなコード サイズと引き換えに速度を向上させます。 CPU は、ジャンプする代わりに直線的なコードを実行すると、より高速に実行できます。

2 つ目の側面は、switch ステートメントです。コードが途中にジャンプできるようにします ループの最初の時間。ほとんどの人にとって驚くべき部分は、そのようなことが許されているということです。まあ、それは許可されています。計算されたケース ラベルから実行が開始され、フォールスルー 他の switch ステートメントと同様に、連続する各代入ステートメントに適用されます。最後の case ラベルの後、実行はループの最後に到達し、その時点で最初にジャンプして戻ります。ループの上部は内側です これにより、スイッチは再評価されなくなります。

元のループは 8 回巻き戻されるため、反復回数は 8 で割られます。コピーするバイト数が 8 の倍数でない場合、いくつかのバイトが残っています。一度にバイトのブロックをコピーするほとんどのアルゴリズムは、残りのバイトを最後に処理しますが、Duff のデバイスはそれらを最初に処理します。この関数は count % 8 を計算します switch ステートメントで残りがどうなるかを判断し、そのバイト数の case ラベルにジャンプして、それらをコピーします。その後、ループは 8 バイトのグループをコピーし続けます。