Почему Кнут использует этот неуклюжий декремент? - PullRequest
0 голосов
/ 30 декабря 2018

Я смотрю на код профессора Дона Кнута, написанный на CWEB, который конвертируется в C. Конкретный пример - dlx1.w, доступный на сайте Кнута

Вна одном этапе значение .len struct nd [cc] уменьшается, и это делается неуклюже:

  o,t=nd[cc].len-1;
  o,nd[cc].len=t;

(Это вопрос, специфичный для Кнута, поэтому, возможно, вы уже знаете, что«o» - это макрос препроцессора для приращения «mems», который представляет собой промежуточный итог затраченных усилий, измеряемый путем доступа к 64-битным словам.) Значение, оставшееся в «t», определенно не используется ни для чего другого.(Примером здесь является строка 665 dlx1.w или строка 193 dlx1.c после ctangle.)

Мой вопрос: почему Кнут пишет это так, а не

nd[cc].len--;

, который он фактически использует в другом месте (строка 551 из dlx1.w):

oo,nd[k].len--,nd[k].aux=i-1;

(И «oo» - это аналогичный макрос для увеличения «mems» в два раза - но здесь есть некоторая тонкостьпотому что .len и .aux хранятся в одном и том же 64-битном слове. Чтобы присвоить значения S.len и S.aux, обычно будет учитываться только одно приращение к мемам.)

Моя единственная теориячто декремент состоит из двух обращений к памяти: сначала для поиска, затем для назначения.(Это правильно?) И этот способ написания это напоминание о двух шагах.Это было бы необычно многословно для Кнута, но, возможно, это инстинктивная памятная записка, а не дидактизм.

Для чего я стоил, я искал в документации CWEB , не найдя ответа.Мой вопрос, вероятно, больше относится к стандартным практикам Кнута, которые я постепенно поднимаю.Я был бы заинтересован в любых ресурсах, где эти практики изложены (и, возможно, критикованы) как блок - но сейчас давайте сосредоточимся на том, почему Кнут пишет это так.

Ответы [ 2 ]

0 голосов
/ 30 декабря 2018

Предварительное замечание: при грамотном программировании в стиле Кнута (то есть при чтении программ WEB или CWEB) «настоящая» программа, задуманная Кнутом, не является ни «исходным» .w файлом, ни сгенерированным (запутанным) .c файл, но набрано (тканый) вывод.Исходный файл .w лучше всего рассматривать как средство его создания (и, конечно, также источник .c, который передается компилятору).(Если у вас нет под рукой cweave и TeX; я набрал некоторые из этих программ здесь ; эта программа DLX1 здесь .)

Так что в этомВ этом случае я бы описал местоположение в коде как модуль 25 DLX1 или подпрограмму «cover»:

question

В любом случае, чтобы вернуться кАктуальный вопрос: обратите внимание, что эта (DLX1) - одна из программ, написанных для Искусство компьютерного программирования .Поскольку сообщение о времени, затраченном программой на «секунды» или «минуты», становится бессмысленным из года в год, он сообщает, сколько времени заняла программа в количестве «мемов» плюс «упс», в которых преобладают «мемы», то естьколичество обращений к памяти до 64-битных слов (обычно).Таким образом, книга содержит утверждения типа «эта программа находит ответ на эту проблему за 3,5 гигабайма времени работы».Кроме того, операторы предназначены в основном для самой программы / алгоритма, а не для конкретного кода, сгенерированного конкретной версией компилятора для определенного оборудования.(В идеале, когда детали очень важны, он пишет программу в MMIX или MMIXAL и анализирует ее работу на оборудовании MMIX, но это редко.) Подсчет mems (о чем сообщается выше) является целью вставки o иoo инструкция в программу.Обратите внимание, что более важно получить это право для инструкций «внутреннего цикла», которые выполняются много раз, например, для всего, что в подпрограмме cover в этом случае.

Это подробно описано в разделе 1.3.1 ′ (часть Fascicle 1 ):

Время. […] Время выполнения программы зависит не только от тактовой частоты, но ио количестве функциональных блоков, которые могут быть активны одновременно, и о степени их конвейерности;это зависит от методов, используемых для предварительной выборки команд перед их выполнением;это зависит от размера оперативной памяти, которая используется для иллюзии 2 64 виртуальных байтов;и это зависит от размеров и стратегий размещения кэшей и других буферов и т. д. и т. д.

