我们能得到一个委托的标识吗

本文关键字:一个 标识 我们 | 更新日期: 2023-09-27 18:16:05

是否可以生成一个代表的身份来区分它与其他代表?想想这段代码:

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

我想解决的问题是,我想在。net中缓存一些JIT编译的GPU代码。为了使它易于使用,我想让用户发送委托,如果委托是相同的,我们尝试从缓存中找出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

一种可能的解决方案是比较表达式字符串,但它仍然存在捕获集群的问题,例如:

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指出,在上面的例子中,delegate1delegate2实际上是相同的。但是缓存委托的目的是在以下用法中:

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!

我们能得到一个委托的标识吗

一种(相对幼稚的)方法是使用Expression<Func> s而不是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();
}

通过测试。


请注意,这不是一个完整的解决方案,并且有很多很多问题。如果需要进行全面的比较,则需要考虑表达式的全部性质,包括(但不限于)进出表达式的类型、方法调用、闭包访问等等。我不认为这在一般情况下是可以解决的,但通常它可以限制在一些更特殊的情况下,可以解决。

您需要在Expression级别工作。c#编译器不能保证相同的lambdas最终得到相同的委托对象。这个优化现在没有被执行,但有一个GitHub问题。即使它被执行,它也会一次工作一个程序集,这对您来说可能还不够。如果委托捕获闭包值,那么这将永远无法工作。

我曾经这样做过,以缓存LINQ 2 SQL编译查询自动给定的查询。比较表达式树并不简单。ToString不是完全保真的,而且速度很慢。您需要编写自己的比较器类。我认为网络上有相应的代码作为起点。

作为一个例子,当你比较常量表达式时,你不能只做ReferenceEquals(val1, val2)。实际上,您必须对许多类型进行特殊处理,例如盒装基本类型。它们可以具有相同的值,但是用不同的对象标识进行封装。

您还需要编写一个哈希代码函数,因为您可能希望使用哈希表查找缓存结果。

您还会遇到GC问题,因为表达式树可以保留任意对象。基本上,闭包可以以意想不到的方式随机保存局部变量。所以我所做的就是在缓存树之前对它们进行消毒。我删除了所有不安全的常量。

是否有可能生成一个委托的身份来区分它与其他委托?

可以,但是它需要手动比较和散列表达式树。

这里的一个选项是使用Dictionary<T1, T2>来保存字符串ID和我们的委托(字符串Key包含排序表达式与来自某些测试调用的值连接在一起[这会生成某种类型的UID]), value是我们的Func委托。当将转换为字符串的表达式映射到_delegatesMapping中的ID时,表达式才第一次编译:

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格式存储UID记录。它还允许我们执行快速(几乎是n(1))查找,而不是每次需要时都计算UID。

所以整个运算看起来像:

Expression -> textExp -> _delegatesMapping (textExp -> UID)中的新记录_delegates[UID]

当访问它时,它一直返回。首先我们得到UID than delegate:

Expression --> textExp --> _delegate [_delegatesMapping[textExp]](如果字典中存在则返回delegate)

使用示例:

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

注:但是SWeko已经在他的回答中写道:

请注意,这不是一个完整的解决方案,并且有很多很多问题。如果你需要一个全面的比较,你需要考虑表达式的全部性质,包括(但不包括)限制了进出表达式的类型,方法调用,闭包访问等等