>>> Ну и скорость - смотря как сравнивать, например байткод Эльбрусы выполняют/интерпретируют
>>> медленнее (и будут медленнее как минимум до релиза 7-й версии архитектуры).
>> Мне казалось, что Эльбрус наоборот должен выигрывать, поскольку в ВМ предсказатель переходов
>> не очень работает. Но зависит от байткода и ВМ, конечно.
> ВМ - более абстрактная вещь, но если говорить о байткоде, то предсказание
> переходов работает не плохо.
> Очень хорошо предсказываются все служебные переходы, leaf-циклы и более-менее ветвления
> в них.Похоже, я что-то недопонимаю. Допустим, у абстрактного процессора есть 256 команд. Их исполняет switch в цикле. Какие-то опкоды можно сгруппировать, в итоге получаем 32 case. При компиляции такой «ВМ» окажется, что switch представляет из себя косвенный переход по таблице адресов в памяти. При таком количестве предсказание на основе истории переходов работает не очень, вот что пишет Интел:
B.8.3.2
Virtual Tables and Indirect Calls
17. Virtual Table Usage: BR_IND_CALL_EXEC / INST_RETIRED.ANY
A high value for the ratio Virtual Table Usage indicates that the code includes many indirect calls. The
destination address of an indirect call is hard to predict.
и вот что рекомендует, что бы упростить предсказание:
Assembly/Compiler Coding Rule 14. (M impact, L generality) When indirect branches are
present, try to put the most likely target of an indirect branch immediately following the indirect
branch. Alternatively, if indirect branches are common but they cannot be predicted by branch
prediction hardware, then follow the indirect branch with a UD2 instruction, which will stop the
processor from decoding down the fall-through path.
Для тестов реализовал интерпретатор байт-кода OCaml на асме AMD64, при этом вместо косвенного перехода по таблице вычислял адрес обработчика умножением на степень двойки (на старых поколениях архитекруры это было бы быстрее, поскольку исключается чтение адреса из памяти).
Вот такие печальные результаты предсказателя из perf stat
12 440 035 801 instructions:u # 0,48 insn per cycle
1 039 998 618 branch-misses:u # 33,33% of all branches
Что бы улучшить ситуацию, пришлось добавить условный переход, который никогда не исполняется
execute_instruction:
mov eax, [opcode]
shl rax, instruction_size_log2
+; Переход никогда не происходит. Предотвращает предсказания следующего
+; косвенного перехода, во многих случаях некорректные.
+ jc .stop
lea rax, [rax + vm_base + vm_base_lbl - execute_instruction]
next_opcode
jmp rax
- ud2
+.stop: ud2
Здесь промахов нет, но обратите внимание - всего одна инструкция за такт, что явно меньше возможного.
11 600 026 289 instructions:u # 1,21 insn per cycle
80 002 443 branch-misses:u # 3,51% of all branches
Последние результаты блики к оригинальному интерпретатору, написанному на Си. В той реализации файл с байткодом считывается в память и каждый опкод заменяется адресом обработчика, в последствии исполняется расширением GCC goto *ptr.
Если же опкодов мало и их обработчики объёмные, тогда предсказатель должен повести себя лучше, но много ли таких ВМ?
> Но основное замедление не из-за отсутствия предсказателя, а из-за отсутствия спекулятивного
> выполнения и низкой заполняемости ШК (широких команд).
> Переименование регистров и спекулятивное выполнение позволяет out-of-order ЦПУ начать
> выполнять следующий и даже через-один элемент байт-кода. Тогда как на VLIW
> каждый элемент байт-кода в большинстве случаев требует выполнения нескольких крайне рыхлых
> ШК.
По-моему, в примере выше процессор не знает, что ему можно исполнить.