stringtranslate.com

Thunk

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

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

Фон

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

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

Приложения

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

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

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

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

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

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

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

класс A { public : virtual int Access () const { возвращаемое значение_ ; }            частный : int value_ ; };  класс B { public : virtual int Access () const { возвращаемое значение_ ; }            частный : 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]

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

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

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

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

Совместимость

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

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

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

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

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

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

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

Thunk технологии

Связанные понятия

Примечания

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

Рекомендации

  1. ^ Эрик Рэймонд отвергает «пару звукоподражательных мифов, циркулирующих о происхождении этого термина», и цитирует изобретателей «thunk», напоминая, что этот термин «был придуман после того, как они поняли (в первые часы после нескольких часов обсуждения), что тип аргумент в Алголе-60 можно было вычислить заранее, немного подумав во время компиляции [...] Другими словами, он «уже был продуман»; поэтому его окрестили thunk , что представляет собой «прошедшее время «думай» в два часа ночи». См.: Рэймонд, Эрик С. (1996). Рэймонд, Эрик С. (ред.). The New Hacker's Dictionary. MIT Press. стр. 445. ISBN. 9780262680929. Проверено 25 мая 2015 г.
  2. ^ См. Ингерман (1961): «Переводчик знает, какой тип структуры создать, учитывая формирование фактического параметра и ранее отсканированные объявления.… [Когда] когда объявление процедуры компилируется, переводчик, опять же, наблюдая за синтаксисом , знает, какой адрес ожидать от преобразователя."
  3. ^ ET Айронс (1 января 1961). «Комментарии к реализации рекурсивных процедур и блоков в АЛГОЛЕ». Коммуникации АКМ . Ассоциация вычислительной техники (ACM). 4 (1): 65–69. дои : 10.1145/366062.366084 . ISSN  0001-0782. S2CID  14646332.
  4. ^ Ингерман, ПЗ (1 января 1961). «Thunks: способ компиляции операторов процедур с некоторыми комментариями к объявлениям процедур». Коммуникации АКМ . Ассоциация вычислительной техники (ACM). 4 (1): 55–58. дои : 10.1145/366062.366084 . ISSN  0001-0782. S2CID  14646332.
  5. ^ Скотт, Майкл (2009). Прагматика языков программирования . п. 395.
  6. ^ Марлоу, Саймон (2013). Параллельное и параллельное программирование на Haskell . п. 10.
  7. ^ Кейннек, Кристиан (2003). Лисп в маленьких кусочках . п. 176.
  8. ^ Страуструп, Бьярн (осень 1989 г.). «Множественное наследование для C++» (PDF) . Вычислительные системы . УСЕНИКС . 1 (4) . Проверено 4 августа 2014 г.
  9. ^ Дрисен, Карел; Хёльцле, Урс (1996). «Прямая стоимость вызовов виртуальных функций в C++» (PDF) . Материалы конференции ACM SIGPLAN 1996 г. по системам, языкам и приложениям объектно-ориентированного программирования, OOPSLA 1996, Сан-Хосе, Калифорния, США, 6-10 октября 1996 г. 11-я OOPSLA 1996: Сан-Хосе, Калифорния, США. АКМ . ISBN 0-89791-788-Х. Архивировано из оригинала (PDF) 29 декабря 2019 г. Проверено 24 февраля 2011 г.[ мертвая ссылка ]
  10. ^ Калькот, Джон (май 1995 г.). «Мышление: использование 16-битных библиотек в OS / 2 2.0». Журнал разработчиков OS/2 . 7 (3): 48–56.
  11. ^ Кинг, Адриан (1994). Внутри Microsoft Windows 95 (2-е изд.). Редмонд, Вашингтон, США: Microsoft Press . ISBN 1-55615-626-Х.
  12. ^ Руководство программиста по Microsoft Windows 95: Ключевые темы по программированию для Windows от группы разработчиков Microsoft Windows (1-е изд.). Редмонд, Вашингтон, США: Microsoft Press . 1 июля 1995 г. ISBN 1-55615-834-3. Проверено 26 мая 2016 г. {{cite book}}: |work=игнорируется ( помощь )
  13. ^ Хазза, Карен (1997). Написание Windows VxD и драйверов устройств — секреты программирования драйверов виртуальных устройств (2-е издание, 2-е изд.). Лоуренс, Канзас, США: Книги по исследованиям и разработкам / Miller Freeman, Inc. ISBN  0-87930-438-3.
  14. ^ Каулер, Барри (август 1997 г.). Язык ассемблера Windows и системное программирование - 16- и 32-битное низкоуровневое программирование для ПК и Windows (2-е изд.). Лоуренс, Канзас, США: Книги по исследованиям и разработкам / Miller Freeman, Inc. ISBN  0-87930-474-Х.
  15. ^ «Почему вы не можете выбирать между 32-битной и 64-битной Windows?». Старая новая вещь . 20 октября 2008 г.
  16. ^ Брайт, Уолтер (1 июля 1990 г.). «Виртуальная память для 640К DOS». Журнал доктора Добба . Проверено 6 марта 2014 г.
  17. ^ Левин, Джон Р. (2000) [октябрь 1999 г.]. Линкеры и загрузчики. Серия Моргана Кауфмана по разработке программного обеспечения и программированию (1-е изд.). Сан-Франциско, США: Морган Кауфманн . ISBN 1-55860-496-0. OCLC  42413382. Архивировано из оригинала 05 декабря 2012 г. Проверено 12 января 2020 г.Код: [1][2] Ошибки: [3]