Можно ли избежать Delegate.DynamicInvoke в этом универсальном коде? - PullRequest
11 голосов
/ 12 июля 2009

Этот вопрос частично относится к делегатам, а частично к генерикам.

С учетом упрощенного кода:

internal sealed class TypeDispatchProcessor
{
    private readonly Dictionary<Type, Delegate> _actionByType 
        = new Dictionary<Type, Delegate>();

    public void RegisterProcedure<T>(Action<T> action)
    {
        _actionByType[typeof(T)] = action;
    }

    public void ProcessItem(object item)
    {
        Delegate action;
        if (_actionByType.TryGetValue(item.GetType(), out action))
        {
            // Can this call to DynamicInvoke be avoided?
            action.DynamicInvoke(item);
        }
    }
}

Я прочитал в другом месте на SO , что прямой вызов делегата (с круглыми скобками) на несколько порядков быстрее, чем вызов DynamicInvoke, что имеет смысл.

Для приведенного выше примера кода мне интересно, могу ли я выполнить проверку типов и как-то улучшить производительность.

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

Один из вариантов - сохранить Action<object> в Dictionary и обернуть делегаты Action<T> другим делегатом. Я еще не сравнивал изменение производительности, на которое может повлиять этот второй косвенный вызов.

Ответы [ 3 ]

23 голосов
/ 12 июля 2009

Я сильно подозреваю, что упаковка вызовов будет намного эффективнее, чем использование DynamicInvoke. Ваш код будет:

internal sealed class TypeDispatchProcessor
{
    private readonly Dictionary<Type, Action<object>> _actionByType 
        = new Dictionary<Type, Action<object>>();

    public void RegisterProcedure<T>(Action<T> action)
    {
        _actionByType[typeof(T)] = item => action((T) item);
    }

    public void ProcessItem(object item)
    {
        Action<object> action;
        if (_actionByType.TryGetValue(item.GetType(), out action))
        {
            action(item);
        }
    }
}

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

7 голосов
/ 13 июля 2009

Так что я сделал несколько измерений на этом.

var delegates = new List<Delegate>();
var actions = new List<Action<object>>();

const int dataCount = 100;
const int loopCount = 10000;

for (int i = 0; i < dataCount; i++)
{
    Action<int> a = d => { };
    delegates.Add(a);
    actions.Add(o => a((int)o));
}

var sw = Stopwatch.StartNew();
for (int i = 0; i < loopCount; i++)
{
    foreach (var action in actions)
        action(i);
}
Console.Out.WriteLine("{0:#,##0} Action<object> calls in {1:#,##0.###} ms",
    loopCount * dataCount, sw.Elapsed.TotalMilliseconds);

sw = Stopwatch.StartNew();
for (int i = 0; i < loopCount; i++)
{
    foreach (var del in delegates)
        del.DynamicInvoke(i);
}
Console.Out.WriteLine("{0:#,##0} DynamicInvoke calls in {1:#,##0.###} ms",
    loopCount * dataCount, sw.Elapsed.TotalMilliseconds);

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

Результаты весьма убедительны!

1,000,000 Action calls in 47.172 ms
1,000,000 Delegate.DynamicInvoke calls in 12,035.943 ms

1,000,000 Action calls in 44.686 ms
1,000,000 Delegate.DynamicInvoke calls in 12,318.846 ms

Таким образом, в этом случае замена на DynamicInvoke дополнительного косвенного вызова и приведение была примерно в 270 раз быстрее . Всего за день работы.

1 голос
/ 19 апреля 2012

Если вам нужно расширить это на перенос вызовов членов из классов без использования Reflection.Emit, вы можете сделать это, создав серию подсказок компилятора, которые могут отображать класс и список параметров функции или тип возвращаемого значения.

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

Вы должны предоставить подсказки для каждого класса и сигнатуры (но не для каждого метода или свойства).

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

В тестировании производительности это далеко не так хорошо, как в примере выше, но оно является общим, что означает, что оно работает в необходимых мне обстоятельствах. Производительность примерно в 4,5 раза при чтении свойства по сравнению с Invoke.

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