Skip to content
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

Enable trimming TLS arrays in ArrayPool.Shared #56316

Merged
merged 3 commits into from
Jul 31, 2021

Conversation

stephentoub
Copy link
Member

Today arrays stored in ArrayPool<T>.Shared's per-core buckets have trimming applied, but arrays stored in the per-thread buckets are only trimmed when there's high memory pressure. This change enables all buffers to be trimmed (eventually). Every time our gen2 callback runs, it ensures any non-timestamped buffers have a timestamp, and also ensures that any timestamped buffers are still timely... if any aren't, they're eligible for trimming. The timestamp is reset for TLS arrays when they're stored, and for per-core buckets when they transition from empty to non-empty; the latter is just a tweak on the current behavior, which incurs the cost of Environment.TickCount upon that transition, whereas now we only pay it as part of the trimming pass.

Contributes to #52098
cc: @jkotas, @Maoni0

@ghost
Copy link

ghost commented Jul 26, 2021

Tagging subscribers to this area: @GrabYourPitchforks, @dotnet/area-system-buffers
See info in area-owners.md if you want to be subscribed.

Issue Details

Today arrays stored in ArrayPool<T>.Shared's per-core buckets have trimming applied, but arrays stored in the per-thread buckets are only trimmed when there's high memory pressure. This change enables all buffers to be trimmed (eventually). Every time our gen2 callback runs, it ensures any non-timestamped buffers have a timestamp, and also ensures that any timestamped buffers are still timely... if any aren't, they're eligible for trimming. The timestamp is reset for TLS arrays when they're stored, and for per-core buckets when they transition from empty to non-empty; the latter is just a tweak on the current behavior, which incurs the cost of Environment.TickCount upon that transition, whereas now we only pay it as part of the trimming pass.

Contributes to #52098
cc: @jkotas, @Maoni0

Author: stephentoub
Assignees: -
Labels:

area-System.Buffers

Milestone: -

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@vargaz
Copy link
Contributor

vargaz commented Jul 29, 2021

The failures are caused by a linker problem. Here is the original finally clause in ::Trim ():

     finally
    {
      IL_009a:  ldloc.s    V_5
      IL_009c:  brfalse.s  IL_00a5

      IL_009e:  ldloc.s    V_5
      IL_00a0:  callvirt   instance void System.IDisposable::Dispose()
      IL_00a5:  endfinally
    }  // end handler

and here is the linked version:

    finally
    {
      IL_0091:  ldloc.s    V_5
      IL_0093:  brfalse.s  IL_009c

      IL_0095:  ldloc.s    V_5
      IL_0097:  callvirt   instance void System.IDisposable::Dispose()
      IL_009c:  endfinally
      IL_009d:  ldloc.1
      IL_009e:  ldc.i4.1
      IL_009f:  bne.un.s   IL_00aa

      IL_00a1:  ldc.i4     0x3a98
    }  // end handler

@stephentoub
Copy link
Member Author

The failures are caused by a linker problem

cc: @sbomer, @vitek-karas

@danmoseley
Copy link
Member

@antonfirsov

@stephentoub
Copy link
Member Author

@steveisok, how can we unblock this PR?

@danmoseley
Copy link
Member

@stephentoub failures in runtime-staging configurations do not block merge. Although, I thought they should not show up as red for that reason.

@steveisok
Copy link
Member

steveisok commented Jul 30, 2021

@stephentoub failures in runtime-staging configurations do not block merge. Although, I thought they should not show up as red for that reason.

That's true for test failures only. If you have a build failure, then it'll show up red.

@danmoseley
Copy link
Member

(I mean, they don't block merge if the failures aren't clearly related. If this change breaks them more, we probably don't want to merge it.)

@danmoseley
Copy link
Member

That makes sense.

@stephentoub
Copy link
Member Author

I mean, they don't block merge if the failures aren't clearly related. If this change breaks them more, we probably don't want to merge it.

Right. The problem is that the correct C# code and generated IL introduced by this PR is then getting mutilated by the linker and causing the AOT compiler to seg fault. As a temporary workaround to unblock this PR, I'd be happy to tweak the C# code to avoid whatever pattern is tripping up the linker, if someone can suggest what that pattern is. But it seems to me there's both a bug in the linker (dotnet/linker#2181) and a bug in the AOT compiler (seg faulting in response to bad IL) to be fixed here.

@jkotas
Copy link
Member

jkotas commented Jul 30, 2021

if someone can suggest what that pattern is.

My guess is that ifdefing the logging specific paths in Trim method for Mono is going to workaround the linker bug:

#if !MONO // Workaround for http://github.com/...
     if (log.IsEnabled())
     {
     ....
     }
     else
#endif

@stephentoub
Copy link
Member Author

Ok, thanks, I'll give that a go.

Today arrays stored in `ArrayPool<T>.Shared`'s per-core buckets have trimming applied, but arrays stored in the per-thread buckets are only trimmed when there's high memory pressure.  This change enables all buffers to be trimmed (eventually).  Every time our gen2 callback runs, it ensures any non-timestamped buffers have a timestamp, and also ensures that any timestamped buffers are still timely... if any aren't, they're eligible for trimming.  The timestamp is reset for TLS arrays when they're stored, and for per-core buckets when they transition from empty to non-empty; the latter is just a tweak on the current behavior, which incurs the cost of Environment.TickCount upon that transition, whereas now we only pay it as part of the trimming pass.
@stephentoub stephentoub merged commit 7d9a08c into dotnet:main Jul 31, 2021
@stephentoub stephentoub deleted the trimmingtls branch July 31, 2021 16:13
@DrewScoggins
Copy link
Member

We are seeing some wins in the ArrayPool perf tests as a result of this change.

image

@stephentoub
Copy link
Member Author

Thanks, @DrewScoggins. It wasn't the initial intention of the PR, but I'm glad it had this affect (I expect from removing the TickCount check every time we transitioned from 0 to 1 array stored in the per-core stack). Do you see any movement here that might counteract what you reported in #56555?

@DrewScoggins
Copy link
Member

Yes, we saw some of it come back, but not all.

image

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants