Поскольку весь смысл этого проекта заключается в изучении, использование инструмента или решения для замены однозначно не отвечает на вопрос.
Во-первых, отказ от ответственности: я не программист x86 - у меня естьпроделал приличный объем работы во встроенных средах и теперь (с мобильными телефонами) чипы ARM.Что касается хороших вещей ...
Есть два основных способа сделать ваш интерпретатор быстрее: заставить его оптимизировать сам код BF и оптимизировать сам интерпретатор.Я рекомендую немного и того, и другого на шаге ниже.
Насколько я знаю, x86 тратит много места на красителях, обеспечивая относительно впечатляющие свойства быстрого разветвления.Вероятно, из-за этого я видел, как несколько компиляторов (включая gcc) создают вложенные ветви в пользу реальных таблиц переходов для x86.Таблицы переходов звучат привлекательно в теории, но на самом деле x86 оптимизирует настолько хорошо для унаследованных методов, что традиционное мышление просто не применимо в большинстве случаев.Вот почему долгосрочные разработчики x86 скажут вам, если вы хотите рассчитать, насколько быстрым является код, тогда вам нужно написать его, запустить и рассчитать время.
Несмотря на скорость, с которой ветки могут происходить на x86, этовсе еще вероятно стоит потратить немного накладных расходов на не ветвление.Так как инструкции BF в любом случае настолько просты, что они могут принять форму «выполняйте большинство инструкций одновременно, так как это быстрее, чем в другой ветви».Часть этого может даже выполняться параллельно процессором, где не может быть ветвления.(x86 имеет достаточно модулей декодирования и выполнения в одном ядре, так что это возможно)
Еще одна вещь, которая может убить вашу производительность, - это проверка ошибок (например, упаковка).Сохранение этого вызывает проблемы с производительностью (сейчас это не так важно), но, что более важно, не позволяет оптимизировать.
Кроме того, ваша программа очень универсальная.Это позволит вам использовать любое максимальное значение для упаковки.Если бы это была степень двойки, вы бы просто выполняли побитовое И (очень быстро, поскольку это практически один цикл ЦП практически на всех современных платформах), эквивалентное переносу вместо сравнения и ветвления.Дьявол кроется в деталях написания действительно быстрых интерпретаторов - каждый добавленный вами кусочек делает его намного более сложным.
С этой целью я рекомендую упростить то, что делает ваш BF-интерпретатор (сделайте так, чтобы он был силениз двух для значений, например).Один только побитовый трюк AND и принудительное использование переноса (как это имеет место в большинстве современных языков программирования для переполнения / недополнения) уже удаляют как минимум две ветви.
После того, как об этом позаботятся, это позволяетИнтересная оптимизация: отбросьте инструкции BF и вместо этого заставьте ваш компьютер выполнять другие инструкции, которые больше подходят для интерпретатора.
Рассмотрим Java: Java не интерпретирует.Это JIT на совершенно другой язык.
Я рекомендую применять ту же логику.Вы уже сделали это немного, передав своим инструкциям значение, связанное с ними.
Я рекомендую создать инструкцию со следующей информацией:
- сумма, добавляемая к значению в указатель данных (или вычитание - вычитание просто добавляет отрицательное число)
- сумма, добавляемая к значению из указатель данных (снова иливычесть)
- указатель на следующую инструкцию для выполнения
- значение, которое сравнивается со значением в указателе данных до , оно изменяется
Затем цикл интерпретатора изменяется на логику следующим образом: добавьте значение данных в инструкции к значению в указателе данных, добавьте значение данных address в инструкции к самому указателю данных, если значениеесли указатель данных совпадает с нашим значением сравнения, установите указатель инструкции на новый указатель инструкции продолжения (до следующего цикла интерпретатора), если значение сравнения является специальнымзначение l (например, вы можете выбрать 0x80000000)обрабатывать ввод / вывод материала
увеличить адрес инструкции на один
Первоначальная интерпретация теперь становится сложнее:
Теперь инструкции могут объединять +, -, <,>, а иногда даже [и обычно] в одну и ту же инструкцию. Первая ветвь в цикле может быть представлена без ветвления, если вы разбираетесь в этом (и даже более эффективно, если какая-то застрявшая сборка или некоторые встроенные функции компилятора). Возможно, вы захотите сообщить компилятору, что вторая ветвь вряд ли ударит (в этом случае узким местом является узкая область ввода / вывода, а не скорость интерпретации, поэтому, даже если вы делаете много ввода / вывода, одну маленькую неоптимизированную ветвь не будет иметь значения).
Необходимо соблюдать осторожность в условиях окончания работы. Последняя инструкция в вашей интерпретируемой BF-программе теперь ВСЕГДА должна указывать на инструкцию NULL, чтобы цикл завершился.
Порядок, в котором происходит интерпретация, важен, потому что - / + выполняется до <> до [выполняется до] перед I / O. Итак,> + - это две интерпретированные инструкции, а +> - одна.
Помимо разработки такого быстрого, зацикленного на себе интерпретатора, как этот, вы изучаете более сложный анализ кода, и в этом случае вы попадаете в конструкцию компилятора и меньше отдаляетесь от простого интерпретатора. Это не то, чем я занимаюсь каждый день, но книга Лоудена "Сборка компилятора" была для меня очень хорошей книгой, но в таком случае она не станет маленьким проектом. Если вы не серьезно относитесь к тому, чтобы сделать эту штуку смехотворно быстрой и, в конце концов, возможно, скомпилировать код, я бы держался подальше от больших и жестких оптимизаций.
Надеюсь, я дал вам идею попробовать и протестировать, которая приведет вас к дополнительным шагам оптимизации. Я сам не пробовал ничего подобного, так что это все еще догадка, основанная на прошлом опыте. Однако, даже если он не будет быстрее, вы приобретете некоторый опыт в переписывании BF-кода для относительно другой архитектуры от ванильного BF-интерпретатора.
P.S. Отлично вопрос!