【问题标题】:Can we get an identity of a delegate?我们可以得到一个代表的身份吗?
【发布时间】:2015-10-05 07:55:23
【问题描述】:

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

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 指出,在上面的示例中,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 trees 而不是Func。我不确定他们是否按照要求实现了EqualsGetHashCode,但是您可以遍历该结构并且可以创建自己的HashCode——但这并不像听起来那么简单。 - 我不确定这是否会带来性能优势。
  • @Verarind 然后我们可以从委托创建表达式树,但问题又来了,我们可以简单地比较两个表达式树对象的相等性吗?
  • 您的代表相同。检查delegate1.Method。检查delegate2.Method。它们是两个不同的功能,碰巧做同样的事情。您询问代表的身份,但您的代码已经正确检测到这一点。你真正感兴趣的是别的东西。
  • @XiangZhang 正如我之前所说:不确定。也许Equals 被实现来处理具有相同内容的不同实例,但我想不是。在这种情况下,您必须自己实现它。
  • 实际上,在第二个示例中,两个代表都将使用更新后的值c

标签: c# .net delegates


【解决方案1】:

一种(相对幼稚的)方法是使用Expression&lt;Func&gt;s 而不是Func 本身,因为它们有更详细的信息,可以让您分析内容。例如

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

通过测试。


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

【讨论】:

  • 好主意 - 但是类型转换将在字符串中表示为 Convert(x) ,无论它将转换为哪种类型。但也许在这种特殊情况下就足够了。
  • 啊,好主意,但比较两个字符串可能会导致性能问题。
  • 如果有某个变量的clouser,那么它可能会失败。请参阅更新后的问题,我在其中编写了该测试的代码。嗯,那看起来很难。
  • 您可以使用序号搜索进行 .startswith 至少可以减少字符串比较的性能损失(对于字符串检查性能问题为我创造了奇迹)
  • @SWeko:请注意这种识别表达式的方式。 Func[int,int] "x =&gt; 2*x" 将被检测为与 Func[int,int] "z =&gt; 2*z" 不同,更重要的是 Func[double,double] "x =&gt; 2*x" 将被检测为匹配项。如果您只使用一种类型的委托,没关系。但是,如果您打算扩大类型,则此类 ID 函数必须至少另外添加有关所有参数类型的信息,并且 100% 安全,以及所涉及的闭包类型......并且在某些情况下,您甚至可能需要有关所涉及程序集的信息..
【解决方案2】:

您需要在Expression 级别工作。 C# 编译器不保证相同的 lambda 以相同的委托对象结束。现在没有执行此优化,但存在 GitHub 问题。即使它被执行,它也会一次工作一个组件,这对你来说可能还不够。如果委托捕获闭包值,那么这将永远无法工作。

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

例如,当您比较常量表达式时,您不能只使用ReferenceEquals(val1, val2)。实际上,您必须对许多类型进行特殊处理,例如盒装原始类型。它们可以具有相同的值,但用不同的对象标识装箱。

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

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

是否可以生成一个代表的身份以将其与其他代表区分开来?

是的,但它涉及手动比较和散列表达式树。

【讨论】:

    【解决方案3】:

    这里的一个选项是使用Dictionary&lt;T1, T2&gt; 来保存字符串 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 而不是委托:

    Expression --> textExp --> _delegates[_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 在他的回答中所写的那样:

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

    【讨论】:

    • 什么是expStr.OrderBy(ch =&gt; ch).ToArray()
    • @usr 好吧,这一行“重置”了表达式的顺序,例如 (a, b) =&gt; (((a + 7) + 5) + b)(b, a) =&gt; (((7 + b) + 5) + a)(相同的 exp 不同的顺序)都将转换为相同的表达式(id),例如 @987654332 @
    • @usr 你是对的,更新了我的答案以涵盖这种情况
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-31
    • 2020-08-06
    • 1970-01-01
    • 1970-01-01
    • 2012-09-08
    • 2012-08-01
    • 2020-04-05
    相关资源
    最近更新 更多