Введение
Программа, исходный код которой состоит исключительно из вызовов функций.
В информатике, потоковый код — это техника программирования, в которой код по сути состоит исключительно из вызовов подпрограмм. Он часто используется в компиляторах, которые могут генерировать код в такой форме или быть реализованными в ней сами. Код может обрабатываться интерпретатором или представлять собой просто последовательность инструкций вызова машинного кода. Потоковый код обладает большей плотностью, чем код, генерируемый альтернативными методами генерации и альтернативными соглашениями о вызовах. В архитектурах с кэшем он может выполняться немного медленнее. Однако программа, достаточно маленькая, чтобы поместиться в кэш процессора, может работать быстрее, чем более крупная программа, страдающая от частых промахов кэша. Небольшие программы также могут быстрее переключаться между потоками, когда другие программы заполнили кэш. Потоковый код наиболее известен своим использованием во многих компиляторах языков программирования, таких как Forth, во многих реализациях BASIC, некоторых реализациях COBOL, ранних версиях B и других языках для небольших миникомпьютеров и для любительских радиоспутников.
История
Обычный способ создания компьютерных программ — использование компилятора для перевода исходного кода (написанного на каком-то символическом языке) в машинный код. Полученный исполняемый файл обычно быстрый, но поскольку он специфичен для аппаратной платформы, он не является переносимым. Другой подход заключается в генерации инструкций для виртуальной машины и использовании интерпретатора на каждой аппаратной платформе. Интерпретатор создает экземпляр среды виртуальной машины и выполняет инструкции. Таким образом, только интерпретатор должен быть скомпилирован. Ранние компьютеры имели относительно небольшой объем памяти. Например, большинство Data General Nova, IBM 1130 и многие из первых микрокомпьютеров имели всего 4 кБ оперативной памяти. Следовательно, много времени тратилось на поиск способов уменьшить размер программы, чтобы она поместилась в доступную память. Одно из решений — использовать интерпретатор, который читает символический язык по частям и вызывает функции для выполнения действий. Поскольку исходный код обычно намного компактнее, чем полученный машинный код, это может снизить общее потребление памяти. Именно поэтому Microsoft BASIC является интерпретатором: его собственный код должен был разделять 4 кБ памяти машин, таких как Altair 8800, с исходным кодом пользователя. Компилятор переводит с исходного языка в машинный код, поэтому компилятор, исходный код и результат должны находиться в памяти одновременно. В интерпретаторе выходных данных нет. *Код с прошивкой* — это стиль форматирования компилируемого кода, который минимизирует использование памяти. Вместо того чтобы записывать каждый шаг операции при каждом ее возникновении в программе, как это было принято, например, в макроассемблерах, компилятор записывает каждый общий фрагмент кода в подпрограмму. Таким образом, каждый фрагмент существует только в одном месте в памяти (см. «Не повторяйся»). Приложение верхнего уровня в этих программах может состоять только из вызовов подпрограмм. Многие из этих подпрограмм, в свою очередь, также состоят только из вызовов подпрограмм нижнего уровня. Основные вычислительные системы и некоторые ранние микропроцессоры, такие как RCA 1802, требовали несколько инструкций для вызова подпрограммы. В приложении верхнего уровня и во многих подпрограммах эта последовательность постоянно повторяется, при этом только адрес подпрограммы меняется от одного вызова к другому. Это означает, что программа, состоящая из множества вызовов функций, также может содержать значительное количество повторяющегося кода. Для решения этой проблемы системы с кодом с прошивкой использовали псевдокод для представления вызовов функций одним оператором. Во время выполнения крошечный «интерпретатор» сканировал бы код верхнего уровня, извлекал адрес подпрограммы в памяти и вызывал ее. В других системах эта же основная концепция реализуется в виде таблицы переходов, таблицы диспетчеризации или виртуальной таблицы методов, все из которых состоят из таблицы адресов подпрограмм. В 1970-х годах разработчики аппаратного обеспечения приложили значительные усилия, чтобы сделать вызовы подпрограмм более быстрыми и простыми. В улучшенных конструкциях для вызова подпрограммы требуется только одна инструкция, поэтому использование псевдоинструкции не экономит место. Кроме того, производительность этих вызовов практически не имеет дополнительных накладных расходов. Сегодня, хотя почти все языки программирования ориентированы на изоляцию кода в подпрограммы, они делают это для ясности и удобства сопровождения кода, а не для экономии места. Системы с кодом с прошивкой экономят место, заменяя список вызовов функций, где только адрес подпрограммы меняется от одного вызова к следующему, списком токенов выполнения, которые по сути являются вызовами функций без кода операции вызова, оставляя только список адресов. За прошедшие годы программисты создали множество вариаций этого «интерпретатора» или «маленького селектора». Конкретный адрес в списке адресов может быть извлечен с помощью индекса, регистра общего назначения или указателя. Адреса могут быть прямыми или косвенными, смежными или несмежными (связанными указателями), относительными или абсолютными, разрешаемыми во время компиляции или динамически создаваемыми. Ни одна из этих вариаций не является «лучшей» для всех ситуаций.
Модели резьбы
Практически весь исполняемый многопоточный код использует один из этих методов для вызова подпрограмм (каждый метод называется "моделью потоков").
Занавески подпрограмм
Так называемый "подпрограммный поточный код" (также "вызов-поточный код") состоит из серии машинных инструкций "вызова подпрограммы" (или адресов функций для "вызова", в отличие от использования "перехода" при прямой поточной адресации). Ранние компиляторы для ALGOL, Fortran, Cobol и некоторых систем Forth часто генерировали подпрограммный поточный код. Код во многих из этих систем работал со стеком операндов, организованным по принципу LIFO (последним пришел – первым ушел), для которого теория компиляции была хорошо разработана. Большинство современных процессоров имеют аппаратную поддержку инструкций "вызов" и "возврат из подпрограммы", поэтому накладные расходы, связанные с одной дополнительной машинной инструкцией при каждом вызове, несколько снижаются. Антон Эртл, соавтор компилятора Gforth, заявил, что "вопреки распространенному мнению, подпрограммная адресация обычно медленнее прямой адресации". Однако, последние тесты Эртла проводились на смарт-картах, игрушках, калькуляторах и часах. Битово-ориентированный токенизированный код, используемый в PBASIC, можно рассматривать как разновидность поточного кода Хаффмана.
Реже используемые нитки
Примером является строковое нить, в котором операции идентифицируются строками, обычно находящимися в хеш-таблице. Это использовалось в самых ранних реализациях Forth Чарльза Х. Мура и в экспериментальном аппаратном интерпретируемом компьютерном языке Университета Иллинойса. Оно также используется в Bashforth.