Skip to content

Bringing down the size of Hello World under 2 MB #80165

Closed

Description

@MichalStrehovsky

Hello World with Native AOT is currently at around 2.7 MB. I spent a couple hours to see what would it take to bring it down to the same size as Golang (2 MB). It might actually be achievable with our default trimming settings and feature switches with a couple days of BCL/compiler work.

The benefits would be threefold:

  • Our internal efficiency (compiling non-xunit-based tests will get faster and require less disk space, which is not insignificant as long as we have thousands of tiny tests)
  • We can delete reflection disabled mode
  • Bragging rights

The 6686dbc commit has a couple hacks in it around areas of interest. With those hacks, the size of a hello world goes down to 2.16 MB. With InvariantGlobalization and UseSystemResourceKeys it goes further down to 1.53 MB. This is awfully close to what is achievable with reflection disabled mode. We actually regressed the reflection disabled mode because a Hello World now has a generic virtual method call around enums and we need the entirety of the type loader to support that. But I digress.

Should we choose to accept this challenge, we'll need to fix all of the issues in the commit to really reap the benefit. The benefit is elimination of the entire reflection stack that deals with type members (in fact my goal was to get MemberPolicies class out of the hello world).

Here's the problems:

  • Stack frame should not eagerly try to populate the MethodBase, only once it's requested.
  • Type components cache should not try to eagerly populate the members. We use this class to stash details about enums in the GenericCache property. Populating anything else is a waste.
  • Type.IsEnum and IsValueType. We expand these as intrinsics in codegen for RuntimeTypes. Hello world only needs them for RuntimeTypes. IL scanner might need to learn the RyuJITs trick. (This is really only a problem because IL scanner runs. Reduce the number of forced MethodTables #79594 might already help with this to some extent but I didn't check. Would be nice if it did.)
  • Searching for DefaultDllImportSearchPathsAttribute is avoidable if this codepath is only reached from p/invoke resolution.
  • Attribute.Equals and GetHashCode. This is a tough one. .NET Native didn't use a reflection-based implementation. We could restore that. Other alternative is to see if we can eliminate all custom attribute descendants from a hello world. The theory is that if we make generation of custom attributes conditional on the presence of the code to read them, we might be able to get rid of them all. But not sure. Chose not to address this and live with a small increase in size due to FieldInfo reflection. That one is less impactful than Method/ConstructorInfo.
  • Resource manager looking for attributes. I don't have ideas right now but I also didn't look at it in detail.

Cc @dotnet/ilc-contrib

Activity

  1. ghost added
    untriagedNew issue has not been triaged by the area owner
    on Jan 4, 2023
  2. MichalStrehovsky commented on Jan 4, 2023

    @MichalStrehovsky
    MemberAuthor

    Other alternative is to see if we can eliminate all custom attribute descendants from a hello world. The theory is that if we make generation of custom attributes conditional on the presence of the code to read them, we might be able to get rid of them all. But not sure.

    I did a quick hack and indeed if we don't generate any custom attributes, there will be no constructed System.Attribute descendant in the closure. It also produces another 20 kB of savings.

  3. MichalStrehovsky commented on Jan 13, 2023

    @MichalStrehovsky
    MemberAuthor

    After #80607 we should no longer need the hacks around Type.IsValueType.

  4. MichalStrehovsky commented on Jan 19, 2023

    @MichalStrehovsky
    MemberAuthor

    Once #80842 and #80843 merge, the only remaining spots will be the spot in Attribute.GetHashCode/Equals and the resource manager ones. Once those are addressed, Hello World will be at 2.1 MB, or 1.45 MB with /p:UseSystemResourceKeys=true /p:InvariantGlobalization=true.

  5. ghost added
    in-prThere is an active PR which will close this issue when it is merged
    on Feb 2, 2023
  6. charlesroddie commented on Feb 3, 2023

    @charlesroddie

    Please don't delete reflection-disabled mode.

    1. Sub 2MB is not enough. NativeAOT produces 1MB binaries in reflection-disabled mode in the articles I have seen. The difference is important for apps and critical for WASM deployment to websites.

    2. Since reflection breaks type-safety, reflection-disabled mode helps to guarantee type safety in a codebase.

    3. Reflection is slow and reflection-disabled mode guarantees that this slow code is not present in deployed software.

    4. Reflection-disabled mode creates incentives for libraries to improve code hygiene to support it.

  7. MichalStrehovsky commented on Feb 3, 2023

    @MichalStrehovsky
    MemberAuthor

    NativeAOT produces 1MB binaries in reflection-disabled mode in the articles I have seen.

    For a hello world, the difference is 1.77 MB by default, and 1.47 MB with reflection disabled mode (coming to a .NET 8 Preview 1 near you soon). It can be squashed down to less with the options to optimize codegen for size, disable stack trace data, globalization invariant mode, etc, but those options equally apply to both modes. It really is not that much. Those 300 kB fix issues people have been running into like Enum.ToString only printing numbers in reflection disabled mode.

    There's not much incentive to enable it. I don't think it's going to pressure any libraries into supporting it - it breaks too much stuff. For the argument of perf - there's no law against slow software. We don't offer compiler switches to break other slow code either. There can be a lot of non-reflection slow code in the NuGets.

    Not planning to fully delete it, but I think it's time for people to think about letting that mode go. I've made my peace even though it's my baby.

  8. ghost removed
    in-prThere is an active PR which will close this issue when it is merged
    on Feb 7, 2023
  9. ghost removed
    untriagedNew issue has not been triaged by the area owner
    on Feb 7, 2023
  10. ghost locked as resolved and limited conversation to collaborators on Mar 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions