Кутейников Дмитрий : другие произведения.

О троичном компе

Самиздат: [Регистрация] [Найти] [Рейтинги] [Обсуждения] [Новинки] [Обзоры] [Помощь|Техвопросы]
Ссылки:


 Ваша оценка:
  • Аннотация:
    Собираю в одну кучу разные соображения о троичных компах. Не обзор, не история - просто соображения программиста, как это всё могло бы выглядеть и каким бы его хотелось видеть.

  Disclaimer: я не виноват, они(*) меня заставили!
  
  * - Умные мысли.
  
  Итак, я задумался (довольно крепко) о троичных вычислениях и троичном компьютере (точнее процессоре) общего назначения.
  Постулируем, что аппаратная часть у нас есть (троичные ячейки памяти и троичные линии данных). На данный момент сделать её возможно (только никому не нужно), и этого достаточно.
  Я опираюсь на процессор 8080, систему команд которого знаю почти наизусть, и озвучу соображения, связанные с семейством х86 (реальный опыт у меня есть с 8086 и некоторыми расширениями 80386, но для именно соображений этого хватит)
  Процессор 8080 был весьма простым(*) и восьмибитным. У него был один аккумулятор, с которым производились все вычисления. Аккумулятор был первым операндом и в него же помещался результат, вторым операндом был другой регистр или ячейка памяти. Помимо аккумулятора, было ещё шесть регистров общего назначения (которые можно было использовать попарно для хранения 16-битных значений и адресации), указатель инструкции, указатель стека и регистр флагов.
  
  * - С точки зрения программиста.
  
  Система команд, насколько я могу судить, вполне стандартная для той эпохи: перемещение данных, арифметические и логические операции (с обязательным участием аккумулятора), сдвиговые операции, передача управления (безусловная и условная, с возвратом и без), манипуляция флагами и некоторое малое количество специальных команд - вроде ввода-вывода через порты. Практически все команды были однобайтовыми, но некоторые имели операнд - один или два байта. Процессор z80, ставший куда популярнее, добавил к этому префиксы работы с двумя дополнительными индексными регистрами, подняв максимальную длину до пяти, насколько я помню, байт (четыре - точно).
  И сразу же - первое из обещанных "соображений из будущего". С точки зрения подсистемы памяти, очень хорошо, когда команды процессора имеют одинаковый размер (или несколько кратных размеров), в идеале - равный ширине шины данных. Для современных х86 длина одной команды может варьироваться от одного до чуть ли не пятнадцати байт, и это совершенно ужасно. Поставим галочку и продолжим.
  В 8080 опытные программисты могли уверенно читать машинный код, записанный в восьмеричной системе счисления: старшие два бита (старшая из трёх цифр) определяли категорию команды, ещё две тройки бит (вторая и третья цифра) определяли операнды, причём таких команд было больше половины, да и остальные тоже были сгруппированы. Применительно к современным компьютерам это уже не нужно: вручную с машкодами никто не работает, а компилятору всё равно, но вот разрядность команд тесно связана с разрядностью данных.
  В двоичных компьютерах минимальным квантом передаваемой информации является байт - восемь бит. Это две четвёрки бит - две цифры в шестнадцатеричном представлении. Это диапазон в 256 значений (со знаком или без - определяет программист для каждой операции, где знак может повлиять на результат). Этого хватило для всех команд 8080 (и даже нашлось место для расширений z80), но не хватило для адресации памяти, и потому адрес был 16-битным... потом стал 32-битным (сначала формально, как в 8086 и 80286, а потом и фактически, в 80386). Сейчас уже и этого мало, и для адреса используют 64 разряда. Поставим ещё одну галочку и продолжим.
  Никакая программа (как минимум - никакая осмысленная) невозможна без условных переходов. Для этого необходимы флаги (в современном х86-процессоре их уже больше шестнадцати против пяти у 8080) и условные переходы. Если оставить в стороне всякую экзотику вроде защищённого режима, обрабатываемую через механизм прерываний, остаётся арифметика. У 8080 таких флагов четыре: знак (выставляется при отрицательном результате), ноль (очевидно), перенос ("лишний" 9-й или 17-й бит для арифметики) и чётность (выставляется, если в результате - чётное количество единиц, полагаю, наследие телеграфа). Из них условными операторами (переход, вызов подпрограммы и возврат) контролируются только три: ноль, перенос и чётность (у 8086 флагов больше - в связи с появлением операций умножения и деления добавили переполнение, и условные операторы используют 16 их комбинаций).
  Переход на троичную логику, очевидно, немедленно объединяет флаги знак и ноль. Остаются перенос, переполнение и чётность. Чётность я предлагаю похоронить с почестями (в троичной логике цифр три, и смысла в чётности не остаётся - напомню, что флаг означает чётное количество единиц в результате). Переполнение у нас волшебным образом начинает различать переполнение вверх (слишком большой результат, overflow) и вниз (слишком маленький результат, underflow), которые в двоичной логике приходилось ловить отдельно, уже после обнаружения факта переполнения вообще. Перенос - это всего лишь дополнительный разряд для арифметики и по определению имеет ту же разрядность, что и вся система. Итого, мы получаем три флага (сравнение, переполнение и перенос), которые, внезапно, во всех сочетаниях дают нам 27 комбинаций (против уже упоминавшихся 16 в х86 или 6 у 8080). Однако, внимательное рассмотрение показывает, что в х86 (как в более богатом на условные переходы, зато без условных вызовов - что, в общем, не критично) есть условные переходы по каждому состоянию каждого флага, плюс условные переходы по арифметическим критериям: меньше, больше, равно, не равно, меньше или равно, больше или равно. Есть ещё некие странные "выше" и "ниже", но, насколько я могу судить, это издержки неопределённости знака при двоичном представлении числа. Если же суммировать вышеизложенное, нам требуется по три условных перехода для переноса и переполнения, и ещё шесть - для знака. Итого - двенадцать.
  И вот теперь, наконец, мы приходим к самому интересному пункту, неоднократно упомянутому и уже помеченному целыми двумя галочками. Разрядность.
  Если мы рассматриваем некий абстрактный процессор, то его разрядность не имеет принципиального значения. Однако, если мы делаем что-то прикладное, нам есть смысл внимательно посмотреть по сторонам.
  Известные мне варианты, как правило, используют трайт из шести тритов. Это даёт диапазон от -364 до +364 (729 значений всего). Достаточно близко к байту, удобно записывать как в 27-ричной системе (в этом примере она становится эквивалентом 16-ричной для двоичных компов) двумя цифрами, так и тремя цифрами в 9-ричной системе (которая становится эквивалентом 8-ричной). Два трайта дадут нам диапазон в 531441 значений - что заметно шире, чем 65536 для двух байт (и в плане адресации памяти тоже - это почти на порядок больше, чем даёт двоичный комп с 16-битным адресом, при этом у троичного линий адреса всего 12). Но современные компьютеры уже давно оперируют куда большими объёмами (у меня в ноуте стоит 16 Гб, а на работе в тестовом сервере - 128, и "чо-та маловато, надо до 512 добить")... Ещё одно удвоение разрядности адреса (кстати, а почему мы его удваиваем, а не утраиваем? об этом будет чуть позже) и мы получаем 282"429"536"481 значений... То есть, для адресации современных объёмов нам нужно ещё одно расширение шины адреса, третье, то самое, из-за которого у современных процессоров (х86, как минимум) есть определённые сложности... Нужно ли это нам?
  Давайте посмотрим на совершенно другой аспект. Unicode. Такой очень удобный способ представления символов, когда не надо думать ни про какие кодовые страницы, у каждого символа каждого алфавита на Земле есть свой код и всё хорошо. Сейчас это около миллиона значений (требуется 20,5 бит для хранения - так забавно спроектировано), и чтобы не тратить на каждый символ четыре байта, есть разные хитрые вариации. Сколько тритов нужно, чтобы получить диапазон, превышающий миллион значений? Тринадцать (правда, это будет почти полтора миллиона, но это не важно). А сколько нужно тритов, чтобы обеспечить эквивалент 64-битной адресации (1.8*10^19)... ну, не прям столько, но близко - чтобы на ближайшие несколько лет хватило? Сорок (1,2*10^19). Правда, троичные ячейки хранят больше информации (трайт из шести битов и его 729 значений против 256 у байта).
  И вот тут я задумался. Если мы переходим на троичную логику, то и разрядность как шины адреса, так и регистров имеет смысл утраивать. И тогда для типичного шеститритного трайта следующим уровнем будет восемнадцатитритный регистр, способный адресовать без малого четыреста миллионов ячеек. А следующее расширение даст нам 54 трита и 5,8*10^25 ячеек... Оно, конечно, да - прогресс не стоит на месте... Но мне что-то кажется, что это тяжёлый перебор. Какие есть варианты? И вот тут мне внезапно начинает очень нравиться трайт из ПЯТИ тритов. Ересь и позор? Нуууу... есть немного, наверное. Ни в 9-ричной, ни 27-ричной системе это нормально не записывается - старшая цифра по определению залезает в "вышестоящий" трайт. Однако, три таких трайта, или 15 тритов, дают нам не только примерно 14 миллионов адресов (мобильные устройства, где сейчас оперативка измеряется единицами гигабайт, смотрят с одобрением), но прекрасно хранят один символ юникода (поверьте, это достоинство). А три таких слова дают нам 45 тритов и совершенно заоблачные значения. На минутку, числа с плавающей точкой, использующиеся сейчас чуть более, чем повсеместно, могут иметь разрядность 16 (извращение для видео), 32 (маловато, но раньше было очень в ходу), 48 (неудобно в работе на современных процессорах), 64 (самый ходовой формат сейчас) и 80 (снова неудобно на современных процессорах) бит. Есть и больше, но совсем узкоспециализированные. Перевод плавучки на троичную логику автоматом снимает необходимость в знаковом бите и делает диапазон степеней симметричным вверх и вниз... Словом, всё красиво, все поют и пляшут - в 15-тритной плавучке мы получим совершенно поразительный запас точности для видео, а в 45-тритной - примерно посередине между нынешними 64- и 80-битными.
  Есть ещё один вариант: трайт из четырёх тритов (удобно использовать 9-ричную систему счисления для записи), но 36-тритный адрес даст нам всего 1,5*10^17 адресов, да и плавучка получится слабоватой, а раздувать её до 108 тритов - это совсем себя не любить. Можно, конечно, оставить и шесть...
  По большому счёту, это всё равно лишь разминка для ума... Например, как у нас изменятся всем привычные конструкции языков программирования? Сам я, волею судеб, вынужден писать на С/С++ и не считаю это таким уж большим удовольствием, лично мне ближе Паскаль... и я считаю, что если бы кое-что в нём было сделано с самого начала чуть-чуть иначе - и С не понадобился бы... Начнём с простейшего оператора проверки условия:
  
  if
   condition // не забываем - ТРИ значения
  on+
   ...
  on0
   ...
  on-
   ...
  end_if
  
  И вот уже тут я предвижу возражения: "а почему такая дурная схема?", "а ведь (а<б) может дать только два результата?", "а где фигурные скобки?"... Перепишем:
  
  if
   condition // не забываем - ТРИ значения
  true:
   ...
  false:
   ...
  undef:
   ...
  end_if
  
  Получилось веселее. Прописываем в синтаксисе, что как минимум одна секция должна присутствовать, порядок может быть произвольным, а фигурные скобки не нужны - и счастливые программисты идут записываться к нам в лагерь. Но продолжим. Цикл по счётчику не изменится - не с чего ему меняться... Кстати, в процессорах х86 для циклов есть специальные команды, если в роли счётчика использовать один конкретный регистр... Что ещё? Условные циклы, с препроверкой и постпроверкой. Тут сложнее, но, как и для цикла со счётчиком, мы просто работаем, считая правильным лишь одно условие. В этом плане, кстати, Паскаль забавен: в цикле с постпроверкой указывается условие выхода. Где ещё троичная логика так уж изменит язык программирования? По-моему, это всё!
  В связи с чем возникает следующий неожиданный вопрос: вот мы расписали уже для ассемблера такие чудесные условные переходы в количестве двенадцати штук... Может, сделать их чуть-чуть иначе? Просто три условных перехода (по одному на каждый флаг), но у каждого - три параметра: три адреса, на которые передаётся управление по соответствующему условию... И если делать условные переходы относительными (определённый смысл в этом есть), то 0 весьма логично будет означать, что по этому условию никуда переходить не надо... Возможно, есть смысл (с точки зрения компактности кода) сделать переходов только два, а по нулю всегда продолжать исполнение, но, как и все компромиссы, этот тоже имеет свои минусы.
  
 Ваша оценка:

Связаться с программистом сайта.

Новые книги авторов СИ, вышедшие из печати:
О.Болдырева "Крадуш. Чужие души" М.Николаев "Вторжение на Землю"

Как попасть в этoт список
Сайт - "Художники" .. || .. Доска об'явлений "Книги"