Для практических целей время выполнения программы MMIX часто можно оценить удовлетворительно, назначив фиксированную стоимостькаждая операция, основанная на приблизительном времени работы, которое будет получено на высокопроизводительной машине с большим объемом оперативной памяти;так вот что мы будем делать.Предполагается, что каждая операция принимает целое число υ, где υ (произносится как «oops») - это единица, которая представляет время тактового цикла в конвейерной реализации.Хотя значение υ уменьшается по мере совершенствования технологии, мы всегда следим за последними достижениями, потому что измеряем время в единицах υ, а не в наносекундах.Время выполнения в наших оценках также будет зависеть от количества ссылок на память или мемов, которые использует программа;это количество инструкций по загрузке и хранению.Например, мы будем считать, что каждая инструкция LDO (загрузка окта) стоит µ + υ, где µ - средняя стоимость ссылки на память.Общее время выполнения программы может быть представлено, скажем, как 35µ + 1000υ, что означает «35 мемов плюс 1000 упс». Отношение µ / υ неуклонно растет на протяжении многих лет;никто не знает наверняка, будет ли эта тенденция продолжаться, но опыт показал, что µ и v заслуживают того, чтобы их рассмотрели независимо.

И он, конечно, понимает отличие от реальности:

Несмотря на то, что мы часто будем использовать допущения Таблицы 1 дляМы должны помнить, что при оценке времени выполнения команды «место в штанах» фактическое время работы может быть весьма чувствительным к порядку выполнения инструкций.Например, целочисленное деление может стоить только одного цикла, если мы сможем найти 60 других вещей, которые нужно сделать между временем, когда мы выполняем команду, и временем, когда нам нужен результат.Некоторым инструкциям LDB (загрузочный байт) может потребоваться ссылаться на память только один раз, если они ссылаются на один и тот же октабайт.Тем не менее, результат команды загрузки обычно не готов к использованию в следующей сразу инструкции.Опыт показывает, что некоторые алгоритмы хорошо работают с кеш-памятью, а другие нет;следовательно, µ не является на самом деле постоянным.Даже расположение инструкций в памяти может оказать существенное влияние на производительность, потому что некоторые инструкции могут быть получены вместе с другими.[…] Только мета-симулятор можно доверять, чтобы предоставлять достоверную информацию о реальном поведении программы на практике;но такие результаты могут быть трудно интерпретировать, потому что бесконечно много конфигураций возможно.Вот почему мы часто прибегаем к гораздо более простым оценкам таблицы 1.

Наконец, мы можем использовать Godbolt's Compiler Explorer , чтобы посмотреть на код, сгенерированный типичным компилятором для этого кода.,(В идеале мы посмотрим на инструкции MMIX, но, поскольку мы не можем этого сделать, давайте согласимся с настройками по умолчанию, которые кажутся x68-64 gcc 8.2.) Я удалил все o s и oo s.

Для версии кода с:

  /*o*/ t = nd[cc].len - 1;
  /*o*/ nd[cc].len = t;

сгенерированный код для первой строки:

  movsx rax, r13d
  sal rax, 4
  add rax, OFFSET FLAT:nd+8
  mov eax, DWORD PTR [rax]
  lea r14d, [rax-1]

, а для второй строки:

  movsx rax, r13d
  sal rax, 4
  add rax, OFFSET FLAT:nd+8
  mov DWORD PTR [rax], r14d

Для версии кода с:

  /*o ?*/ nd[cc].len --;

сгенерированный код:

  movsx rax, r13d
  sal rax, 4
  add rax, OFFSET FLAT:nd+8
  mov eax, DWORD PTR [rax]
  lea edx, [rax-1]
  movsx rax, r13d
  sal rax, 4
  add rax, OFFSET FLAT:nd+8
  mov DWORD PTR [rax], edx

, который, как вы можете видеть (даже не зная много о x86-64 сборка) - это просто конкатенация кода, сгенерированного в первом случае (за исключением использования регистра edx вместо r14d), так что запись декремента в одну строку не спасла вас ни от одного мема.В частности, было бы неправильно считать его единым, особенно для чего-то вроде cover, которое в этом алгоритме вызывается огромное количество раз (танцующие ссылки для точного покрытия).

Так чтоВерсия, написанная Кнутом, является правильной, для ее цели подсчета количества мемов.Он также мог написать oo,nd[cc].len--; (считая два мема), как вы заметили, но, возможно, в этом случае это может выглядеть как ошибка на первый взгляд.(Кстати, в вашем примере в вопросе oo,nd[k].len--,nd[k].aux=i-1; два мема поступают от нагрузки и хранилища в --; не в двух хранилищах.)

0 голосов
/ 30 декабря 2018

Кажется, что вся эта практика основана на ошибочной идее / модели того, как работает C, что существует некоторое соответствие между работой, выполняемой абстрактной машиной, и фактической программой, которая была выполнена (то есть ошибкой «C - портативный ассемблер»).).Я не думаю, что мы можем дать гораздо больше ответов о том, почему появляется именно этот фрагмент кода, за исключением того, что он является необычной идиомой для подсчета нагрузок и сохранения на абстрактной машине как отдельного.

...