2015-01-25 15 views
12

Tôi đang tự hỏi, trong đó kiểm tra các trường hợp bình đẳng trong F # nguyên nhân đấm bốc, và cho dù có những trường hợp trong đó trọng EqualsGetHashCode và thực hiện IEquatable<> là thích hợp hơn để sử dụng StructuralEqualityAttribute. Nếu vậy, nó có thể được thực hiện mà không làm giảm hiệu suất của nhà điều hành =?IEquatable trong F #, = hiệu suất điều hành và bình đẳng về cơ cấu

Đối với các cấu trúc đơn giản giữ một số nguyên, tôi chạy một vòng lặp lặp lại kiểm tra bình đẳng tương tự 1M lần. Tôi hẹn giờ vòng lặp sử dụng ...

  • = với tùy chỉnh (loại và kiểm tra giá trị) bình đẳng: khoảng 110ms
  • = với sự bình đẳng về cơ cấu: 20ms đến 25ms
  • Một tùy chỉnh == điều hành mà chuyển hướng đến IEquatable: 1ms để 3ms
  • Một tùy chỉnh == điều hành so sánh các giá trị trực tiếp: 0ms (xóa bởi ưu)

Từ những gì tôi under tand, giao diện IEquatable<> có thể được sử dụng như một tối ưu hóa hiệu suất để ngăn chặn quyền anh khi kiểm tra sự bình đẳng. Điều này có vẻ là phổ biến trong C#, nhưng tôi khó có thể tìm thấy đề cập đến nó trong F #. Ngoài ra, trình biên dịch F # phàn nàn khi cố gắng ghi đè toán tử = cho một loại đã cho.

Thuộc tính StructuralEqualitydocumented in the MSDN chỉ ghi đè EqualsGetHashCode. Tuy nhiên, nó ngăn chặn việc triển khai rõ ràng IEquatable<>. Tuy nhiên, loại kết quả không tương thích với IEquatable<MyType>. Điều này có vẻ không hợp lý với tôi, nên một loại cấu trúc tương đương không thực hiện IEquatable<>?

Có một lưu ý về việc thực hiện của = trong F # đặc điểm kỹ thuật (8.15.6.2 trong spec 3.0), nhưng tôi không biết phải làm gì với nó:

Lưu ý: Trong thực tế, nhanh (nhưng ngữ nghĩa tương đương) mã được phát ra cho các cuộc gọi trực tiếp đến (=), so sánh, và băm cho tất cả các loại cơ sở, và các đường dẫn nhanh hơn được sử dụng để so sánh nhất mảng

định nghĩa về "các loại cơ sở" được đưa ra trước đây không có vẻ hữu ích để đọc ghi chú này. Điều này có đề cập đến các loại cơ bản không?

Tôi đang bối rối. Chuyện gì vậy? Việc thực hiện bình đẳng thích hợp sẽ như thế nào, nếu loại có thể được sử dụng như một khóa tập hợp hoặc trong một thử nghiệm bình đẳng thường xuyên?

Trả lời

8

Dưới đây là những gì tôi đã thu thập được dựa trên kinh nghiệm hạn chế của tôi:

Tuy nhiên, loại kết quả là không phù hợp với IEquatable<MyType>.

Điều này không chính xác, loại kết quả thực hiện IEquatable<MyType>. Bạn có thể xác minh trong ILDasm.Ví dụ:

[<StructuralEquality;StructuralComparison>]  
type SomeType = { 
    Value : int 
} 

let someTypeAsIEquatable = { Value = 3 } :> System.IEquatable<SomeType> 
someTypeAsIEquatable.Equals({Value = 3}) |> ignore // calls Equals(SomeType) directly 

Có lẽ bạn đang nhầm lẫn bởi cách F # không làm upcasts ngầm như C#, vì vậy nếu bạn đã chỉ làm:

{ Value = 3 }.Equals({Value = 4}) 

Điều này sẽ thực sự gọi Equals (obj) thay vì của thành viên giao diện, trái với kỳ vọng đến từ C#.

Tôi đang tự hỏi, trong đó kiểm tra các trường hợp bình đẳng trong F # nguyên nhân đấm bốc

Một trường hợp phổ biến và gây nhiều tranh cãi là đối với bất kỳ cấu trúc quy định tại ví dụ C# và triển khai IEquatable<T>, ví dụ:

public struct Vector2f : IEquatable<Vector2f> 

hoặc tương tự, bất kỳ cấu trúc quy định tại F # với một thực hiện tùy chỉnh của IEquatable<T>, ví dụ:

