我们能得到一个委托的标识吗
本文关键字:一个 标识 我们 | 更新日期: 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指出,在上面的例子中,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!
一种(相对幼稚的)方法是使用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已经在他的回答中写道:
请注意,这不是一个完整的解决方案,并且有很多很多问题。如果你需要一个全面的比较,你需要考虑表达式的全部性质,包括(但不包括)限制了进出表达式的类型,方法调用,闭包访问等等