.NET 2.0: Activator.CreateInstance(...) против нового: Почему скорость выполнения зависит от порядка?

Когда я создаю объект класса, который реализует интерфейс через Activator.CreateInstance или через new Ключевое слово, скорость выполнения явно отличается. Вид создания, который я использую первым, является более быстрым для остальной части выполнения программы. Я написал следующий "Тест производительности":

static private void SpeedTest(IPlugin plugin)
{
    Console.Write("SpeedTest of \"" + plugin.Name + "\": ");

    Stopwatch sw = new Stopwatch();

    int workingNumber = 0;

    sw.Start();

    for (int i = 0; i < 2147483647; i++)
    {
        plugin.Add(ref workingNumber);
        plugin.DSub(ref workingNumber);
        plugin.Add(ref workingNumber);
    }

    sw.Stop();

    Console.WriteLine(sw.Elapsed.ToString());
}

Я загружаю экземпляры классов, реализующих IPlugin через:

static void Main(string[] args)
{
    IPlugin pluginInstance = null;
    IPlugin localInstance = new LocalPlugin();

    Assembly plugin = Assembly.LoadFile("Path\\Plugin.dll");

    Type pluginInterface = typeof(IPlugin);

    foreach (Type type in plugin.GetTypes())
        if (pluginInterface.IsAssignableFrom(type))
            pluginInstance = (IPlugin)Activator.CreateInstance(type);

    SpeedTest(localInstance);
    SpeedTest(pluginInstance);
    SpeedTest(localInstance);
    SpeedTest(pluginInstance);
}

IPlugin выглядит так:

public interface IPlugin
{
    string Name { get; }

    void Add(ref int number);
    void DSub(ref int number);
}

"Локальный" плагин (и удаленный плагин из Plugin.dll, который является тем же, за исключением того, что он имеет public-модификатор и возвращает "Remote" вместо "Local") выглядят так:

class LocalPlugin : IPlugin
{
    public string Name
    {
        get { return "Local"; }
    }

    public void Add(ref int number)
    {
        number++;
    }

    public void DSub(ref int number)
    {
        number -= 2;
    }
}

Здесь вы можете скачать оба проекта: ZIP-файл с примерами проектов.

Вам нужно скомпилировать PluginHost-Проект до Plugin-Проект.

Теперь программа-выход для этого тест-блока

SpeedTest(pluginInstance);
SpeedTest(localInstance);
SpeedTest(pluginInstance);
SpeedTest(localInstance);

является:

SpeedTest of "Plugin": 00:00:25.9785649
SpeedTest of "Local": 00:00:38.8875138
SpeedTest of "Plugin": 00:00:25.8757588
SpeedTest of "Local": 00:00:38.5222134

Если я переместить первую строку в последнюю позицию:

SpeedTest(localInstance);
SpeedTest(pluginInstance);
SpeedTest(localInstance);
SpeedTest(pluginInstance);

Я получаю следующий вывод:

SpeedTest of "Local": 00:00:26.1881051
SpeedTest of "Plugin": 00:00:38.9942815
SpeedTest of "Local": 00:00:25.9634257
SpeedTest of "Plugin": 00:00:38.6881451

Вывод: первый экземпляр, который я использую, быстрее второго. Это поведение не зависит от метода.

Вопросы:

  1. Почему это так?
  2. Что я могу сделать, чтобы получить одинаковую производительность для каждого вида исполнения?

С уважением,

Матиас

1 ответ

Решение

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

Конкретный интересный текст следующий:

Наша логика диспетчеризации интерфейса имеет важную оптимизацию, где FOR EACH CALL SITE, она явно проверяет одну конкретную цель, и, если она терпит неудачу, выполняет медленный поиск в хеш-таблице, и, если этот поиск завершается неудачей, возвращается к дорогостоящему поиску. Таким образом, для сайтов вызовов, которые имеют тенденцию переходить в одно место назначения, это очень быстро, а для сайтов вызовов, которые имеют много целей, вы получаете хорошую, но не столь высокую производительность.

Оптимизация сайта вызова инициализируется в соответствии с конкретным типом первого появившегося объекта.

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

Другие вопросы по тегам