Вы можете доказать это очень просто:
console.log(Object.getPrototypeOf(Function.prototype));
console.log(Object.prototype === Object.getPrototypeOf(Function.prototype));
// or, being evil
Object.prototype.testFn = () => console.log('Exists on all objects');
({}).testFn();
[].testFn();
(new Number(5)).testFn();
Math.sqrt.testFn();
Первые две строки показывают, что следующий шаг в цепочке прототипов за пределами Function.prototype
is Object.prototype
.
Другие строки показывают, что вы можете добавить свойство в Object.prototype
(серьезно не делайте этого никогда) и оно существует для всех объектов. В этом случае мы проверяем его на пустой объект, пустой массив, объект Number
и на функцию Math.sqrt
. Добавляя свойство (функция в этом случае) к Object.prototype
, все остальные также получают свойство.
Просто экспериментируя, обнаружил, что другой вариант проходит без чередования и петли:
int n = (count + 1) / 8;
switch (count % 8)
{
LOOP:
case 0:
if(n-- == 0)
break;
putchar('.');
case 7:
putchar('.');
case 6:
putchar('.');
case 5:
putchar('.');
case 4:
putchar('.');
case 3:
putchar('.');
case 2:
putchar('.');
case 1:
putchar('.');
default:
goto LOOP;
}
Вот подробное объяснение, которое, как я считаю, является сутью устройства Даффа:
Дело в том, что C - это в основном приятный фасад для языка ассемблера (сборка PDP-7 для конкретных , если вы изучили это, вы увидите, насколько поразительны сходства). И на языке ассемблера у вас на самом деле нет циклов - у вас есть метки и инструкции условной ветви. Таким образом, цикл является лишь частью общей последовательности команд с меткой и веткой где-то:
instruction
label1: instruction
instruction
instruction
instruction
jump to label1 some condition
и команда переключения несколько разветвляется / прыгает вперед:
evaluate expression into register r
compare r with first case value
branch to first case label if equal
compare r with second case value
branch to second case label if equal
etc....
first_case_label:
instruction
instruction
second_case_label:
instruction
instruction
etc...
В сборке легко мыслимо, как объединить эти две структуры управления, и когда вы думаете об этом таким образом, их комбинация в C больше не кажется такой странной.
Хотя я не уверен на 100%, о чем вы просите, здесь идет ...
Проблема в том, что адреса устройства Даффа являются одним из циклов (например, вы, несомненно, увидите на ссылку Wiki, которую вы опубликовали). То, что это в основном приравнивает, - это оптимизация эффективности во время выполнения, по памяти. Устройство Duff имеет дело с серийным копированием, а не только с какой-либо старой проблемой, но является классическим примером того, как оптимизация может быть достигнута за счет сокращения количества раз, когда сравнение должно выполняться в цикле.
As альтернативный пример, который может облегчить понимание, представьте, что у вас есть массив элементов, которые вы хотите перебрать, и каждый раз добавляйте к ним 1 ... обычно вы можете использовать цикл for и цикл около 100 раз. Это кажется довольно логичным и, тем не менее, оптимизация может быть выполнена путем разворачивания цикла (очевидно, не слишком далеко ... или вы можете просто не использовать цикл).
So регулярный цикл:
for(int i = 0; i < 100; i++)
{
myArray[i] += 1;
}
становится
for(int i = 0; i < 100; i+10)
{
myArray[i] += 1;
myArray[i+1] += 1;
myArray[i+2] += 1;
myArray[i+3] += 1;
myArray[i+4] += 1;
myArray[i+5] += 1;
myArray[i+6] += 1;
myArray[i+7] += 1;
myArray[i+8] += 1;
myArray[i+9] += 1;
}
То, что делает устройство Duff, реализует эту идею на C, но (как вы видели на Wiki) с серийными копиями , То, что вы видите выше, с размотанным примером, составляет 10 сравнений по сравнению с 100 в оригинале - это составляет незначительную, но, возможно, значительную оптимизацию.
Точка устройства duffs состоит в том, чтобы уменьшить количество сравнений, выполненных в жесткой реализации memcpy.
Предположим, вы хотите скопировать байты «count» с a на b, прямой подход - сделать следующее:
do {
*a = *b++;
} while (--count > 0);
Сколько раз вам нужно сравнивать счетчик, чтобы увидеть, если он выше 0? «count».
Теперь устройство duff использует неприятный непреднамеренный побочный эффект корпуса коммутатора, который позволяет уменьшить количество сравнений, необходимых для подсчета / 8.
Теперь предположим, что вы хотите скопировать 20 байтов с помощью устройства duffs, сколько сравнений вам нужно? Только 3, поскольку вы копируете восемь байтов за раз, за исключением последнего первого, где вы копируете только 4.
ОБНОВЛЕНО: вам не нужно делать 8 сравнений / case-in-switch, но это разумный компромисс между размером функции и скоростью.
Когда я впервые прочитал его, я автоматически отформатировал его на этом
void dsend(char* to, char* from, count) {
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);
}
}
, и я понятия не имел, что происходит.
Возможно, нет, когда задан этот вопрос , но теперь Wikipedia имеет очень хорошее объяснение
. Устройство является действительным, законным C в силу двух атрибутов в C:
blockquote>
- Расслабленная спецификация оператора switch в определении языка. Во время изобретения устройства это был первый выпуск языка программирования C, который требует только того, чтобы управляемая инструкция коммутатора была синтаксически корректным (составным) оператором, в котором метки меток могут отображаться с префиксом любого подзаголовка. В сочетании с тем фактом, что при отсутствии инструкции break поток контроля будет проходить через оператор, управляемый одним ярлыком case, на управляемый следующим, это означает, что код указывает последовательность копий count из последовательные исходные адреса на порт вывода с отображением памяти.
- Возможность юридически переходить в середину цикла в C.
Объяснение в журнале доктора Добба - лучшее, что я нашел на эту тему.
Это мой момент 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++;
}
:) становится:
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);
}
len%8
равен 4, он выполнит случай 4, случай 2, случай 2 и случай 1, а затем отскочит назад и выполнит все случаи из следующего цикла вперед. Это часть, которая требует объяснения, как цикл и оператор переключения «взаимодействуют».
– ShreevatsaR
24 January 2012 в 14:24
len % 8
байты не будут скопированы?
– newbie
30 March 2014 в 22:54
do
так много. Вместо этого посмотрите на switch
и while
в виде старомодных вычисленных GOTO
статусов или ассемблерных jmp
операторов со смещением. switch
выполняет некоторую математику, а затем jmp
s в нужном месте. while
выполняет булевскую проверку, а затем слепо jmp
s справа, где находился do
.
– Clinton Pierce
17 September 2015 в 18:40
1: Устройство Duffs - это особая модификация разворота цикла. Что такое разворот цикла? Если у вас есть операция для выполнения N раз в цикле, вы можете торговать размером программы для скорости, выполняя цикл N / n раз, а затем в петле, вставляя (разматывая) код цикла n раз, например. заменяя:
for (int i=0; i<N; i++) {
// [The loop code...]
}
с
for (int i=0; i<N/n; i++) {
// [The loop code...]
// [The loop code...]
// [The loop code...]
...
// [The loop code...] // n times!
}
Это отлично работает, если N% n == 0 - нет необходимости в Duff! Если это не так, вам придется обрабатывать остаток - это боль.
2: Как устройство Duffs отличается от этого стандартного цикла? Устройство Duffs - это всего лишь умный способ борьбы с циклами циклов останова, когда N% n! = 0. Весь do / while выполняет N / n число раз в соответствии со стандартным циклом (поскольку применяется случай 0). При последнем прохождении цикла («N / n + 1'-й раз) случай срабатывает, и мы переходим в случай N% n и запускаем код цикла« остальное »количество раз.
Для устройства Даффа есть две ключевые вещи. Во-первых, я подозреваю, что это легче понять, цикл развернут. Это уменьшает размер более крупного кода для большей скорости, избегая некоторых из служебных задач, связанных с проверкой завершения цикла и переходом на вершину цикла. CPU может работать быстрее, когда он выполняет прямолинейный код вместо перехода.
Второй аспект - это оператор switch. Это позволяет коду с первого раза перейти в середину середины цикла. Удивительная часть большинства людей заключается в том, что такая вещь разрешена. Ну, это разрешено. Выполнение начинается с вычисленного ярлыка case, а затем попадает через в каждый следующий оператор присваивания, как и любой другой оператор switch. После последней метки ярлыка выполнение достигает нижней части цикла, после чего оно возвращается к вершине. Верхняя часть цикла внутри оператора switch, поэтому переключатель больше не переоценивается.
Исходный цикл разматывается восемь раз, поэтому число итераций разделяется на восемь. Если количество байтов, которые нужно скопировать, не кратно восьми, то есть несколько оставшихся байтов. Большинство алгоритмов, которые копируют блоки байтов за один раз, будут обрабатывать оставшиеся байты в конце, но устройство Даффа обрабатывает их в начале. Функция вычисляет count % 8
для оператора switch, чтобы определить, что останется в остатке, перескакивает на метку case для этого количества байтов и копирует их. Затем цикл продолжает копировать группы из восьми байтов.