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

What's new in .NET 7 Preview 5 [WIP] #7441

Closed
danmoseley opened this issue May 9, 2022 · 15 comments
Closed

What's new in .NET 7 Preview 5 [WIP] #7441

danmoseley opened this issue May 9, 2022 · 15 comments

Comments

@danmoseley
Copy link
Member

danmoseley commented May 9, 2022

What's new in .NET 7 Preview 5

This issue is for teams to highlight work for the community that will release .NET 7 Preview 5

To add content, use a new conversation entry. The entry should include the team name and feature title as the first line as shown in the template below.

## Team Name: Feature title

[link to the tracking issue or epic item for the work]

Tell the story of the feature and anything the community should pay particular attention 
to be successful using the feature.

Preview 1: #7106
Preview 2: #7107
Preview 3: #7108
Preview 4: #7378
Preview 5: #7441
Preview 6: #7454
Preview 7: #7455

@danmoseley
Copy link
Member Author

danmoseley commented May 9, 2022

dotnet/runtime#67917 by @steveharter

Update: see #7441 (comment) below

@tarekgh
Copy link
Member

tarekgh commented May 10, 2022

Observability

Expose performant ActivityEvent and ActivityLink tags enumerator methods dotnet/runtime#68056

The exposed methods can be used in the perf critical scenarios to enumerate the Tags objects without any extra allocations and with fast items access.

namespace System.Diagnostics
{
    public partial struct ActivityLink
    {
        public Activity.Enumerator<KeyValuePair<string, object?>> EnumerateTagObjects();
    }

    public partial struct ActivityEvent
    {
        public Activity.Enumerator<KeyValuePair<string, object?>> EnumerateTagObjects();
    }
}

Sample Code

        var tags = new List<KeyValuePair<string, object?>>()
        {
            new KeyValuePair<string, object?>("tag1", "value1"),
            new KeyValuePair<string, object?>("tag2", "value2"),
        };
        
        ActivityLink link = new ActivityLink(default, new ActivityTagsCollection(tags));
        
        foreach (ref readonly KeyValuePair<string, object?> tag in link.EnumerateTagObjects())
        {
            // Consume the link tags without any extra allocations or value copying.
        }            
        
        ActivityEvent e = new ActivityEvent("SomeEvent", tags: new ActivityTagsCollection(tags));
        
        foreach (ref readonly KeyValuePair<string, object?> tag in e.EnumerateTagObjects())
        {
            // Consume the event's tags without any extra allocations or value copying.
        }            

@eiriktsarpalis
Copy link
Member

eiriktsarpalis commented May 24, 2022

System.Text.Json

Polymorphism dotnet/runtime#63747

System.Text.Json now supports serializing and deserializing polymorphic type hierarchies using attribute annotations:

[JsonDerivedType(typeof(Derived))]
public class Base
{
    public int X { get; set; }
}

public class Derived : Base
{
    public int Y { get; set; }
}

This configuration enables polymorphic serialization for Base, specifically when the runtime type is Derived:

Base value = new Derived();
JsonSerializer.Serialize<Base>(value); // { "X" : 0, "Y" : 0 }

Note that this does not enable polymorphic deserialization since the payload would be roundtripped as Base:

Base value = JsonSerializer.Deserialize<Base>(@"{ ""X"" : 0, ""Y"" : 0 }");
value is Derived; // false

Using Type Discriminators

To enable polymorphic deserialization, users need to specify a type discriminator for the derived class:

[JsonDerivedType(typeof(Base), typeDiscriminator: "base")]
[JsonDerivedType(typeof(Derived), typeDiscriminator: "derived")]
public class Base
{
    public int X { get; set; }
}

public class Derived : Base
{
    public int Y { get; set; }
}

Which will now emit JSON along with type discriminator metadata:

Base value = new Derived();
JsonSerializer.Serialize<Base>(value); // { "$type" : "derived", "X" : 0, "Y" : 0 }

which can be used to deserialize the value polymorphically:

Base value = JsonSerializer.Deserialize<Base>(@"{ ""$type"" : ""derived"", ""X"" : 0, ""Y"" : 0 }");
value is Derived; // true

Type discriminator identifiers can also be integers, so the following form is valid:

[JsonDerivedType(typeof(Derived1), 0)]
[JsonDerivedType(typeof(Derived2), 1)]
[JsonDerivedType(typeof(Derived3), 2)]
public class Base { }

JsonSerializer.Serialize<Base>(new Derived2()); // { "$type" : 1, ... }

Utf8JsonReader.CopyString dotnet/runtime#54410

Until today, Utf8JsonReader.GetString() has been the only way users could consume decoded JSON strings. This will always allocate a new string, which might be unsuitable for certain performance-sensitive applications. The newly included CopyString methods allow copying the unescaped UTF-8 or UTF-16 strings to a buffer owned by the user:

int valueLength = reader.HasReadOnlySequence ? checked((int)ValueSequence.Length) : ValueSpan.Length;
char[] buffer = ArrayPool<char>.Shared.Rent(valueLength);
int charsRead = reader.CopyString(buffer);
ReadOnlySpan<char> source = buffer.Slice(0, charsRead);

ParseUnescapedString(source); // handle the unescaped JSON string
ArrayPool<char>.Shared.Return(buffer);

Or if handling UTF-8 is preferable:

ReadOnlySpan<byte> source = stackalloc byte[0];
if (!reader.HasReadOnlySequence && !reader.ValueIsEscaped)
{
    source = reader.ValueSpan; // No need to copy to an intermediate buffer if value is span without escape sequences
}
else
{
    int valueLength = reader.HasReadOnlySequence ? checked((int)ValueSequence.Length) : ValueSpan.Length;
    Span<byte> buffer = valueLength <= 256 ? stackalloc byte[256] : new byte[valueLength];
    int bytesRead = reader.CopyString(buffer);
    source = buffer.Slice(0, bytesRead);
}

ParseUnescapedBytes(source);

Source Generation improvements

Added source generation support for IAsyncEnumerable<T>, JsonDocument and DateOnly/TimeOnly types.

@eerhardt
Copy link
Member

System.IO.Stream

Add Stream ReadExactly and ReadAtLeast dotnet/runtime#16598

One of the most common mistakes when using Stream.Read() is that the programmer doesn't realize that Read() may return less data than what is available in the Stream and less data than the buffer being passed in. And even for programmers who are aware of this, having to write the same loop every single time they want to read from a Stream is annoying.

To help this situation, we have added new methods to the base System.IO.Stream class:

namespace System.IO;

public partial class Stream
{
    public void ReadExactly(Span<byte> buffer);
    public void ReadExactly(byte[] buffer, int offset, int count);

    public ValueTask ReadExactlyAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);
    public ValueTask ReadExactlyAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default);

    public int ReadAtLeast(Span<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true);
    public ValueTask<int> ReadAtLeastAsync(Memory<byte> buffer, int minimumBytes, bool throwOnEndOfStream = true, CancellationToken cancellationToken = default);
}

The new ReadExactly methods are guaranteed to read exactly the number of bytes requested. If the Stream ends before the requested bytes have been read an EndOfStreamException is thrown.

Example

using FileStream f = File.Open("readme.md");
byte[] buffer = new byte[100];

f.ReadExactly(buffer); // guaranteed to read 100 bytes from the file

The new ReadAtLeast methods will read at least the number of bytes requested. It can read more if more data is readily available, up to the size of the buffer. If the Stream ends before the requested bytes have been read an EndOfStreamException is thrown. You can opt out of throwing an exception, however that means ReadAtLeast will read less than the requested bytes if the Stream ends.

Example

using FileStream f = File.Open("readme.md");
byte[] buffer = new byte[100];

int bytesRead = f.ReadAtLeast(buffer, 10);
// 10 <= bytesRead <= 100

@danmoseley
Copy link
Member Author

danmoseley commented May 31, 2022

New Roslyn analyzer and fixer for Regex source generator

dotnet/runtime#69872

