Перед вами один из самых знаменитых и «вирусных» фрагментов кода на C за всю историю языка.
На первый взгляд кажется, что это синтаксическая ошибка — ведь здесь switch буквально переплетён с циклом do-while. Но нет, это абсолютно валидный, стандартный C-код!
void fastCopy(short* to, short* from, int count)
{
if (count <= 0) return;
int 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);
}
}
Приём назван в честь Тома Даффа, который придумал его в 1983 году, работая в Lucasfilm Computer Division — подразделение, из которого позже вырос Pixar.
Его задача была ускорить копирование данных при работе над программой для анимации и он нашёл элегантное и слегка безумное решение, которое сочетает в себе механизм switch и раскрутку цикла loop unrolling
Это классический приём раскрутки цикла (loop unrolling) для оптимизации копирования памяти. Идея простая:
--n > 0 срабатывает в 8 раз режеswitch в начале служит точкой входа, которая отправляет выполнение прямо на нужную итерацию, чтобы обработать остаток (count % 8), а затем переходит к полным цикламСекрет в том, что в C метки case — это, по сути, обычные метки для перехода, и они могут находиться внутри вложенных блоков.
switchdo-whilewhile, и цикл продолжает крутиться уже целикомhttps://godbolt.org/z/3x94PhzsY
#include <stdio.h>
void fastCopy(short *to, short *from, int count)
{
if (count <= 0) return;
int 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);
}
}
int main(void)
{
short src[20] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20};
short dest[20] = {0};
fastCopy(dest, src, 20);
printf("Source array: ");
for (int i = 0; i < 20; ++i)
{
printf("%d ", src[i]);
}
printf("\\n");
printf("Copied array: ");
for (int i = 0; i < 20; ++i)
{
printf("%d ", dest[i]);
}
printf("\\n");
return 0;
}
Скорее нет, чем да. Современные компиляторы сами разворачивают циклы и применяют SIMD-инструкции. Более того, Duff's Device нередко мешает векторизации и может оказаться медленнее обычного memcpy.