是否可以生成委托的标识以区别于其他委托?想想这段代码:
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
一种可能的解决方案是比较表达式字符串,但仍然有问题赶上clouser,例如:
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 :(得分:4)
一种(相对天真的)方法是使用Expression<Func>
而不是Func
自己,因为它们有更详细的信息,允许您分析内容。 E.g。
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();
}
通过测试。
请注意,这不是一个完整的解决方案,并且存在很多问题。如果需要进行全面比较,则需要考虑表达式的完整性,包括(但不限于)进出表达式,方法调用,闭包访问等等的类型。 在一般情况下,我认为这根本不可解决,但通常可以限制在一些更专业的情况下,可以解决。
答案 1 :(得分:3)
您需要在Expression
级别工作。 C#编译器不保证相同的lambda最终具有相同的委托对象。此优化目前尚未执行,但存在GitHub问题。即使它被执行,它也会一次完成一个装配,这对你来说可能是不够的。如果委托捕获了闭包值,那么这将无法工作。
我曾经这样做是为了在给定查询的情况下自动缓存LINQ 2 SQL编译查询。比较表达式树并非易事。 ToString
并非完全保真,而且速度很慢。您需要编写自己的比较器类。我认为网上有代码作为起点。
例如,当您比较常量表达式时,您不能只执行ReferenceEquals(val1, val2)
。实际上,您必须使用多种类型的特殊情况,例如盒装基元类型。它们可以具有相同的值,但装有不同的对象标识。
您还需要编写哈希码函数,因为您可能希望使用哈希表来查找缓存结果。
您还会遇到GC问题,因为表达式树可以保留在任意对象上。基本上,闭包可以以意想不到的方式随机保留局部变量。所以我所做的就是在缓存树木之前对它们进行消毒。我删除了所有不安全的常量。
是否可以生成委托的身份以区别于其他委托?
是的,但它涉及手动比较和散列表达式树。
答案 2 :(得分:2)
这里的一个选项是使用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记录 - &gt; UID。它还允许我们执行快速(几乎n(1)
)查找,而不是在每次需要时计算UID。
所以整个操作看起来像:
表达式 - &gt; textExp - &gt; _delegatesMapping中的新记录(textExp - &gt; UID)
_delegates[UID]
当它一直回到它的时候。首先我们获得UID而不是委托:
表达式 - &gt; textExp - &gt; _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在他的回答中写道的那样:
请注意,这不是一个完整的解决方案,并且有很多很多 的问题。如果您需要全面比较,您需要采取 考虑到表达的全部性质,包括(但不是 进入和退出表达式,方法调用, 关闭访问等等。