colgreen / Redzen

General purpose C# code library.
Other
94 stars 16 forks source link

Make use of SkipLocalsInit #17

Closed colgreen closed 2 years ago

colgreen commented 3 years ago

Local field init (zeroing) can be optionally omitted. See:

C# 9 - Improving performance using the SkipLocalsInit attribute

It appears from the benchmarks in that link, that this is most impactful where a stackalloc of a reasonable size is performed. There are also risky scenarios if using 'Unsafe' access to fields, hence possible/probably this should be done on a per method basis, rather than a wider scope (class or module/project level). That said, the dotnet runtime repo seems to use this csproj element '', albeit conditionally per project:

  <PropertyGroup>
    <SkipLocalsInit Condition="'$(SkipLocalsInit)' == '' and '$(MSBuildProjectExtension)' == '.csproj' and '$(IsNETCoreAppSrc)' == 'true' and ($([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', '$(NetCoreAppCurrent)')))">true</SkipLocalsInit>
  </PropertyGroup>

Otherwise, possible targets here are the Random and Distribution classes.

colgreen commented 2 years ago

Done. I decided to enable this at the project level, as per the dotnet runtime projects.

HavenDV commented 2 years ago

I want to note that in fact you did not include this. Here is the dotnet config file you have probably seen. https://github.com/dotnet/runtime/blob/71e324fa72e31928d39e8e2f375bdca4c0a11957/src/libraries/Directory.Build.targets#L222-L238 Here is the code you need to include in the build for this to work: https://github.com/dotnet/runtime/blob/71e324fa72e31928d39e8e2f375bdca4c0a11957/src/libraries/Common/src/SkipLocalsInit.cs

HavenDV commented 2 years ago

https://github.com/HavenDV/SkipLocalsInit I recommend this package as the easiest way to do it. It will add two files to compile and set the MSBuild property to AllowUnsafeBlocks. This is a completely develop-time package, the usage is quite simple:

<PackageReference Include="SkipLocalsInit" Version="1.0.0" PrivateAssets="all" />
colgreen commented 2 years ago

Thanks for pointing this out, @HavenDV.

For the record, whether the parameter/locals initialization is enabled or not can be seen by examining the IL for a method (e.g. using ILSpy):

E.g.

.method public hidebysig static 
    int32 Log2Ceiling (
        uint32 x
    ) cil managed 
{
    // Method begins at RVA 0x35c4
    // Header size: 12
    // Code size: 22 (0x16)
    .maxstack 2
    .locals init (
        [0] int32 exp
    )

vs.

.method public hidebysig static 
    int32 Log2Ceiling (
        uint32 x
    ) cil managed 
{
    // Method begins at RVA 0x35c4
    // Header size: 12
    // Code size: 22 (0x16)
    .maxstack 2
    .locals (
        [0] int32 exp
    )

I.e. .locals init becomes .locals

In practical terms, the distinction is most notable when performing stackalloc, as the allocated span will contain unitialized data, and the author of the method must take care to handle this.