【问题标题】:Implementing fast CustomEquality and CustomComparison for discriminated union types为有区别的联合类型实现快速的 CustomEquality 和 CustomComparison
【发布时间】:2012-03-01 14:42:13
【问题描述】:

为了使用键引用一些坐标,我想使用可区分的联合类型,因为它们允许各种有效的模式匹配。

考虑以下代码片段:

[<CustomEquality; CustomComparison>]
type Coord = 
| Spot of AssetKey
| Vol of AssetKey * DateTime option 
| Rate of Currency                                   
.....

    member this.sortKey = 
        match this with
        | Spot(key)                               -> (0 , key.toString)
        | Vol(key)                                -> (1 , key.toString)
        | Vol(key, Some(t))                       -> (2 , key.toString + t.ToShortString())
        | Rate(cur)                               -> (3 , cur.toString)
        ......

    interface IComparable with 
        member this.CompareTo(obj) = 
            match obj with 
            | :? Coord as other -> compare this.sortKey other.sortKey
            | _ -> invalidArg "obj" "not a Coord type"

    override this.Equals(obj) = 
            match obj with 
            | :? Coord as other -> this.sortKey = other.sortKey
            | _ -> false

    override this.GetHashCode() = this.sortKey.GetHashCode()

我需要强制执行特定的排序顺序。例如 Spot

AssetKey 又是一个非常相似的可区分联合类型:

[<StructuralEqualityAttribute; StructuralComparisonAttribute>]
type AssetKey =
| Equity of string
| EquityIndex of string
.....

所以这一切都很好,但它很慢。据我所知,如果调用了 sortKey 函数,则重新构建整个键,特别是再次调用 toString 函数。

一个明显的改进是添加一个缓存层,这更像是一种破解而不是解决方案。

进一步的优化是在字符串上使用哈希键。但是在这里我需要再次添加缓存,因为我需要缓存哈希键并且我不想重新计算它。

如果我使用结构或类,性能优化会更容易,但是我会失去模式匹配的灵活性,例如

match c with 
| Coord.Vol(ak, _) when ak = assetKey -> true
| _ -> false

什么是替代方法,效果很好?在我的某些时间里,sortKey 函数损失了 30% 甚至更多的整体性能。

感谢您的任何建议和改进。

【问题讨论】:

    标签: hash f# pattern-matching equality discriminated-union


    【解决方案1】:

    简单优化
    您可以轻松进行的一项基本优化是避免调用toString,因为您可以仅根据Coord 的类型做出决定。你可以写而不是构建sortKey

    // Separate functions that return tag and key, so that we don't
    // have to call 'toString' if we can decide based just on the Tag
    member this.Tag = 
        match this with 
        | Spot _ -> 0 | Vol(_, None) -> 1 
        | Vol _ -> 2 | Rate _ -> 3
    member this.Key = 
        match this with 
        | Spot(key) | Vol(key, None) -> key.toString | Rate cur -> cur.toString
        | Vol(key, Some t) -> key.toString + t.ToShortString())  
    
    interface IComparable with  
        member this.CompareTo(obj) =  
            match obj with  
            | :? Coord as other -> 
                let c = compare this.Tag other.Tag
                // Try comparing based on the tag first - if the tags 
                // are the same, then get Key and compare based on the key
                if c <> 0 then c else compare this.Key other.Key
            | _ -> invalidArg "obj" "not a Coord type" 
    

    如果您想缓存toString 的结果,那么您需要使用一些允许您存储本地字段的结构。我可能会使用对象类型(表示为类或简单结构)。

    包装类型
    在这种情况下,您仍然可以使用活动模式获得很好的模式匹配,但它需要为每个类定义一个活动模式(这可能不是那么糟糕)。这是一个例子:

    // This type will not be used directly - it is an internal implementation
    // hidden from the users that will be accessed using active patterns
    [<RequiresQualifiedAccess>]
    type AssetKeyInternal =       
      | Equity of string       
      | EquityIndex of string  
      override x.ToString() = ...
    
    // Public type with active patterns for pattern matching
    type AssetKey(key:AssteKeyInternal) = 
      let str = lazy key.ToString() // Lazily cached string
      member x.Key = str.Value      // Evaluated when accessed for the first time
    
      member x.Value = key // Returns the internal representation
    
    // Define active patterns working over AssetKey type
    let (|Equity|EquityIndex|) (k:AssetKey) =
      match k.Value with
      | AssetKeyInternal.Equity(e) -> Equity(e)
      | AssetKeyInternal.EquityIndex(e) -> EquityIndex(e)
    

    给定一个 AssetKey 类型的值,您现在可以编写 k.Key 来获取缓存的字符串表示,并且您可以使用活动模式对其进行模式匹配:

    match k with 
    | Equity k -> ...
    | EquityIndex i -> ...
    

    【讨论】:

      【解决方案2】:

      你可以考虑做类似的事情

      type CoordRepr =
      | Spot of AssetKey 
      | Vol of AssetKey * DateTime option  
      | Rate of Currency              
      
      let sortKey = function
      | Spot(key) -> 1,key.ToString()
      | Vol(key,None) -> 2,key.ToString()
      | Vol(key,Some(v)) -> 2,key.ToString() + v.ToShortDateString()
      | Rate(key) -> 3,key.ToString()
      
      type Coord(repr) =
          let sortKey = sortKey repr
          member __.Repr = repr
          member __.SortKey = sortKey
          override __.Equals(that) =
              match that with
              | :? Coord as c -> sortKey = c.SortKey
              | _ -> false
          override __.GetHashCode() = sortKey.GetHashCode()
          interface System.IComparable with
              member __.CompareTo(that) =
                  match that with
                  | :? Coord as c -> compare sortKey c.SortKey
                  | _ -> failwith "invalidArg"
      
      let Spot k = Coord(Spot k)
      let Vol(k,v) = Coord(Vol(k,v))
      let Rate(k) = Coord(Rate(k))
      
      let (|Spot|Vol|Rate|) (c:Coord) =
          match c.Repr with
          | Spot k -> Spot k
          | Vol(k,v) -> Vol(k,v)
          | Rate k -> Rate k
      

      然后使用签名文件隐藏CoordReprCoord的构造函数、sortKey

      【讨论】:

      • 您好,感谢您的帮助。我实现了第一个版本,它快了大约 100 倍。这是我的实现:
      猜你喜欢
      • 1970-01-01
      • 2021-02-14
      • 1970-01-01
      • 2015-04-26
      • 1970-01-01
      • 2021-04-28
      • 1970-01-01
      • 2020-04-14
      • 1970-01-01
      相关资源
      最近更新 更多