CLR реализация вызовов виртуальных методов для членов интерфейса - PullRequest
31 голосов
/ 21 марта 2012

Из любопытства: как CLR отправляет вызовы виртуальных методов членам интерфейса для правильной реализации?

Я знаю о VTable, который CLR поддерживает для каждого типа со слотами для каждого метода, а также тот факт, что для каждого интерфейса у него есть дополнительный список слотов для методов, которые указывают на соответствующие реализации метода интерфейса. Но я не понимаю следующее: как CLR эффективно определяет, какой список слотов метода интерфейса выбрать из VTable типа?

Статья Детализация внутренних компонентов .NET Framework, чтобы увидеть, как CLR создает объекты среды выполнения из майского выпуска журнала MSDN Magazine за 2005 год рассказывает о таблице сопоставления уровня процесса IVMap, проиндексированной по идентификатору интерфейса. Означает ли это, что все типы в одном и том же процессе имеют одинаковый указатель на один и тот же IVMap?

В нем также говорится, что:

Если MyInterface1 реализован двумя классами, будет два записи в таблице IVMap. Запись будет указывать на начало вложенной таблицы, встроенной в таблицу методов MyClass.

Как CLR узнает, какую запись выбрать? Делает ли он линейный поиск, чтобы найти запись, которая соответствует текущему типу? Или бинарный поиск? Или какая-то прямая индексация и карта с большим количеством пустых записей?

Я также прочитал главу об интерфейсах в CLR через C # 3-е издание, но она не говорит об этом. Поэтому ответы на этот другой вопрос не отвечают на мой вопрос.

Ответы [ 3 ]

22 голосов
/ 21 марта 2012

.NET Stack

Если вы посмотрите на диаграмму, которая была на связанном сайте, это может облегчить понимание.

Означает ли это, что все типы в одном и том же процессе имеют одинаковый указатель на один и тот же IVMap?

Да, поскольку он находится на уровне домена, это означает, что все в этом AppDomain имеет тот же IVMap.

Как CLR узнает, какую запись выбрать? Делает ли он линейный поиск, чтобы найти запись, которая соответствует текущему типу? Или бинарный поиск? Или какая-то прямая индексация и карта с большим количеством пустых записей?

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

Снова с сайта, на который вы ссылались:

Каждая реализация интерфейса будет иметь запись в IVMap. Если MyInterface1 реализован двумя классами, в таблице IVMap будет две записи. Запись будет указывать на начало вложенной таблицы, встроенной в таблицу методов MyClass

Это означает, что каждый раз, когда интерфейс реализован, он имеет уникальную запись в IVMap, которая указывает на MethodSlotTable, который, в свою очередь, указывает на реализацию. Таким образом, он знает, какую реализацию выбрать, основываясь на классе, который его вызывает, так как эта запись IVMap указывает на MethodSlotTable в классе, вызывающем метод. Так что я представляю, что это просто линейный поиск в IVMap, чтобы найти правильный экземпляр, а затем они выключены и работают.


РЕДАКТИРОВАТЬ: Чтобы получить больше информации о IVMap.

Снова по ссылке в ОП:

Первые 4 байта первого входа InterfaceInfo указывают на TypeHandle MyInterface1 (см. Рисунок 9 и рисунок 10). Следующее WORD (2 байта) занято Flags (где 0 наследуется от родителя, а 1 реализуется в текущем классе). WORD сразу после Flags - это Start Slot, который используется загрузчиком классов для размещения вложенной таблицы реализации интерфейса.

Итак, у нас есть таблица, где число - это смещение байтов. Это всего лишь одна запись в IVMap:

+----------------------------------+
| 0 - InterfaceInfo                |
+----------------------------------+
| 4 - Parent                       |
+----------------------------------+
| 5 - Current Class                |
+----------------------------------+
| 6 - Start Slot (2 Bytes)         |
+----------------------------------+

Предположим, в этом AppDomain имеется 100 интерфейсных записей, и нам нужно найти реализацию для каждой из них. Мы просто сравниваем 5-й байт, чтобы увидеть, соответствует ли он нашему текущему классу, и если это так, мы переходим к коду в 6-м байте. Поскольку каждая запись имеет длину 8 байт, нам нужно сделать что-то вроде этого: (Psuedocode)

findclass :
   if (!position == class) 
      findclass adjust offset by 8 and try again

Хотя это все еще линейный поиск, на самом деле это не займет так много времени, пока размер итерируемых данных невелик. Надеюсь, это поможет.


РЕДАКТИРОВАТЬ2:

Итак, взглянув на диаграмму и спросив, почему в IVMap нет слота 1 для класса на диаграмме, я перечитал раздел и обнаружил:

IVMap создается на основе информации карты интерфейса, встроенной в таблицу методов. Карта интерфейса создается на основе метаданных класса в процессе компоновки MethodTable. После завершения загрузки типов в диспетчеризации методов используется только IVMap.

Таким образом, IVMap для класса загружается только с интерфейсами, которые наследует определенный класс. Похоже, что он копирует из домена IVMap, но сохраняет только те интерфейсы, на которые указывают. Это поднимает другой вопрос, как? Скорее всего, это эквивалентно тому, как C ++ делает vtables, где каждая запись имеет смещение, а карта интерфейса предоставляет список смещений для включения в IVMap.

Если мы посмотрим на IVMap, который может быть для всего этого домена:

+-------------------------+
| Slot 1 - YourInterface  |
+-------------------------+
| Slot 2 - MyInterface    |
+-------------------------+
| Slot 3 - MyInterface2   |
+-------------------------+
| Slot 4 - YourInterface2 |
+-------------------------+

Предположим, что в этом домене есть только 4 реализации Карты интерфейса.Каждый слот будет иметь смещение (аналогично записи IVMap, которую я разместил ранее), и IVMap для этого класса будет использовать эти смещения для доступа к записи в IVMap.

Предположим, что каждый слот имеет 8 байтов с началом слота 1в 0, поэтому, если мы хотим получить слот 2 и 3, мы сделаем что-то вроде этого:

mov ecx,edi
mov eax, dword ptr [ecx]
mov eax, dword ptr [ecx+08h] ; slot 2
; do stuff with slot 2
mov eax, dword ptr [ecx+10h] ; slot 3
; do stuff with slot 3

Пожалуйста, извините мой x86, так как я не знаком с ним, но я попытался скопировать то, что у них есть встатья, которая была связана с.

18 голосов
/ 30 октября 2016

Этой статье уже более 10 лет, и с тех пор много изменилось.

IVMaps теперь заменены Virtual Stub Dispatch .

Виртуальная диспетчеризация заглушек (VSD) - это метод использования заглушек для вызовов виртуальных методов вместо традиционной таблицы виртуальных методов.В прошлом для диспетчеризации интерфейса требовалось, чтобы интерфейсы имели уникальные идентификаторы процесса и чтобы каждый загруженный интерфейс был добавлен в карту виртуальных таблиц глобального интерфейса.

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

Нет смысла дублировать статью здесь, но я просто укажу основные моменты и их значение:

  • КогдаJIT видит вызов к элементу интерфейса, он компилирует его в заглушку lookup .Этот фрагмент кода вызовет универсальный преобразователь .
  • универсальный преобразователь - это функция, которая определяет, какой метод вызывать.Это самый общий и, следовательно, самый медленный способ вызова такого метода.При первом вызове из заглушки lookup он исправит эту заглушку (переписать ее код во время выполнения) в диспетчерскую заглушку .Он также генерирует заглушку resol для дальнейшего использования.На этом этапе справочная заглушка исчезает.
  • A диспетчерская заглушка - это самый быстрый способ вызвать элемент интерфейса, но есть одна загвоздка: она настроена оптимистичноcall monomorphic , что означает, что он оптимизирован для случая, когда интерфейсный вызов всегда разрешается в один и тот же конкретный тип.Он сравнивает таблицу методов (т. Е. Конкретный тип) объекта с ранее замеченной (которая жестко закодирована в заглушку) и вызывает кэшированный метод (адрес которого также жестко задан), если сравнение завершается успешно.Если происходит сбой, он возвращается к заглушке разрешения .
  • Заглушка разрешения обрабатывает полиморфные вызовы (общий случай).Он использует кеш, чтобы найти метод для вызова.Если метод отсутствует в кеше, он вызывает универсальный распознаватель (который также записывает в этот кэш).

А вот важное соображение, прямо из статьи:

Когда заглушка отправки не удается достаточно часто, сайт вызова считается полиморфным, и заглушка разрешения будет исправлять сайт вызова, чтобы он указывал прямо на заглушку разрешения, чтобы избежать накладных расходов при постоянно сбойной отправкезаглушки.В точках синхронизации (в настоящее время это конец GC), полиморфные сайты будут случайным образом возвращаться к мономорфным сайтам вызовов при условии, что полиморфный атрибут сайта вызова обычно является временным.Если это предположение неверно для какого-либо конкретного сайта вызовов, он быстро запустит повторный патч, чтобы снова превратить его в полиморфный.

Время выполнения действительно оптимистично в отношении мономорфных сайтов вызовов, чтоимеет большой смысл в реальном коде, и он будет стараться избегать разрешения заглушек в максимально возможной степени.

0 голосов
/ 21 марта 2012

Из первой статьи, которую вы связали:

Если MyInterface1 реализован двумя классами, будет два записи в таблице IVMap. Запись будет указывать на начало вложенной таблицы, встроенной в таблицу методов MyClass, как показано на Рисунок 9

и

ClassLoader просматривает метаданные текущего класса, родительский класс, и интерфейсы, и создает таблицу методов. в процесс компоновки, он заменяет любые переопределенные виртуальные методы, заменяет любые методы родительского класса, скрытые, создают новые слоты и дублирует слоты по мере необходимости. Дублирование слотов необходимо создать иллюзию, что у каждого интерфейса есть своя мини-таблица. Однако дублированные слоты указывают на один и тот же физический осуществление.

Это наводит меня на мысль, что в IVMap интерфейса есть записи, обозначенные именем класса (или некоторым эквивалентом), указывающие на подраздел vtable класса, который по существу имеет дублированные реализации каждого из методов класса, реализующих этот интерфейс, при поддержке указатели на ту же физическую реализацию, что и собственные записи класса vtable.

Может быть, совершенно не так.

...