Skip to content

InlineArray in generic class #106129

Closed

Description

@iSazonov

Steps to Reproduce:

This example code does nothing with the parametric type value except to read and return as a result.
Therefore, it was surprising to find a strong difference in the code size and performance for value and reference types.
Is this expected?

See sharplab

Expected Behavior:

Code for value types and reference types is almost the same, performance is almost the same.

Actual Behavior:

Code for reference types is much larger and works 2-3 times slower.

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Aug 7, 2024
  2. jaredpar commented on Aug 8, 2024

    @jaredpar
    Member

    Moving to runtime as this is a question about the ASM code generation.

  3. transferred this issue fromdotnet/roslynon Aug 8, 2024
  4. ghost added
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Aug 8, 2024
  5. added
    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
    and removed
    needs-area-labelAn area label is needed to ensure this gets routed to the appropriate area owners
    on Aug 8, 2024
  6. dotnet-policy-service commented on Aug 8, 2024

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
    See info in area-owners.md if you want to be subscribed.

  7. jakobbotsch commented on Aug 8, 2024

    @jakobbotsch
    Member

    The generic type is used for InlineArrayAsSpan calls generated under-the-hood by Roslyn. The JIT does not manage to remove all those runtime lookups as some of them feed control flow (in the end, there is no difference in taking the true and false path for these tests, but the JIT's DCE is not able to reason that far).
    We could clean this up if we taught the early liveness passes how to DCE other things than stores, since then we can do it before we expand the runtime lookups into control flow here.

  8. added this to the 10.0.0 milestone on Aug 8, 2024
  9. removed
    untriagedNew issue has not been triaged by the area owner
    on Aug 8, 2024
  10. iSazonov commented on Aug 9, 2024

    @iSazonov
    ContributorAuthor

    For current 9.0 milestone I'd ask to add a warning in docs "Don't use InlineArray with reference types for performance reason". This will save someone a lot of time searching for the reasons why their code is slow.

  11. jakobbotsch commented on Aug 9, 2024

    @jakobbotsch
    Member

    Can you share a benchmark that shows a perf problem warranting such a statement? Your code here always throws NullReferenceException.

  12. iSazonov commented on Aug 9, 2024

    @iSazonov
    ContributorAuthor

    Yes, sharplab example is minimal asm demo.

    I attached working benchmark project for you.
    genericlist.zip

  13. jakobbotsch commented on Aug 9, 2024

    @jakobbotsch
    Member

    I get the following results:

    Method Mean Error StdDev Ratio RatioSD Code Size
    Array 3.570 ns 0.0861 ns 0.0763 ns 1.00 0.00 71 B
    Array2 5.888 ns 0.0634 ns 0.0593 ns 1.65 0.05 43 B
    IPAddress1 6.196 ns 0.1363 ns 0.1208 ns 1.74 0.06 145 B
    IPAddress21 3.590 ns 0.0596 ns 0.0558 ns 1.01 0.03 53 B

    In general we expect shared generic code to be slightly slower than unshared code. For example, you will also see a slowdown for List<string>.set_Item compared to List<int>.set_Item. We don't try to document this anywhere, so I'm not sure it's warranted to do it for this case either, especially considering that I expect we can improve on this (either by improving our DCE, or by implementing #105586).

    Thoughts @dotnet/jit-contrib @jkotas?

  14. added a commit that references this issue on Aug 9, 2024
    74eea89
  15. added
    in-prThere is an active PR which will close this issue when it is merged
    on Aug 9, 2024
  16. iSazonov commented on Aug 9, 2024

    @iSazonov
    ContributorAuthor

    In general we expect shared generic code to be slightly slower than unshared code. For example, you will also see a slowdown for List.set_Item compared to List.set_Item.

    While I was researching this problem, I measured this example too and the differences there are scanty, there is an understanding of this and it is acceptable.

    If we compare 1 and 3 lines (InlineArray code - value vs referense) - diff is 2.47!

    | Method      | Mean      | Error     | StdDev    | Ratio | RatioSD | Code Size |
    |------------ |----------:|----------:|----------:|------:|--------:|----------:|
    | Array       | 13.817 ns | 0.1581 ns | 0.1479 ns |  1.00 |    0.00 |      81 B |
    | Array2      |  9.376 ns | 0.2055 ns | 0.1922 ns |  0.68 |    0.02 |      47 B |
    | IPAddress1  | 34.252 ns | 0.4945 ns | 0.4129 ns |  2.47 |    0.04 |     184 B |
    | IPAddress21 | 11.089 ns | 0.2526 ns | 0.2363 ns |  0.80 |    0.02 |      54 B |
    

    I don't insist on documenting. But the problem is that there are intuitive expectations that the new InlineArray is designed for high-performance code, although it works worse than the usual language constructs.

  17. jakobbotsch commented on Aug 9, 2024

    @jakobbotsch
    Member

    I think we can get a fix for this into .NET 9. #106183 and #106185 both fix the performance disparity here.

  18. modified the milestones: 10.0.0, 9.0.0 on Aug 9, 2024
  19. self-assigned this
    on Aug 9, 2024
  20. locked and limited conversation to collaborators on Sep 12, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMIin-prThere is an active PR which will close this issue when it is merged

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions