stringtranslate.com

Тьфу-тьфу

В программировании thunk — это подпрограмма, используемая для внедрения вычисления в другую подпрограмму. Thunk в основном используются для задержки вычисления до тех пор, пока не понадобится его результат, или для вставки операций в начало или конец другой подпрограммы. Они имеют много других применений в генерации кода компилятора и модульном программировании .

Термин возник как причудливая неправильная форма глагола think . Он относится к первоначальному использованию thunks в компиляторах ALGOL 60 , которые требовали специального анализа (мысли) для определения типа генерируемой процедуры. [1] [2]

Фон

В первые годы исследований компиляторов проводились обширные эксперименты с различными стратегиями оценки . Ключевым вопросом было, как скомпилировать вызов подпрограммы, если аргументы могут быть произвольными математическими выражениями, а не константами. Один подход, известный как « вызов по значению », вычисляет все аргументы перед вызовом, а затем передает полученные значения в подпрограмму. В конкурирующем подходе « вызов по имени » подпрограмма получает неоцененное выражение аргумента и должна его оценить.

Простая реализация «вызова по имени» может заменить код выражения аргумента для каждого появления соответствующего параметра в подпрограмме, но это может создать несколько версий подпрограммы и несколько копий кода выражения. В качестве улучшения компилятор может сгенерировать вспомогательную подпрограмму, называемую thunk , которая вычисляет значение аргумента. Адрес и окружение [a] этой вспомогательной подпрограммы затем передаются исходной подпрограмме вместо исходного аргумента, где она может быть вызвана столько раз, сколько необходимо. Питер Ингерман впервые описал thunks в отношении языка программирования ALGOL 60, который поддерживает оценку вызова по имени. [4]

Приложения

Функциональное программирование

Хотя индустрия программного обеспечения в значительной степени стандартизировала оценку вызовов по значению и вызовов по ссылке , [5] активное изучение вызова по имени продолжалось в сообществе функционального программирования . Это исследование привело к появлению ряда языков программирования с ленивой оценкой , в которых некоторый вариант вызова по имени является стандартной стратегией оценки. Компиляторы для этих языков, такие как Glasgow Haskell Compiler , в значительной степени полагались на thunks, с дополнительной функцией, заключающейся в том, что thunks сохраняют свой начальный результат, чтобы они могли избежать его повторного вычисления; [6] это известно как мемоизация или вызов по необходимости .

Функциональные языки программирования также позволили программистам явно генерировать thunks. Это делается в исходном коде путем оборачивания выражения аргумента в анонимную функцию , которая не имеет собственных параметров. Это предотвращает вычисление выражения до тех пор, пока принимающая функция не вызовет анонимную функцию, тем самым достигая того же эффекта, что и вызов по имени. [7] Внедрение анонимных функций в другие языки программирования сделало эту возможность широко доступной.

Ниже приведена простая демонстрация на JavaScript (ES6):

// 'hypot' — это бинарная функция const hypot = ( x , y ) => Math . sqrt ( x * x + y * y );            // 'thunk' — это функция, которая не принимает аргументов и при вызове выполняет потенциально затратную операцию (в данном примере — вычисление квадратного корня) и/или вызывает некоторый побочный эффект const thunk = () => hypot ( 3 , 4 );      // затем thunk можно передавать без оценки... doSomethingWithThunk ( thunk );// ...или вычисленный thunk (); // === 5 

Объектно-ориентированное программирование

Thunks полезны в объектно-ориентированных программных платформах, которые позволяют классу наследовать несколько интерфейсов , что приводит к ситуациям, когда один и тот же метод может быть вызван через любой из нескольких интерфейсов. Следующий код иллюстрирует такую ​​ситуацию в C++ .