[<Struct;CustomEquality;NoComparison>] 
type MyVal = 
    val X : int 
    new(x) = { X = x } 
    override this.Equals(yobj) = 
     match yobj with 
     | :? MyVal as y -> y.X = this.X 
     | _ -> false 
    interface System.IEquatable<MyVal> with 
     member this.Equals(other) = 
      other.X = this.X 

So sánh hai trường hợp của struct này với các nhà điều hành = thực sự gọi Equals(obj) thay vì Equals(MyVal), gây ra hiện tượng boxing xảy ra trên cả hai giá trị được so sánh (và sau đó truyền và hủy hộp). Lưu ý: Tôi đã báo cáo điều này là bug on the Visualfsharp Github và dường như trường hợp này sẽ được khắc phục sớm hơn sau này.

Và nếu bạn cho rằng việc truyền tới IEquatable<T> sẽ giúp ích một cách rõ ràng, tốt thôi, nhưng đó là một hoạt động quyền anh. Nhưng ít nhất bạn có thể tiết kiệm cho mình một trong hai hộp này theo cách này.

Tôi đang bối rối. Chuyện gì vậy? Điều gì sẽ là một sự bình đẳng thích hợp thực hiện giống như, nếu loại có thể được sử dụng như một bộ sưu tập quan trọng hoặc trong một thử nghiệm bình đẳng thường xuyên?

Tôi bối rối như bạn vậy. F # dường như rất GC-hạnh phúc (nevermind nó GCs Tuples và không có hỗ trợ cho các bản ghi struct hoặc DUs). Ngay cả hành vi mặc định:

[<Struct>] 
type MyVal = 
    val X : int 
    new(x) = { X = x } 

for i in 0 .. 1000000 do 
     (MyVal(i) = MyVal(i + 1)) |> ignore;; 
Réel : 00:00:00.008, Processeur : 00:00:00.015, GC gén0: 4, gén1: 1, gén2: 0 

Vẫn gây áp lực đấm bốc và áp lực GC quá mức! Xem bên dưới để biết cách giải quyết.

Điều gì xảy ra nếu loại phải được sử dụng làm khóa trong ví dụ: một cuốn từ điển? Vâng, nếu nó là System.Collections.Generics.Dictionary bạn tốt, mà không sử dụng toán tử F # bình đẳng. Nhưng bất kỳ bộ sưu tập nào được định nghĩa trong F # có sử dụng toán tử này sẽ gặp vấn đề về quyền anh.

Tôi đang tự hỏi (...) cho dù có những trường hợp trong đó trọng Equals và GetHashCode và thực hiện IEquatable <> là thích hợp hơn để sử dụng các StructuralEqualityAttribute.

Điểm sẽ là xác định sự bình đẳng tùy chỉnh của riêng bạn, trong trường hợp này bạn sử dụng CustomEqualityAttribute thay vì StructuralEqualityAttribute.

Nếu có, có thể thực hiện được mà không làm giảm hiệu suất của toán tử = không?

Cập nhật: Tôi khuyên bạn nên tránh mặc định (=) và trực tiếp sử dụng IEquatable (T) .Equals. Bạn có thể xác định toán tử nội tuyến cho toán tử đó hoặc thậm chí bạn có thể xác định lại (=) theo điều kiện của nó. Điều này phù hợp với hầu hết tất cả các loại trong F # và phần còn lại sẽ không biên dịch, do đó bạn sẽ không gặp phải các lỗi nhỏ. I describe the approach in detail here.

gốc: Bắt đầu với F # 4.0, bạn có thể làm như sau (thanks latkin):

[<Struct>] 
type MyVal = 
    val X : int 
    new(x) = { X = x } 
    static member op_Equality(this : MyVal, other : MyVal) = 
     this.X = other.X 

module NonStructural = 
    open NonStructuralComparison 
    let test() = 
     for i in 0 .. 10000000 do 
       (MyVal(i) = MyVal(i + 1)) |> ignore 

// Real: 00:00:00.003, CPU: 00:00:00.000, GC gen0: 0, gen1: 0, gen2: 0 
NonStructural.test() 

Module NonStructuralComparison ghi đè mặc định = với một phiên bản mà chỉ đơn giản gọi op_Equality. Tôi sẽ thêm các thuộc tính NoEqualityNoComparison vào cấu trúc chỉ để đảm bảo bạn không vô tình sử dụng mặc định hoạt động kém =.

Các vấn đề liên quan