Skip the `FSharpType.IsUnion` check when a quotation node for an instance member is built
Maintainer thường phản hồi trong vòng 3 ngày
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 3/5
- Thời gian dự kiến
- 1-2 ngày
- Mức phù hợp với người mới
- 57/100
- Loại issue
- Tính năng
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- fsharp
- Lĩnh vực
- performance
Hướng nghiên cứu
Start by reading checkObj and the instance member builders named in the issue, then inspect how RebuildShapeCombination reaches them. The proposed change is to check whether the receiver type is assignable to the declaring type before running the union type lookup. Verify that existing accepted and rejected receiver cases remain the same and compare instance member construction benchmarks with the static case.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Is your feature request related to a problem? Please describe.
Building a quotation node for an instance property, field or method takes 5 µs, a hundred times what the node for a static member takes. Every transformation through ExprShape pays that for each such node, because RebuildShapeCombination runs the same check as Expr.PropertyGet and Expr.Call.
type Person = { Name : string; Age : int; City : string }
type Clock () =
static member Zone = "UTC"
[<MemoryDiagnoser>]
type InstanceMember () =
let p = Var ("p", typeof<Person>)
let q = Var ("q", typeof<Person>)
let person = Expr.Var p
let age = typeof<Person>.GetProperty "Age"
let zone = typeof<Clock>.GetProperty "Zone"
let predicate =
<@@ (%%person : Person).Age >= 18 && (%%person : Person).City = "Kyiv" && (%%person : Person).Name <> "" @@>
let rec renameByShape (expr : Expr) : Expr =
match expr with
| ShapeVar v -> if v = p then Expr.Var q else expr
| ShapeLambda (v, body) -> Expr.Lambda (v, renameByShape body)
| ShapeCombination (shape, parts) -> RebuildShapeCombination (shape, List.map renameByShape parts)
[<Benchmark>]
member _.StaticProperty () = Expr.PropertyGet zone
[<Benchmark>]
member _.InstanceProperty () = Expr.PropertyGet (person, age)
[<Benchmark>]
member _.IsUnion () = FSharpType.IsUnion typeof<Person>
[<Benchmark>]
member _.RenameByShape () = renameByShape predicate
[<Benchmark>]
member _.RenameBySubstitute () = predicate.Substitute (fun v -> if v = p then Some (Expr.Var q) else None)
| Method | Mean | Allocated |
|---|---|---|
| StaticProperty | 0.05 µs | 104 B |
| InstanceProperty | 5.2 µs | 1216 B |
| IsUnion | 5.1 µs | 1080 B |
| RenameByShape | 20.1 µs | 7824 B |
| RenameBySubstitute | 0.8 µs | 2264 B |
BenchmarkDotNet 0.15.8, .NET 10.0.12, FSharp.Core 10.1.401, Windows 11 x64. The machine was not idle: standard deviations are up to 10 percent, the allocations are exact. A property of a class, an instance method call and RebuildShapeCombination of one such node measure like InstanceProperty.
Where it goes, as far as I read the code at a274432:
-
mkInstancePropGet,mkInstancePropSet,mkInstanceFieldGet,mkInstanceFieldSet,mkInstanceMethodCallandmkInstanceMethodCallWcallcheckObj, andRebuildShapeCombinationcalls them. -
checkObjfirst asksFSharpType.IsUnionabout the declaring type, for the case that the member belongs to the nested class of a union case:let rec loop (ty: Type) = if FSharpType.IsUnion ty && FSharpType.IsUnion ty.BaseType then loop ty.BaseType else ty let declType = loop membInfo.DeclaringType if not (assignableFrom declType (typeOf obj)) then invalidArg "obj" (SR.GetString(SR.QincorrectInstanceType)) -
FSharpType.IsUnionreadsCompilationMappingAttributethroughGetCustomAttributesand keeps nothing; for a record type that is four reads in one call (isExceptionRepr,isConstructorReprfor the type and its base type,isUnionType). -
Expr.Substitutebuilds nodes without the check, hence the last row.
Describe the solution you'd like
Test the common case first: when the receiver's type is assignable to the declaring type, the node is valid and nothing else has to be asked.
let checkObj (membInfo: MemberInfo) (obj: Expr) =
let objType = typeOf obj
if not (assignableFrom membInfo.DeclaringType objType) then
// as today: find the union type, test again, raise
It accepts and rejects what the current code does: loop gives the declaring type or a base type of it, and a receiver assignable to a type is assignable to its base types. I have not built FSharp.Core with it; from the table, an instance member node should then cost about what a static one costs. I can send a pull request.
Describe alternatives you've considered
- Keeping the answer of
FSharpType.IsUnionper type. It helps every caller ofFSharp.Reflection, but adds a cache for the life of the process where the change above removes the question. - In my code:
Expr.Substitutewhere a rewrite only replaces variables. A rewrite that replaces any other node still rebuilds every node above it.
Additional context
Found while measuring a quotation normalizer (pipes, let and [<ReflectedDefinition>] calls rewritten away) for fsprojects/FSharp.Azure.Cosmos: inlining a function with five member accesses took about 57 µs through ExprShape and 22 µs once the parameters were replaced with Expr.Substitute.
- Ngôn ngữ chính
- F#
- Star
- 4.3k
- Fork
- 880
- Merge trung bình
- 4 ngày 14 phút
- Pull request đã merge (30 ngày)
- 99
Chuẩn bị môi trường
Khởi chạy dev container của dự án ngay trên trình duyệt, bằng tài khoản GitHub của bạn.
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của dotnet/fsharp
-
Semantic classification cache for opened documents is never populated (written to the unopened-documents cache)Có thể đã có người làm @xperiandri đã nhận 38 ngày trước. Đang mởNeeds-Triage
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 90/100
Maintainer thường phản hồi trong vòng 3 ngày
-
Needs-Triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
dotnet/fsharp#20265 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 3 ngày
-
Bug Needs-Triage
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
Maintainer thường phản hồi trong vòng 3 ngày
-
Feature Request Needs-Triage
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
Maintainer thường phản hồi trong vòng 3 ngày
-
VS: hang when deleting a fileĐang mởBug Needs-Triage
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 53/100
Maintainer thường phản hồi trong vòng 3 ngày
Tất cả issue của dotnet/fsharp
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 76/100
swc-project/swc#12516 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Possible memory leak: message iterators from pactffi_pact_handle_get_*message_iter are never deletedĐang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 85/100
pact-foundation/pact-go#652 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 77/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 82/100
prime-radiant-inc/evener#4370 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
track: dx
Độ khó 1/5 1-3 giờ Mức phù hợp với người mới 76/100