класс A { public : virtual int Access () const { return value_ ; }            частный : int value_ ; };  класс B { public : virtual int Access () const { return value_ ; }            частный : int value_ ; };  класс C : public A , public B { public : int Access () const override { return better_value_ ; }                 частный : int better_value_ ; };  int use ( B * b ) { return b -> Access (); }      int main () { // ... B some_b ; использование ( & some_b ); C some_c ; использование ( & some_c ); }         

В этом примере код, сгенерированный для каждого из классов A, B и C, будет включать таблицу диспетчеризации , которая может использоваться для вызова Accessобъекта этого типа через ссылку, имеющую тот же тип. Класс C будет иметь дополнительную таблицу диспетчеризации, используемую для вызова объекта Accessтипа C через ссылку типа B. Выражение b->Access()будет использовать собственную таблицу диспетчеризации B или дополнительную таблицу C, в зависимости от типа объекта, на который ссылается b. Если он ссылается на объект типа C, компилятор должен гарантировать, что Accessреализация C получит адрес экземпляра для всего объекта C, а не унаследованной части B этого объекта. [8]

В качестве прямого подхода к этой проблеме корректировки указателя компилятор может включить целочисленное смещение в каждую запись таблицы диспетчеризации. Это смещение представляет собой разницу между адресом ссылки и адресом, требуемым реализацией метода. Код, сгенерированный для каждого вызова через эти таблицы диспетчеризации, должен затем извлечь смещение и использовать его для корректировки адреса экземпляра перед вызовом метода.

Описанное выше решение имеет проблемы, схожие с наивной реализацией вызова по имени, описанной ранее: компилятор генерирует несколько копий кода для вычисления аргумента (адреса экземпляра), а также увеличивает размеры таблицы диспетчеризации для хранения смещений. В качестве альтернативы компилятор может сгенерировать thunk-регулятор вместе с реализацией C, Accessкоторая корректирует адрес экземпляра на требуемую величину, а затем вызывает метод. Thunk может появиться в таблице диспетчеризации C для B, тем самым устраняя необходимость для вызывающих программ самостоятельно корректировать адрес. [9]

Численные расчеты, требующие оценок в нескольких точках

Подпрограммы для вычислений, таких как интегрирование, должны вычислять выражение в нескольких точках. Вызов по имени использовался для этой цели в языках, которые не поддерживали замыкания или параметры процедур .

Взаимодействие

Thunk широко использовались для обеспечения взаимодействия между программными модулями, чьи процедуры не могут вызывать друг друга напрямую. Это может произойти из-за того, что процедуры имеют разные соглашения о вызовах , работают в разных режимах ЦП или адресных пространствах или, по крайней мере, одна из них работает в виртуальной машине . Компилятор (или другой инструмент) может решить эту проблему, создав thunk, который автоматизирует дополнительные шаги, необходимые для вызова целевой процедуры, будь то преобразование аргументов, копирование их в другое место или переключение режима ЦП. Успешный thunk минимизирует дополнительную работу, которую должен выполнить вызывающий по сравнению с обычным вызовом.

Большая часть литературы по интероперабельности thunks относится к различным платформам Wintel , включая MS-DOS , OS/2 , [10] Windows [11] [12] [13] [14] и .NET , а также к переходу от 16-битной к 32-битной адресации памяти. Поскольку клиенты переходили с одной платформы на другую, thunks стали необходимы для поддержки устаревшего программного обеспечения, написанного для старых платформ.

Переход от 32-битного к 64-битному коду на x86 также использует форму преобразования ( WoW64 ). Однако, поскольку адресное пространство x86-64 больше, чем доступное для 32-битного кода, старый механизм «generic thunk» не мог использоваться для вызова 64-битного кода из 32-битного кода. [15] Единственный случай, когда 32-битный код вызывает 64-битный код, — это преобразование WoW64 API Windows в 32-битный.

Наложения и динамическое связывание

В системах, в которых отсутствует автоматическое виртуальное аппаратное обеспечение памяти , thunks могут реализовывать ограниченную форму виртуальной памяти, известную как overlays . С помощью overlays разработчик делит код программы на сегменты, которые могут быть загружены и выгружены независимо, и определяет точки входа в каждый сегмент. Сегмент, который вызывает другой сегмент, должен делать это косвенно через таблицу ветвлений . Когда сегмент находится в памяти, записи его таблицы ветвлений переходят в сегмент. Когда сегмент выгружается, его записи заменяются «reload thunks», которые могут перезагружать его по требованию. [16]

Аналогично, системы, которые динамически связывают модули программы вместе во время выполнения, могут использовать thunks для соединения модулей. Каждый модуль может вызывать другие через таблицу thunks, которую компоновщик заполняет при загрузке модуля. Таким образом, модули могут взаимодействовать без предварительного знания того, где они находятся в памяти. [17]

Смотрите также

Технологии Thunk

Связанные концепции

Примечания

  1. ^ Thunk — это ранний ограниченный тип замыкания . Окружение, передаваемое для thunk, — это окружение выражения, а не вызываемой процедуры. [3]

Ссылки

  1. ^ Эрик Рэймонд отвергает «пару звукоподражательных мифов, циркулирующих о происхождении этого термина» и цитирует изобретателей thunk, вспоминая, что термин «был придуман после того, как они поняли (в предрассветные часы после многочасовых обсуждений), что тип аргумента в Algol-60 можно вычислить заранее с помощью небольшого размышления во время компиляции [...] Другими словами, он «уже был обдуман»; поэтому его окрестили thunk , что является «прошедшим временем от «think» в два часа ночи». См.: Raymond, Eric S. (1996). Raymond, Eric S. (ред.). The New Hacker's Dictionary. MIT Press. стр. 445. ISBN 9780262680929. Получено 25.05.2015 .
  2. См. Ингерман (1961): «Транслятор знает, какой тип thunk создать, учитывая формирование фактического параметра и ранее просмотренные объявления.… [К]огда компилируется объявление процедуры, транслятор, снова наблюдая за синтаксисом, знает, какой тип адреса ожидать от thunk».
  3. ^ ET Irons (1961-01-01). «Комментарии по реализации рекурсивных процедур и блоков в ALGOL». Сообщения ACM . 4 (1). Ассоциация вычислительной техники (ACM): 65–69. doi : 10.1145/366062.366084 . ISSN  0001-0782. S2CID  14646332.
  4. ^ Ингерман, П. З. (1961-01-01). «Thunks: способ компиляции операторов процедур с некоторыми комментариями по объявлениям процедур». Сообщения ACM . 4 (1). Ассоциация вычислительной техники (ACM): 55–58. doi : 10.1145/366062.366084 . ISSN  0001-0782. S2CID  14646332.
  5. ^ Скотт, Майкл (2009). Прагматика языка программирования . стр. 395.
  6. ^ Марлоу, Саймон (2013). Параллельное и одновременное программирование в Haskell . стр. 10.
  7. ^ Куиннек, Кристиан (2003). Lisp в небольших пьесах . стр. 176.
  8. ^ Страуструп, Бьярне (осень 1989 г.). "Множественное наследование для C++" (PDF) . Вычислительные системы . 1 (4). USENIX . Получено 04.08.2014 .
  9. ^ Driesen, Karel; Hölzle, Urs (1996). "Прямая стоимость вызовов виртуальных функций в C++" (PDF) . Труды конференции ACM SIGPLAN 1996 года по объектно-ориентированным системам программирования, языкам и приложениям, OOPSLA 1996, Сан-Хосе, Калифорния, США, 6-10 октября 1996 года . 11-я OOPSLA 1996: Сан-Хосе, Калифорния, США. ACM . ISBN 0-89791-788-X. Архивировано из оригинала (PDF) 2019-12-29 . Получено 2011-02-24 .[ мертвая ссылка ]
  10. Calcote, John (май 1995 г.). «Thunking: Using 16-Bit Libraries in OS/2 2.0». OS/2 Developer Magazine . 7 (3): 48–56.
  11. ^ Кинг, Адриан (1994). Внутри Microsoft Windows 95 (2-е изд.). Редмонд, Вашингтон, США: Microsoft Press . ISBN 1-55615-626-X.
  12. Руководство программиста по Microsoft Windows 95: ключевые темы по программированию для Windows от группы разработчиков Microsoft Windows (1-е изд.). Редмонд, Вашингтон, США: Microsoft Press . 1995-07-01. ISBN 1-55615-834-3. Получено 2016-05-26 . {{cite book}}: |work=проигнорировано ( помощь )
  13. ^ Хазза, Карен (1997). Написание Windows VxD и драйверов устройств - секреты программирования для виртуальных драйверов устройств (2-е издание, 2-е изд.). Лоуренс, Канзас, США: R&D Books / Miller Freeman, Inc. ISBN  0-87930-438-3.
  14. ^ Каулер, Барри (август 1997 г.). Windows Assembly Language and Systems Programming - 16- и 32-битное низкоуровневое программирование для ПК и Windows (2-е изд.). Лоуренс, Канзас, США: R&D Books / Miller Freeman, Inc. ISBN  0-87930-474-X.
  15. ^ "Почему вы не можете выбирать между 32-битной и 64-битной Windows?". The Old New Thing . 2008-10-20.
  16. ^ Брайт, Уолтер (1990-07-01). "Виртуальная память для 640K DOS". Журнал доктора Добба . Получено 2014-03-06 .
  17. ^ Левин, Джон Р. (2000) [октябрь 1999]. Компоновщики и загрузчики. Серия Моргана Кауфмана по программной инженерии и программированию (1-е изд.). Сан-Франциско, США: Morgan Kaufmann . ISBN 1-55860-496-0. OCLC  42413382. Архивировано из оригинала 2012-12-05 . Получено 2020-01-12 .Код: [1][2] Опечатки: [3]