-
Notifications
You must be signed in to change notification settings - Fork 4.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Invariant Arrays #23393
Comments
As Jon Skeet rightfully concluded:
People just don't write code like |
@Kosat thanks for the comment. This proposal is not about code that uses For example, you can see some checks for coreclr here: The common case is where the element type and the type of the value are equal. This still has to perform tests for null (as it doesn't have a method table), and equality of types. Now potentially the JIT can lift some checks out of loops, but by actually providing proper invariant arrays the performance can be directly improved, by removing these checks in the type system. |
Thank you for the explanation, but with current implementation of JIT_Stelem_Ref, which you reffered to, when we've got an exact match between types (e.g. class A in line 5) class A{}
A var1 = new A();
A[] arr = new A[1];
arr[0] = var1; //line 5 when it comes to type checking, we only pay for comparing the array element type with the type of the rvalue mov r9, [r10 + OFFSETOF__MethodTable__m_ElementType] ; 10h -> typehandle offset
; check for exact match
cmp r9, [r8]
jne NotExactMatch
... In our case they match so we directly processed to writing bits into array. That's pretty much all if types are the same and there is naturally more overhead if there's a typecasting involved. If you want to get rid of those 3 instructions without doing struct wrappers like in Skeet's blog post, you still need to somehow make the static compiler (Roslyn) do that checks for you so jit would gain luxury of not doing those checks in runtime. How can it be done to disable polymorphism in line 5 so that compiler would prevent me from doing this? class A { }
class B: A { }
InvariantArray<A> arr3 = new InvariantArray<A>(10);
arr3[0] = new B(); //line 5 Regarding nulls, there will be changes in C# 8. |
This does not need to be disabled. It is perfectly valid to perform the assignment unchecked, if you do not allow covariant array subtyping. With invariant arrays, it is not that every element is of that precise type. it is that every view of this as an array thinks it is the same type of array. Covariant subtyping on arrays is generally a bad idea, but is very useful if you don't have generics. My plan was to use a particular IL instruction which disables the type checking component of array access (readonly ldelema T). I need to implement the mapping, to see if it works, and will do that in the next day or so. Verification would forbid assigning to this address, it is called "controlled mutability" in the ECMA spec, but by disabling verification for this method, we can bypass that issue. This type of trick was done for a lot of the Span work, but I don't believe this approach was used, so needs testing. |
If you put a fast e.g. string [] a = ...; //A
Span<string> s = a;
s[1] = "hello"; // Invariant array assignment |
Though you pay a type check on construction so isn't better for single assignments, only if you were doing multiple assignments in a loop etc; since it could only live on the stack and needs to be created each time. List would likely be the single assignment case. |
@benaadams I think there is potential to use Span to replace some of the bulk operations on arrays, but I don't think it will work for single assignment case. Although, the JIT has more potential to optimise the Span as the type comparison is done in managed code, rather than the C++ runtime. So far for use cases I think
The |
@mjp41 agreed, it needs to be a heap object you can store with a single initialization, rather than span else will be paying invariance check cost multiple times anyway and it wouldn't help. |
Instead of using an array wrapper, what about using an attribute, similiar to SkipLocalsInit |
@Suchiman I like your idea of enabling this optimization without requiring code changes everywhere! We can start with implementing it in ILLinker to measure the benefit and prove its usefulness, and maybe later in Roslyn. We have taken the same journey for SkipLocalsInit. The implementation in ILLinker can:
|
Raised issue for automatic conversion via escape analysis dotnet/linker#296 However it wouldn't cover all cases; i.e. where escape analysis couldn't be performed, manual opt in. |
Resurrecting this old conversation. If the automatic escape analysis is still some way off, would it make sense to implement InvariantArray as an internal type for now and experiment with plumbing it through our collections? That would prevent a proliferation of unsafe code (it'd all be confined to a single file) and could allow us to collect the evidence we need that the escape analysis improvement would be worthwhile to investigate. |
Take a look at the conversation in dotnet/coreclr#17386. Wrapping arrays in InvariantArray tends to disable optimizations that are happily kicking in today... |
@jkotas I just did a quick experiment on my box and JIT seems to be eliding the bounds check correctly, and 00007ffc`6dd9ceeb ff470c inc dword ptr [rdi+0Ch] ; _version++
00007ffc`6dd9ceee 488b5710 mov rdx,qword ptr [rdi+10h] ; make local enregistered copy of _items
00007ffc`6dd9cef2 8b4f08 mov ecx,dword ptr [rdi+8] ; make local enregistered copy of _size
00007ffc`6dd9cef5 8b4208 mov eax,dword ptr [rdx+8] ; unnecessary local copy of _items.Length
00007ffc`6dd9cef8 3bc1 cmp eax,ecx ; compare size to _items.Length
00007ffc`6dd9cefa 7618 jbe TO_RESIZE_CODE
00007ffc`6dd9cefc 8d4101 lea eax,[rcx+1]
00007ffc`6dd9ceff 894708 mov dword ptr [rdi+8],eax
00007ffc`6dd9cf02 4863c9 movsxd rcx,ecx
00007ffc`6dd9cf05 488d4cca10 lea rcx,[rdx+rcx*8+10h]
00007ffc`6dd9cf0a 488bd6 mov rdx,rsi
00007ffc`6dd9cf0d e8deab8a5f call CoreCLR!JIT_CheckedWriteBarrier (00007ffc`cd647af0)
|
Nice, that looks like it might finally be a win. Is the AddIntToIntList test a repeatable perf regression? It looks above noise. |
@mjp41 It's noise. A subsequent run showed 0.98, not 1.03. The get_Item indexer benchmark (not shown) is also noise: bounces around .99 and 1.02. |
I'll spin up a new PR with the proposed changes. |
New PR: dotnet/coreclr#23571 |
I think instead of a separate type, there should be a runtime mode that would disable array variance completely, and this would be enabled for new projects. |
Due to lack of recent activity, this issue has been marked as a candidate for backlog cleanup. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will undo this process. This process is part of our issue cleanup automation. |
This issue will now be closed since it had been marked |
Rationale and Proposed Usage
.NET arrays are covariant. These leads to many complexities in the implementation of the runtime for dealing with checking if assignments to the array are valid. For example,
The assignment labelled
//B
must check if thea
allowsstring
s to be stored in it. If the initialisation, had been:this would raise an exception at
//B
.There are clever/ugly workarounds to remove the runtime type checks, such as https://codeblog.jonskeet.uk/2013/06/22/array-covariance-not-just-ugly-but-slow-too/. But they do not provide nice interop as moving between covariant arrays and invariant arrays using this approach requires coping.
Generics negates many of the reasons for covariant subtyping on arrays. Let's add arrays that are invariantly typed, and thus do not require type checks for assignments.
This proposal is to wrap standard covariant arrays with a generic struct to make its use invariantly typed, and thus enable optimization at the runtime level. If we had
We would simply not be allowed to cast it to a different type as generic parameters to classes and structs are invariant with respect to subtyping.
I think an initially beneficiary of this addition would be
System.Collections.Generic.List
, where this is used to store lots of objects the runtime type checking can be noticeable.Proposed API
This should be expanded to cover most of the things in System.Array.
Implementation sketch
I believe this could be implemented using similar tricks to those used in
Span
.System.Collections.Generic.List
. This could demonstrate the performance benefits.Issues
The text was updated successfully, but these errors were encountered: