2015-10-05 20 views
14

È possibile generare un'identità di un delegato per distinguerlo da altri delegati? Pensate a questo codice:Possiamo ottenere un'identità di un delegato?

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); 

Il problema che voglio risolvere è, voglio mettere in cache una certa JIT compilato il codice GPU in .NET. Per rendere più facile da usare, voglio lasciare all'utente di inviare l'delegato, e se il delegato è lo stesso, cerchiamo di scoprire il codice GPU da una cache, piuttosto che JIT compilare esso ogni volta:

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 possibile soluzione è quella di confrontare la stringa di espressione, ma ha ancora un problema per prendere il Clouser, come ad esempio:

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(); 

come sottolineato grazie @SWeko e @Luaan, nell'esempio precedente, delegate1 e delegate2 sono in realtà lo stesso. Ma lo scopo della cache il delegato è il seguente utilizzo:

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

Forse è possibile utilizzare [Alberi di espressione] (https://msdn.microsoft.com/en-us/library/bb397951.aspx) invece di 'Func'. Non sono sicuro che implementino 'Equals' e' GetHashCode' come richiesto, ma tu puoi scorrere la struttura e puoi creare il tuo HashCode - ma non è così semplice come sembra. - E non sono sicuro che ciò comporterà un vantaggio in termini di prestazioni. –

+0

@Verarind quindi possiamo creare un albero di espressioni da un delegato, ma poi di nuovo la domanda, possiamo semplicemente confrontare l'equità di due oggetti dell'albero dell'espressione? –

+3

I tuoi delegati * non sono * gli stessi. Ispezionare 'delegate1.Method'. Ispeziona 'delegate2.Method'. Sono due funzioni diverse che capita di fare la stessa cosa. Chiedi l'identità di un delegato, ma il tuo codice lo rileva già correttamente. Quello che ti interessa davvero è qualcos'altro. – hvd

risposta

4

One (relativamente ingenuo) approccio sarebbe quello di utilizzare Expression<Func> s invece di Func 's se stessi, in quanto hanno molto più dettagliate informazioni, che permette analizzare le cose Per esempio.

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); 

dove id è banale come:

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

supera i test.


Si noti che questa non è una soluzione completa e ha un sacco di problemi. Se hai bisogno di un confronto completo, devi tenere conto della natura completa dell'espressione, inclusi (ma non limitati) i tipi che entrano ed escono dall'espressione, i metodi call, l'accesso alla chiusura, ecc., Ecc. Non penso che questo sia risolvibile nel caso generale, ma di solito può essere limitato ad un caso più specializzato, che può essere risolto.

+0

Buona idea - ma una conversione di tipo verrà rappresentata in stringa come 'Convert (x)' indipendentemente dal tipo in cui verrà convertita. Ma forse in questo caso speciale sarà sufficiente. –

+0

ah, buona idea, ma confrontare due stringhe potrebbe portare a problemi di prestazioni. –

+0

e se c'è un clouser di alcune variabili, potrebbe non riuscire. Vedi la domanda aggiornata, dove codifico un excpetion di questo test. Uhmm, allora sembra difficile. –

3

È necessario lavorare al livello Expression qui. Il compilatore C# non garantisce che identici lambda finiscano con lo stesso oggetto delegato. Questa ottimizzazione non viene eseguita in questo momento, ma esiste un problema con GitHub. Anche se viene eseguito, funzionerà un assemblaggio alla volta che potrebbe non essere sufficiente per te. Se il delegato acquisisce valori di chiusura, questo non funzionerà mai.

Una volta ho fatto questo per mettere in cache LINQ 2 query compilate SQL automaticamente data una query. Non è banale confrontare gli alberi di espressione. ToString non è completamente fedele ed è lento. Dovrai scrivere la tua classe di confronto. Penso che ci sia un codice per quello sul web come punto di partenza.

Ad esempio, quando si confrontano le espressioni costanti non è possibile eseguire solo ReferenceEquals(val1, val2). In realtà, si tratta di un caso speciale di molti tipi, come i tipi primitivi in ​​scatola. Possono avere lo stesso valore ma sono inscatolati con identità di oggetti differenti.

Sarà inoltre necessario scrivere una funzione di codice hash perché probabilmente si desidera cercare i risultati della cache utilizzando una tabella hash.

Si avranno anche problemi di GC perché gli alberi di espressione possono trattenere oggetti arbitrari. Fondamentalmente, una chiusura può trattenere casualmente le variabili locali in modi imprevisti.Quindi quello che ho fatto è stato quello di disinfettare gli alberi prima di metterli in cache. Ho eliminato tutte le costanti da quelle che non erano sicure.

È possibile generare un'identità di un delegato per distinguerlo da altri delegati?

Sì, ma comporta il confronto manuale e l'hashing dell'albero delle espressioni.

2

Un'opzione qui è quella di utilizzare Dictionary<T1, T2> per contenere l'ID stringa ei nostri delegati (la chiave stringa contiene un'espressione ordinata concatenata con valore da qualche invocazione di test [questo genera un UID di qualche tipo]) e il valore è il nostro delegato Func. L'espressione compila solo la prima volta in cui si mappa la nostra espressione convertita in stringa ID in _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]]; 
     } 
    } 
} 

abbiamo bisogno di questo _delegatesMapping per memorizzare le nostre registrazioni UID nella stringa di formato Espressione -> UID. Ci consente anche di eseguire una ricerca rapida (quasi n(1)) piuttosto che calcolare l'UID ogni volta che ne abbiamo bisogno.

Così l'intera operazione si presenta come:

Espressione -> textExp -> nuovo record nella _delegatesMapping (textExp -> UID) nuovo record nel _delegates[UID]

E quando si accede è tutto il viaggio di ritorno . Prima otteniamo UID che delegato:

Espressione -> textExp -> _delegates [_delegatesMapping [textExp]] (restituisce delegato se esiste nel dizionario).

Esempio di utilizzo:

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); 
    } 
} 

uscita:

true 
false 

P.S. Ma, come Sweko già scritto nella sua risposta:

Si noti che questa non è una soluzione completa, e ha un sacco e un sacco di problemi. Se è necessario un confronto completo, è necessario prendere in considerazione per la natura completa dell'espressione, compresi (ma non anche ) tipi che entrano ed escono dall'espressione, chiamate di metodi, accesso di chiusura, ecc, ecc. ..

+0

Che cosa è 'expStr.OrderBy (ch => ch) .ToArray()'? – usr

+0

@usr bene questa riga 'reimposta' l'ordine per l'espressione, ad esempio '(a, b) => ((a + 7) + 5) + b)' e '(b, a) => (((7 + b) + 5) + a) '(stesso exp con un ordine diverso) saranno entrambi convertiti in identiche espressioni (id) qualcosa come' (((()))) +++, ab57 => ' – Fabjan

+0

Si resetta anche "21" a "12" e tutti i tipi di altre cose ... – usr

Problemi correlati