MulticastDelegate.CombineImpl неэффективен? - PullRequest
1 голос
/ 24 октября 2010

Я только что запустил Reflector и заглянул в MultiCastDelegate.CombineImpl и увидел некоторый очень длинный код ... каждый раз, когда два делегата объединяются (читай: каждый раз, когда вы присоединяете более одного обработчика события к событию), следующий код получаетзапустить, что кажется неэффективным для такой фундаментальной производительности критической функции.Кто-нибудь знает, почему он был написан таким образом?

[SecuritySafeCritical]
protected sealed override Delegate CombineImpl(Delegate follow)
{
    object[] objArray;
    int num2;
    if (follow == null)
    {
        return this;
    }
    if (!Delegate.InternalEqualTypes(this, follow))
    {
        throw new ArgumentException(Environment.GetResourceString("Arg_DlgtTypeMis"));
    }
    MulticastDelegate o = (MulticastDelegate) follow;
    int num = 1;
    object[] objArray2 = o._invocationList as object[];
    if (objArray2 != null)
    {
        num = (int) o._invocationCount;
    }
    object[] objArray3 = this._invocationList as object[];
    if (objArray3 == null)
    {
        num2 = 1 + num;
        objArray = new object[num2];
        objArray[0] = this;
        if (objArray2 == null)
        {
            objArray[1] = o;
        }
        else
        {
            for (int i = 0; i < num; i++)
            {
                objArray[1 + i] = objArray2[i];
            }
        }
        return this.NewMulticastDelegate(objArray, num2);
    }
    int index = (int) this._invocationCount;
    num2 = index + num;
    objArray = null;
    if (num2 <= objArray3.Length)
    {
        objArray = objArray3;
        if (objArray2 == null)
        {
            if (!this.TrySetSlot(objArray, index, o))
            {
                objArray = null;
            }
        }
        else
        {
            for (int j = 0; j < num; j++)
            {
                if (!this.TrySetSlot(objArray, index + j, objArray2[j]))
                {
                    objArray = null;
                    break;
                }
            }
        }
    }
    if (objArray == null)
    {
        int length = objArray3.Length;
        while (length < num2)
        {
            length *= 2;
        }
        objArray = new object[length];
        for (int k = 0; k < index; k++)
        {
            objArray[k] = objArray3[k];
        }
        if (objArray2 == null)
        {
            objArray[index] = o;
        }
        else
        {
            for (int m = 0; m < num; m++)
            {
                objArray[index + m] = objArray2[m];
            }
        }
    }
    return this.NewMulticastDelegate(objArray, num2, true);
}

Разве недостаточно простого шаблона связанного списка?


РЕДАКТИРОВАТЬ: Мой оригинальный вопрос предполагает, что реализациянеэффективен.Ханс и Джон (оба уважаемые авторы здесь, в SO) с замечательным упорством указали на несколько фактов о пригодности описанной выше реализации.

Ганс отмечает, что использование массивов с TrySetSlot в конечном итоге приводит к кешу.дружественный к многоядерным процессорам (таким образом улучшающий производительность ), и Джон любезно собрал микробенчмарк, который показывает, что вышеупомянутая реализация дает очень приемлемые характеристики производительности.

Ответы [ 2 ]

2 голосов
/ 24 октября 2010

Это полная противоположность, этот код был оптимизирован , поскольку не использовал List <> или подобный объект коллекции. Все, что делает List, встроено здесь. Дополнительным преимуществом является то, что блокировка дешевая (TrySetSlot использует Interlocked.CompareExchange) и позволяет сэкономить на перемещении объекта блокировки. Явное встраивание подобного кода вместо того, чтобы оставлять его на усмотрение JIT-компилятора, не так часто встречается в .NET Framework. Но исключения сделаны для таких низкоуровневых примитивов.

1 голос
/ 24 октября 2010

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

Как часто, по вашему мнению, делегаты присоединяются к событиям (или объединяются в другое время)?

Например, в приложении Windows Forms это может происходить довольно редко - в основном при настройке формы, по большей части ... в этот момент происходят гораздо более тяжелые вещи, чем в MulticastDelegate.CombineImpl.

Что делает очень часто, так это то, что делегаты вызываются ... например, для каждого элемента в каждой проекции или предикате (и т. Д.) В запросе LINQ. Это действительно важный бит производительности, IMO.

Я также не уверен, что этот код настолько неэффективен, как вы думаете.Он использует тот же подход, что и ArrayList, в плане создания массива большего размера, чем необходимо, чтобы заполнить его по мере необходимости.Будет ли связанный список более эффективным?Возможно, в некоторых терминах - но в равной степени это будет менее эффективным с точки зрения локальности и уровней косвенности.(Поскольку каждый узел должен быть новым объектом, который сам содержит ссылку на делегат, перемещение по списку может привести к тому, что в память будет добавлено больше страниц, чем массив ссылок.)

РЕДАКТИРОВАТЬ:быстрый микробенчмарк (со всеми обычными предостережениями) вот код для выполнения заданного количества итераций объединения заданного числа делегатов:

using System;
using System.Diagnostics;

class Test
{
    const int Iterations = 10000000;
    const int Combinations = 3;

    static void Main()
    {
        // Make sure all paths are JITted
        Stopwatch sw = Stopwatch.StartNew();
        sw.Stop();
        Action tmp = null;
        for (int j = 0; j < Combinations; j++)
        {
            tmp += Foo;
        }

        sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            Action action = null;
            for (int j = 0; j < Combinations; j++)
            {
                action += Foo;
            }
        }
        sw.Stop();
        Console.WriteLine(sw.ElapsedMilliseconds);
    }

    static void Foo()
    {
    }
}

Некоторые результаты на моем компьютере, все с 10 000 000 итераций:

5 делегатов: около 5,8 секунд
4 делегата: около 4,3 секунд
3 делегата: около 3,2 секунд
2 делегата: около 1,4 секунд
1 делегат: около 160 мс

(Все тесты выполняются несколько раз; приведенные выше примеры являются просто примерами, которые казались достаточно репрезентативными. Я не взял среднее значение или что-то в этом роде.)

Учитывая вышеприведенные результаты, я подозреваю, что любые пути, даже в комбинации, тяжелыеWPF, к которому присоединяется только один делегат , будет невероятно быстрым.Они значительно замедляются, начиная с 1-2, а затем постепенно ухудшаются (но с гораздо меньшей пропорциональной разницей, чем случай 1-2).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...