Corefxlab: Is Span<T> usable for .NET Framework 4.6?

Created on 3 Nov 2018  路  11Comments  路  Source: dotnet/corefxlab

My project targets .NET Framework 4.6 and want to onboard the wonderfulness of Span / Memory (and even Utf8String that .NET Core will bring in the near future).

But to migrate the project as a whole to .NET Core requres a lot more effort.

So my question is, is there a smoother path of migration that involves first using Span on top of .NET Framework 4.6, and then migrate the underneath runtime to .NET Core at some future time.

I see that we already have the NuGet package System.Memory : https://www.nuget.org/packages/System.Memory/

I want to double confirm that this package is mature enough to be used with .NET Framework 4.6+.

And is there any API level incompatibleness that could prevent me from further moving to .NET Core in the future if I adopt this package now in .NET Framework.

Help is greatly appreciated!

question or comment

Most helpful comment

It depends on what you mean by usable, precisely.

The short answer is, yes. You can reference the System.Memory package (which was shipped as part of the .NET Core 2.1 release) and get access to Span/Memory along with things like Utf8Parser/etc.

What you don't get, though, is all the other APIs that were added to .NET Core that accept and use span across the platform (things like span-based overloads on stream, since these are .NET Core specific). For a more detailed list of APIs that were span-enabled, see https://github.com/dotnet/corefx/issues/21281

So, it depends on your use case and scenario. You can start introducing span in isolation just fine and grow its usage from there. If you suspect that not having all the other APIs know about span blocks you, then you can only use it in isolation (based on whatever scenarios the APIs in the System.Memory package enable).

And is there any API level incompatibleness that could prevent me from further moving to .NET Core in the future if I adopt this package now in .NET Framework.

I don't think so. As far as I know, once you move to .NET Core (2.1+), your span usage would continue to work and you can remove the reference to the System.Memory package. At that time, in many places, you could then use the span-enabled APIs across the platform as well as incremental improvement. However, there might be some edge case that I haven't considered. Feedback on your usage and migration effort would be awesome (for example, things that caused you friction or scenarios that were blocked)!

cc @terrajobst

All 11 comments

It depends on what you mean by usable, precisely.

The short answer is, yes. You can reference the System.Memory package (which was shipped as part of the .NET Core 2.1 release) and get access to Span/Memory along with things like Utf8Parser/etc.

What you don't get, though, is all the other APIs that were added to .NET Core that accept and use span across the platform (things like span-based overloads on stream, since these are .NET Core specific). For a more detailed list of APIs that were span-enabled, see https://github.com/dotnet/corefx/issues/21281

So, it depends on your use case and scenario. You can start introducing span in isolation just fine and grow its usage from there. If you suspect that not having all the other APIs know about span blocks you, then you can only use it in isolation (based on whatever scenarios the APIs in the System.Memory package enable).

And is there any API level incompatibleness that could prevent me from further moving to .NET Core in the future if I adopt this package now in .NET Framework.

I don't think so. As far as I know, once you move to .NET Core (2.1+), your span usage would continue to work and you can remove the reference to the System.Memory package. At that time, in many places, you could then use the span-enabled APIs across the platform as well as incremental improvement. However, there might be some edge case that I haven't considered. Feedback on your usage and migration effort would be awesome (for example, things that caused you friction or scenarios that were blocked)!

cc @terrajobst

@ahsonkhan awesome! Thanks a lot for the patient and detailed explanation! 馃憤

"Being usable" also extends to reasonable performance. What is the expected impact of looping over such a Span vs an array in .Net Framework? For example, are range checks properly eliminated also on .Net Framework runtime/JIT, on both x64 and x86? What about SIMD in vectors?

"Being usable" also extends to reasonable performance.

Yes, of course. The portable implementation of span is slower than the in-box implementation, so you will likely see some regression for limited scenarios like just looping over a span vs an array. The .NET Framework runtime/JIT is not aware of span. However, measuring on a larger scope when you are dealing with slicing data, parsing, etc., and you save on the data copy/allocations, you would likely still come out on top. Regardless, if high performance is a major priority for you, then the best recommendation would be to move to .NET Core, which OP plans to anyway. Hence, I addressed their compatability and migration questions rather than the performance characteristics of the OOB package (since that is only an intermediary for their end goal).

I don't know enough about SIMD in vectors on .NET Framework versus .NET Core to help there.

Feel free to checkout this article which goes into more details: https://www.codemag.com/Article/1807051/Introducing-.NET-Core-2.1-Flagship-Types-Span-T-and-Memory-T

Due to the extra field and computation of the offset, some portable span APIs are slightly slower than the built-in span APIs that come with .NET Core 2.1.

Thanks for the reply and the link! Some of my early benchmarks indeed showed using some of these newer data structures led to worse performance on .Net Framework. I guess I'll have to use #if directives in my libraries again to differentate between .Net Framework and .Net Standard builds for a while...

Sorry for hijacking the thread.

I guess I'll have to use #if directives in my libraries again to differentate between .Net Framework and .Net Standard builds for a while...

We don't do it in huge majority of our portable libraries as we found the performance differences insignificant.

On a related note: if I make a .net standard 2.0 library which references System.Memory and uses Span, compared to a 'native' .net core 2.1 library will I get the same or worse performance when using the same runtime? (iow, does CoreCLR recognise the Span from the nuget package, or only the built-in one?) Just wondering if it's worth making a separate build for that platform if there's a benefit to doing so.

On a runtime that supports Span it will use the "fast Span".

if I make a .net standard 2.0 library which references System.Memory and uses Span, compared to a 'native' .net core 2.1 library will I get the same or worse performance when using the same runtime? (iow, does CoreCLR recognise the Span from the nuget package, or only the built-in one?)

You can check where Span<T> is coming from to answer that question for yourself. The one from System.Private.CoreLib is the faster implementation that has runtime support. The one from System.Memory is the "portable" span. If your library targets net standard, I wouldn't change it to netcoreapp for the sake of span. It should work fine as a netstandard library as you would expect.

Netstandard library:
```C#
public static class MyClass
{
public static bool MyExtensionIsEmpty(this Span span)
{
Console.WriteLine(typeof(Span).Assembly.FullName);
if (span.Length >= 0) return false;
return true;
}
}

```csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.Memory" Version="4.5.1" />
  </ItemGroup>

</Project>

.Net Core 2.0 application referencing the net standard 2.0 library (or a .NET full framework app):
```C#
class Program
{
static void Main(string[] args)
{
Console.WriteLine(typeof(Span).Assembly.FullName);
Span span = new byte[10];
Console.WriteLine(UseSpan.MyClass.MyExtensionIsEmpty(span));
}
}

```csproj
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.0</TargetFramework>
  </PropertyGroup>
System.Memory, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
System.Memory, Version=4.0.1.0, Culture=neutral, PublicKeyToken=cc7b13ffcd2ddd51
False

.Net Core 2.1 application referencing the net standard 2.0 library:

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>
System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
System.Private.CoreLib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e
False

@pongba does this answer all your questions? If so let's close the issue.

@danmosemsft it does, thank you:)

Was this page helpful?
0 / 5 - 0 ratings