Создание компилятора языка для .NET Framework - Использование LCG для выполнения динамического связывания

ОГЛАВЛЕНИЕ

Использование LCG для выполнения динамического связывания

Вызов метода на основе Reflection может быть весьма медленным (см. мою статью "Reflection: Dodge Common Performance Pitfalls to Craft Speedy Applications" («Reflection: как избежать обычных провалов в области производительности для создания быстрых приложений» на msdn.microsoft.com/msdnmag/issues/05/07/Reflection). Как связывание метода, так и вызов метода на много порядков медленнее, чем простая инструкция вызова IL. CLR в .NET Framework 2.0 включает функцию, именуемую созданием облегченного кода (Lightweight Code Generation – LCG), которую можно использовать для динамического создания кода на лету с целью связи узла вызова с методом, посредством более быстрой инструкции вызова IL. Это значительно ускоряет вызов методов. Поиск метода на объекте по-прежнему необходим, но после того, как он найден, можно создать мостовой DynamicMethod и кэшировать его для каждого повторяющегося вызова.

Рис. 15 показывает очень простую версию динамического связывателя, выполняющего динамическое создание кода для мостового метода. Сперва он просматривает кэш и проверяет, был ли вызов узла замечен ранее. Если вызов узла выполняется в первый раз, он создает DynamicMethod, возвращающий объект и принимающий массив объектов как аргумент. Аргумент массива объекта содержит объект экземпляра и аргументы, которые будут использованы для финального вызова метода. Код IL создается для распаковки массива объекта на стек, начиная объекта экземпляра и продолжая аргументами. Затем выдается инструкция вызова и результат вызова возвращается вызываемому.

Вызов мостового метода LCG выполняется через делегата, то есть очень быстро. Я просто помещаю аргументы мостового метода в массив объектов и выполняю вызов. Когда это происходит в первый раз, компилятор JIT компилирует динамический метод и исполняет IL, который, в свою очередь, вызывает финальный метод.

По сути этот код является тем, что создал бы статический компилятор с ранней привязкой. Он сперва помещает на стек объект экземпляра, затем аргументы и затем вызывает метод, используя инструкцию вызова. Это хитрый способ задержки данной семантики до последнего момента, что удовлетворяет динамическую семантику вызова, присутствующую в большинстве динамических языков.