Вы тестируете влияние выравнивания памяти на эффективность кода. 32-битному JIT-компилятору не удается создать эффективный код для типов значений, размер которых превышает 32 бита, длинный и двойной в коде C #. Корень проблемы - 32-битный распределитель кучи GC, он обещает выравнивание выделенной памяти только по адресам, кратным 4. Это проблема, вы увеличиваете удвоения. Двойной код эффективен только тогда, когда он выровнен по адресу, кратному 8. Такая же проблема со стеком, в случае локальных переменных он также выравнивается только до 4 на 32-разрядном компьютере.
Кэш ЦП L1 внутренне организован в блоки, называемые «линия кеша». Существует штраф, когда программа читает неправильно выровненный дубль. Особенно тот, который охватывает конец строки кэша, байты из двух строк кэша должны быть прочитаны и склеены вместе. Неправильное выравнивание не является редкостью в 32-битном джиттере, это всего лишь 50-50 шансов, что поле 'x' будет выделено по адресу, кратному 8. Если это не так, то 'x' и 'y' будет неправильно выровнен, и один из них вполне может занять строчку кэша. То, как вы написали тест, будет замедлять VirtualMethod или SealedMethod. Убедитесь, что вы позволили им использовать одно и то же поле для получения сопоставимых результатов.
То же самое относится и к коду. Поменяйте местами код для виртуального и запечатанного теста, чтобы произвольно изменить результат. У меня не было проблем с тем, чтобы сделать запечатанный тест немного быстрее. Учитывая скромную разницу в скорости, вы, вероятно, смотрите на проблему с выравниванием кода. Джиттер x64 пытается вставить NOP, чтобы выровнять цель ветвления, а джиттер x86 - нет.
Вам также следует запускать проверку синхронизации несколько раз в цикле, по крайней мере, 20. Затем вы, вероятно, также заметите, как сборщик мусора перемещает объект класса. У двойника может быть другое выравнивание впоследствии, резко изменяя выбор времени. Доступ к 64-битному значению типа значения, например long или double, имеет 3 различных момента времени, выровненных по 8, выровненных по 4 в пределах строки кэша и выровненных по 4 по двум строкам кэша. В быстром или медленном порядке.
Штраф круче, чтение двойного, который пересекает строку кэша, примерно в в три раз медленнее, чем чтение выровненного. Кроме того, основная причина, почему double [] (массив значений типа double) выделяется в куче больших объектов, даже если он содержит только 1000 элементов, к югу от нормального порога в 80 КБ, у LOH есть гарантия выравнивания 8. Эти проблемы выравнивания полностью исчезают в коде, генерируемом джиттером x64, и стек, и куча GC имеют выравнивание 8.