In his blog post about Regular Expression performance, @stephentoub described the new source generator that will be shipping in .NET 7 to allow you to to statically generate regular expression logic at compile time. In order to take advantage of the source generator, you have to find places in your code where it could be used and make the necessary modification to the code in those places. This sounds like the perfect job for a Roslyn analyzer and fixer, and we've added this in Preview 5.

Analyzer

The new analyzer is inbox in .NET 7, and will search for uses of Regex that could be converted to use the Regex source generator instead. The analyzer will detect both, all uses of the Regex constructors, as well as all uses of the Regex static methods that qualify. In order for a use of Regex to be a candidate (and therefore, flagged by the analyzer) for conversion, the use must:

  • Have all the parameters passed in to the constructor or the static method be constant at compile time. This is since the source generator's output depends on these parameters, so if their values are not constant at compile time, you can't use the source generator for that case.
  • Target .NET 7. The analyzer ships inside the .NET 7 targeting pack, so only apps that target .NET 7 will be eligible for this analyzer.
  • Use LangVersion higher to 10. The regex source generator currently requires LangVersion to be set to preview in your project in order to be able to use the source generator, so the analyzer will only emit diagnostics if the project is using that version.

Here is an example of how the diagnostic is displayed in Visual Studio

image

Code Fixer

The code fixer is also shipping inbox in .NET 7, and will work for all diagnostics emitted by the analyzer. By clicking on "Show potential fixes" on the diagnostic logged by the analyzer, you will see an option called "Convert to 'RegexGenerator'". This is the code fixer and when you apply the changes, it will let you override the name to use for the generated method. It would also replace the invocation code that was causing the diagnostic with an invocation to the new source generated method.

Here is an example of how the code fixer gets applied in Visual Studio

image

We are hoping that this analyzer and fixer will help with the visibility of the source generator and will also make onboarding to it easier. Please try it out and let us know if you have any feedback.

@tannergooding
Copy link
Member

tannergooding commented Jun 2, 2022

Generic Math

In .NET 6 we previewed a feature known as "generic math": https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/. Since then, we have made continuous improvements to the implementation and responded to various feedback from the community in order to ensure that relevant scenarios are possible and the necessary APIs are available.

For more information on the changes and available APIs see https://devblogs.microsoft.com/dotnet/dotnet-7-generic-math/.

@JulieLeeMSFT
Copy link
Member

CodeGen

Community PRs

@SingleAccretion made 23 PR contributions during Preview 5 with some highlighted items below:

  • Improve the redundant branch optimization to handle more side effects PR#68447
  • PUTARG_STK/x86: mark push [mem] candidates reg optional PR#68641
  • Copy propagate on LCL_FLDs PR#68592

@sandreenko completed allowing StoreLclVar src to be IND/FLD PR#59315.
@hez2010 fixed CircleInConvex test in PR#68475.

See contributions from @anthonycanino, @aromaa and @ta264 in the below sections.

Arm64

image

  • PR#68363 consolidated 'msub' and 'madd' logic.

Loop Optimization

image

image

x86/x64 Optimizations

There were a number of optimizations targetted for x86/x64 platforms:

Modernize JIT

As more and more community contributors participate in the JIT code base, it is increasingly getting important to restructure our code and modernize it in order to allow our contributors to easily ramp up and rapidly develop code.
This release we did a lot of work on the internals of the JIT, cleaning up the JIT’s intermediate representation and removing limitations as a result of old design decisions. In many cases this work resulted in less memory usage and higher throughput of the JIT itself, while in other cases it resulted in better code quality. Some of these changes were:

The above allowed us to remove an old limitation in the JIT’s inliner when inlining functions with parameters of byte/sbyte/short/ushort type, resulting in better code quality:

  • Allow the inliner to substitute for small arguments PR#69068

As .NET has become a more and more popular platform for writing high-performance code the coding style has changed. One area where the JIT was lacking was in its understanding of unsafe code involving reads and writes of structs and struct fields. @SingleAccretion contributed great changes in this area by switching the JIT’s internal model to a more general “physical” model. This paves the way for the JIT to better reason about unsafe code using features like struct reinterpretation.

  • Physical value numbering PR#68712
  • Implement constant-folding for VNF_BitCast PR#68979

Other minor cleanups were also made to simplify the JIT IR:

  • Remove GTF_LATE_ARG PR#68617
  • Substitute GT_RET_EXPR in inline candidate arguments PR#69117
  • Remove stores as operands of calls in LIR PR#68460

General Optimizations

A few general JIT optimizations were done in below PRs:

  • PR#68105 enabled multiple nested "no GC" region requests.
  • PR#69034 removed "promoted parameter" tailcall limitation.

@hez2010
Copy link

hez2010 commented Jun 8, 2022

I think we may also want to mention that crossgen2 now has been published with NativeAOT?

/cc: @agocke

@agocke
Copy link
Member

agocke commented Jun 8, 2022

An interesting question -- it does demonstrate that we're shipping programs using NativeAOT, but crossgen is also effectively private surface area, so I'm not sure if it would be helpful to most blog readers

@steveharter
Copy link
Member

System.Reflection performance improvements when invoking members

The overhead of using reflection to invoke a member (whether a method, constructor or a property getter\setter) has been substantially reduced when the invoke is done several times on the same member. Typical gains are 3-4x faster.

See dotnet/runtime#67917 for more information.

Using the BenchmarkDotNet package:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using System.Reflection;

namespace ReflectionBenchmarks
{
    internal class Program
    {
        static void Main(string[] args)
        {
            BenchmarkRunner.Run<InvokeTest>();
        }
    }

    public class InvokeTest
    {
        private MethodInfo? _method;
        private object[] _args = new object[1] { 42 };

        [GlobalSetup]
        public void Setup()
        {
            _method = typeof(InvokeTest).GetMethod(nameof(InvokeMe), BindingFlags.Public | BindingFlags.Static)!;
        }

        [Benchmark]
        // This went from ~116ns to ~39ns or 3x (66%) faster.
        public void InvokeSimpleMethod() => _method!.Invoke(obj: null, new object[] { 42 });

        [Benchmark]
        // This went from ~106ns to ~26ns or 4x (75%) faster.
        public void InvokeSimpleMethodWithCachedArgs() => _method!.Invoke(obj: null, _args);

        public static int InvokeMe(int i) => i;
    }
}

@AngelosP
Copy link

AngelosP commented Jun 13, 2022

Observability

Expose performant ActivityEvent and ActivityLink tags enumerator methods dotnet/runtime#68056

The exposed methods can be used in the perf critical scenarios to enumerate the Tags objects without any extra allocations and with fast items access.

namespace System.Diagnostics
{
    public partial struct ActivityLink
    {
        public Activity.Enumerator<KeyValuePair<string, object?>> EnumerateTagObjects();
    }

    public partial struct ActivityEvent
    {
        public Activity.Enumerator<KeyValuePair<string, object?>> EnumerateTagObjects();
    }
}

@tarekgh Can you please provide some sample code for the above?

@tarekgh
Copy link
Member

tarekgh commented Jun 13, 2022

@AngelosP I have updated my comment #7441 (comment) to include the code sample. Please let me know if you need anything else.

@AngelosP
Copy link

All submission and changes are in the latest draft, thank you all! <3

@eiriktsarpalis
Copy link
Member

@AngelosP one minor erratum concerning the IAsyncEnumerable example:

// It now works and no longer throws NotSupportedException
JsonSerializer.Serialize(new MyPoco { Data = ... }, MyContext.MyPoco); 

The above is not support and will always throw, since IAsyncEnumerable requires one of the async serialization methods. For the sake of correctness it should probably read:

// It now works and no longer throws NotSupportedException
await JsonSerializer.SerializeAsync(new MyPoco { Data = ... }, MyContext.MyPoco); 

@leecow
Copy link
Member

leecow commented Jan 24, 2023

.NET 7 GA is available. Closing these pre-release issues.

@leecow leecow closed this as completed Jan 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests