Skip to content

Inconsistent inlining between Span.ToArray and ReadOnlySpan.ToArray #119713

Closed

Description

@MihaZupan

It's odd that Span.ToArray is marked AggressiveInlining but ReadOnlySpan.ToArray isn't.
This is the only shared helper where they differ in this way.

Seems to have snuck in during dotnet/corefx#26667

/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] ToArray()

/// </summary>
public T[] ToArray()

Activity

  1. added
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Sep 15, 2025
  2. added and removed
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Sep 15, 2025
  3. dotnet-policy-service commented on Sep 15, 2025

    @dotnet-policy-service
    Contributor

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

  4. xtqqczze commented on Sep 15, 2025

    @xtqqczze
    Contributor

    Inlining seems beneficial if the span length is known to be non-zero, as the static ctor for Array.Empty<T>() can then be elided.

  5. xtqqczze commented on Sep 15, 2025

    @xtqqczze
    Contributor

    Some nice code size improvements by removing AggressiveInlining and splitting the implementation across two methods: MihuBot/runtime-utils#1497

  6. added
    needs-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration
    and removed
    untriagedNew issue has not been triaged by the area owner
    on Sep 24, 2025
  7. added this to the Future milestone on Sep 24, 2025
  8. added
    help wanted[up-for-grabs] Good issue for external contributors
    and removed
    needs-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration
    on Aug 17, 2026
  9. jeffhandley commented on Aug 17, 2026

    @jeffhandley
    Member

    Marked as help wanted, inviting a thorough analysis of the tradeoffs.

  10. xtqqczze commented on Aug 17, 2026

    @xtqqczze
    Contributor

    Some nice code size improvements by removing AggressiveInlining and splitting the implementation across two methods: MihuBot/runtime-utils#1497

    Code size improvement is even better now: MihuBot/runtime-utils#2119

  11. jkotas commented on Aug 17, 2026

    @jkotas
    Member

    code size improvements

    These numbers do not seem to account for the code size of the non-inlined method.

    Also, I assume that the code size improvements would be even better if the method is not split. Is that right? I do not see why we would want to pay extra code bloat to optimize ToArray() on empty Span.

  12. xtqqczze commented on Aug 17, 2026

    @xtqqczze
    Contributor

    The way I see it, the useful case is when the span is known to be non-empty, since the JIT can then get rid of the static constructor for Array.Empty<T>. So it's a way of having the JIT inline everything in that case without using AggressiveInlining.

    Let’s see anyway: MihuBot/runtime-utils#2120, MihuBot/runtime-utils#2121.

  13. jkotas commented on Aug 17, 2026

    @jkotas
    Member

    when the span is known to be non-empty

    How common is that with ToArray?

  14. MihaZupan commented on Aug 17, 2026

    @MihaZupan
    MemberAuthor

    Code size improvement is even better now

    Note that this is not an apples-apples comparison since the default MihuBot job is now also testing against a small subset of NuGet packages

    646 total methods with Code Size differences (598 improved, 48 regressed), 249906 unchanged.
    916 total methods with Code Size differences (859 improved, 57 regressed), 815988 unchanged.

  15. xtqqczze commented on Aug 17, 2026

    @xtqqczze
    Contributor

    when the span is known to be non-empty

    How common is that with ToArray?

    Not that uncommon, here's an example:

    public static ImmutableArray<T> Create<T>(params ReadOnlySpan<T> items)
    {
    if (items.IsEmpty)
    {
    return ImmutableArray<T>.Empty;
    }
    T[] array = items.ToArray();
    return new ImmutableArray<T>(array);
    }

  16. jkotas commented on Aug 17, 2026

    @jkotas
    Member

    Not that uncommon, here's an example:

    The special casing for items.IsEmpty should not be needed in this case. I think we should delete it. Span.ToArray takes care of special casing the empty array already.

  17. xtqqczze commented on Aug 17, 2026

    @xtqqczze
    Contributor

    In the case of ImmutableArray, we also target .NET Framework, so this may well be written this way for a reason. If not, there doesn’t seem to be any reason to use ImmutableArray<T>.Empty rather than new ImmutableArray<T>(new T[0]) internally.

  18. jkotas commented on Aug 17, 2026

    @jkotas
    Member

    this may well be written this way for a reason.

    I doubt it. For reference, this was introduced in #61196. .NET Framework Span is very slow, there is no point in trying to optimize for it.

    there doesn’t seem to be any reason to use ImmutableArray.Empty rather than new ImmutableArray(new T[0]) internally.

    ImmutableArray.Empty is a singleton. It avoids GC heap allocation. The same optimization is in .NET Framework Span.

    There is also Create() that is wrapper around ImmutableArray.Empty. There is no reason to use Create() internally - it is just an extra overhead. You can replace all Create() with ImmutableArray.Empty if you are going to submit a PR for this.

  19. xtqqczze commented on Aug 17, 2026

    @xtqqczze
    Contributor

    I think the special casing avoids an extra generic instantiation for Array.Empty<T>() in Span<T>.ToArray. Perhaps this explains the significant code size improvement in MihuBot/runtime-utils#2119

  20. added a commit that references this issue on Aug 24, 2026
    868c06a
  21. locked and limited conversation to collaborators on Oct 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions