2015-10-05 2 views
14

Возможно ли генерировать idenitity делегата, чтобы отличить его от другого делегата? Подумайте об этом коде:Можем ли мы получить личность делегата?

Func<int, int, int> delegate1 = a, b => a + b; 
Func<int, int, int> delegate2 = a, b => a + b; 
Func<int, int, int> delegate3 = a, b => a - b; 
let id1 = id(delegate1); 
let id2 = id(delegate2); 
let id3 = id(delegate3); 
Assert(id1 == id2); 
Assert(id1 != id3); 

Проблема, которую я хочу, чтобы решить, я хочу, чтобы кэшировать некоторые JIT скомпилированный код GPU в .NET. Для того, чтобы сделать его легко использовать, я хочу, чтобы пользователь послать делегата, и если делегат же, мы пытаемся выяснить код GPU из кэша, а не JIT скомпилировать его каждый раз:

Parallel(outputA, inputA1, inputA2, a, b => a + b); //should JIT compile GPU code, and cache it by its identity 
Parallel(outputB, inputB1, inputB2, a, b => a + b); //should NOT JIT compile GPU code, and use the cached GPU code by its identity 

One возможным решением является сравнение строку выражения, но она все еще имеет проблемы, чтобы поймать Клоузер, такие как:

int c = 0; 
Expression<Func<int, int, int>> delegate1 = (a, b) => a + b + c; 
c += 1; 
Expression<Func<int, int, int>> delegate2 = (a, b) => a + b + c; 
Expression<Func<int, int, int>> delegate3 = (a, b) => a - b - c; 
Console.WriteLine(delegate1); 
Console.WriteLine(delegate2); 
Console.WriteLine(delegate1.ToString() == delegate2.ToString()); 
Console.ReadKey(); 

Как уже указывалось, благодаря @SWeko и @Luaan, в приведенном выше примере, delegate1 и delegate2 на самом деле тоже самое. Но цель кэширования делегатов в следующем использовании:

int c = 1; 
Parallel(outputA, inputA1, inputA2, (a,b) => a+b); //do JIT compile of GPU code 
c += 1; 
Parallel(outputB, inputB1, inputB2, (a,b) => a+b); //as the delegate is same then the previouse one, it will not do JIT GPU code compiling, but then, that is wrong! 
+0

Возможно, вы можете использовать [Деревья выражений] (https://msdn.microsoft.com/en-us/library/bb397951.aspx) вместо 'Func'. Я не уверен, что они реализуют «Equals» и «GetHashCode» по запросу, но вы можете выполнять итерацию по структуре, и вы можете создать свой собственный HashCode, но это не так просто, как кажется. - И я не уверен, что это приведет к повышению производительности. –

+0

@Verarind, тогда мы можем создать дерево выражений из делегата, но опять же вопрос, можем ли мы просто сравнить equaliaty двух объектов дерева выражений? –

+3

Ваши делегаты * не * одинаковы. Проверьте 'delegate1.Method'. Осмотрите 'delegate2.Method'. Это две разные функции, которые выполняют одно и то же. Вы спрашиваете о личности делегата, но ваш код уже правильно определяет это. То, что вас действительно интересует, - это что-то еще. – hvd

ответ

4

Один (относительно наивный) подход будет использовать Expression<Func> с вместо Func «S себя, так как они имеют гораздо более детальную информацию, которая позволяет вам анализировать материал. Например.

Expression<Func<int, int, int>> delegate1 = (a, b) => a + b; 
Expression<Func<int, int, int>> delegate2 = (a, b) => a + b; 
Expression<Func<int, int, int>> delegate3 = (a, b) => a - b; 
var id1 = id(delegate1); 
var id2 = id(delegate2); 
var id3 = id(delegate3); 
Debug.Assert(id1 == id2); 
Debug.Assert(id1 != id3); 

где id является тривиальным, как:

public static string id(Expression<Func<int, int, int>> expression) 
{ 
    return expression.ToString(); 
} 

проходит испытания.


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

+0

Хорошая идея - но преобразование типа будет представлено в строке как 'Convert (x)' независимо от того типа, который будет преобразован. Но, возможно, в этом специальном случае этого будет достаточно. –

+0

ах, хорошая идея, но сравнить две строки могут привести к проблеме производительности. –

+0

, и если есть кузнец некоторой переменной, это может привести к ошибке. См. Обновленный вопрос, где я кодирую excpetion этого теста. Хм, тогда это выглядит тяжело. –

3

Вам необходимо работать на уровне Expression. Компилятор C# не гарантирует, что идентичные lambdas получат один и тот же объект-делегат. Эта оптимизация не выполняется прямо сейчас, но есть проблема GitHub. Даже если он будет выполнен, он будет работать на одной сборке одновременно, что может быть недостаточно для вас. Если делегат фиксирует значения закрытия, это никогда не будет работать.

Я как-то сделал это, чтобы автоматически скомпилировать запросы SQLQLQ 2 SQL с учетом запроса. Нетрудно сравнить деревья выражений. ToString не является полной верностью, и он медленный. Вам нужно будет написать свой собственный класс сравнения. Я думаю, что есть код для этого в Интернете в качестве отправной точки.

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

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

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

Возможно ли создать идентификатор делегата, чтобы отличить его от другого делегата?

Да, но он включает в себя ручное сравнение и хэширование дерева выражений.

2

Опция здесь заключается в использовании Dictionary<T1, T2> для хранения идентификатора строки и наших делегатов (строка Key содержит отсортированное выражение, объединенное со значением из некоторого вызова вызова [это генерирует UID какого-то типа]), а значение - наш делегат Func. Выражение компилирует только первый раз, когда мы отображаем наше выражение преобразуется в строку с ID в _delegatesMapping:

public class Funker 
{ 
    private static Dictionary<string, string> _delegatesMapping; 
    private static Dictionary<string, Func<int, int, int>> _delegates; 
    public static Funker Instance { get; private set; } 

    static Funker() 
    { 
     _delegatesMapping = new Dictionary<string, string>(); 
     _delegates = new Dictionary<string, Func<int, int, int>>(); 
     Instance = new Funker(); 
    } 

    private Funker() { } 

    public Func<int, int, int> this[Expression<Func<int, int, int>> del] 
    { 
     get 
     { 
      string expStr = del.ToString(); 

      var parameters = del.Parameters; 

      for (int i = 0; i < parameters.Count; i++) 
       expStr = expStr.Replace(parameters[i].Name, String.Concat('_' + i)); 

      Func<int, int, int> _Del = null; 

      if (!_delegatesMapping.ContainsKey(expStr)) 
      { 
       _Del = del.Compile(); 
       _delegatesMapping.Add(expStr, new String(expStr.OrderBy(ch => ch).ToArray()) + "|" + _Del(55, 77)); 
      } 

      if (!_delegates.ContainsKey(_delegatesMapping[expStr])) _delegates.Add(_delegatesMapping[expStr], _Del ?? del.Compile()); 
      return _delegates[_delegatesMapping[expStr]]; 
     } 
    } 
} 

Мы нуждаемся в этом _delegatesMapping хранить наши UID записи в строке формата Expression -> UID. Это также позволяет нам быстро выполнять поиск (почти n(1)), а не вычислять UID каждый раз, когда нам это нужно.

Таким образом, вся операция выглядит следующим образом:

Expression -> textExp -> новая запись в _delegatesMapping (textExp -> UID) новый рекорд в _delegates[UID]

И когда доступ к ней все пути назад , Сначала мы получаем UID, чем делегат:

Выражение -> textExp -> _delegates [_delegatesMapping [textExp]] (возвращает делегат, если он существует в словаре).

Пример использования:

class Program 
{   

    static void Main(string[] args) 
    { 
     var funker = Funker.Instance; 

     var del1 = funker[(a, b) => a + 71 + 12 + b]; 
     var del2 = funker[(hello, world) => 71 + hello + world + 12]; 
     var del3 = funker[(a, c) => a + 17 + 21 + c]; 

     Debug.Assert(del1 == del2); 
     Debug.Assert(del1 == del3); 
    } 
} 

Выход:

true 
false 

P.S. Но SWeko уже писал в своем ответе:

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

+0

Что такое 'expStr.OrderBy (ch => ch) .ToArray()'? – usr

+0

@usr и эта строка 'сбрасывает' порядок выражения, например '(a, b) => (((a + 7) + 5) + b)' и '(b, a) => (((7 + b) + 5) + a) '(тот же самый exp с разным порядком) оба будут преобразованы в идентичное выражение (id) что-то вроде' ((()))) +++, ab57 => ' – Fabjan

+0

Он также сбрасывает «21» до «12» и все другие вещи ... – usr

Смежные вопросы