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

Prefer static ReadOnlySpan<byte> properties over static readonly byte[] fields #33780

Open
Tracked by #57797 ...
terrajobst opened this issue Mar 19, 2020 · 22 comments · May be fixed by dotnet/roslyn-analyzers#5548
Open
Tracked by #57797 ...
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime code-analyzer Marks an issue that suggests a Roslyn analyzer code-fixer Marks an issue that suggests a Roslyn code fixer in-pr There is an active PR which will close this issue when it is merged
Milestone

Comments

@terrajobst
Copy link
Member

With a pattern like private static readonly byte[] s_array = new byte[] { ... } where the static readonly byte[] is internal/private, all consumers of s_array could instead operate on a span, and the field is initialized to a new byte[] of constant values, it can be changed instead to static ReadOnlySpan<byte> Data => new byte[] { ... }, and the C# compiler will optimize the implementation.

Category: Performance

@terrajobst terrajobst added api-suggestion Early API idea and discussion, it is NOT ready for implementation area-System.Runtime code-analyzer Marks an issue that suggests a Roslyn analyzer labels Mar 19, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Mar 19, 2020
@stephentoub stephentoub changed the title Prefer ReadOnlySpan<byte> over byte[] Prefer static ReadOnlySpan<byte> properties over static readonly byte[] fields Mar 20, 2020
@jeffhandley jeffhandley added this to the Future milestone Mar 20, 2020
@jeffhandley jeffhandley added the code-fixer Marks an issue that suggests a Roslyn code fixer label Mar 21, 2020
@jeffhandley
Copy link
Member

Estimates:

  • Analyzer: Medium
  • Fixer: Small

@terrajobst terrajobst removed the untriaged New issue has not been triaged by the area owner label Mar 21, 2020
turbedi added a commit to turbedi/sharpcompress that referenced this issue Apr 6, 2020
@Mrnikbobjeff
Copy link

Should this only trigger for byte or for other primitives as well?

@stephentoub
Copy link
Member

byte, sbyte, and bool

@carlossanlop
Copy link
Member

The analyzer makes sense, but the fixer is potentially problematic. Take a look at the referenced PR from above, where after converting the byte array to a ROS, the places where it was used had to be heavily adjusted.

Applying a fixer would cause build failures if methods that are exclusive for that data type were being called (byte[].Rank, for example).

Question:

  • Why should only byte, sbyte, bool be considered for this analyzer?

Cases that the analyzer would handle:

// Yes
private static readonly byte[] s_byte = new byte[] { ... };
internal static readonly byte[] s_byte = new byte[] { ... };

// No
public static readonly byte[] s_byte = new byte[] { ... }; // public
protected static readonly byte[] s_byte = new byte[] { ... }; // protected
private readonly byte[] s_byte = new byte[] { ... }; // no static
private static byte[] s_byte = new byte[] { ... }; // no readonly

@stephentoub
Copy link
Member

Why should only byte, sbyte, bool be considered for this analyzer?

Until #24961 is implemented and Roslyn optimizes with it, Roslyn only applies the relevant optimization to single-byte primitive types, i.e. byte, sbyte, and bool.

@ghost ghost added the no-recent-activity label Feb 6, 2021
@ghost
Copy link

ghost commented Feb 6, 2021

This issue has been automatically marked no recent activity because it has been marked as needs author feedback but has not had any activity for 14 days. It will be closed if no further activity occurs within 7 more days. Any new comment (by anyone, not necessarily the author) will remove no recent activity

@carlossanlop
Copy link
Member

Removing the label so it doesn't get autoclosed by the bot.

This could be ready to mark as ready for review, keeping in mind that the analyzer would be limited to byte/sbyte/bool. We can decide if we want a fixer or not in the API review meeting.

@buyaa-n @stephentoub any additional thoughts you'd like to share before I apply the label?

@En3Tho
Copy link
Contributor

En3Tho commented Feb 6, 2021

@stephentoub Is there an option to get static read-only ros backing memory region by reflection? Can we actually consider adding const modifier to such ros? Like with { 1, 2, 3, 4, 5 } value

@stephentoub
Copy link
Member

Can we actually consider adding const modifier to such ros? Like with { 1, 2, 3, 4, 5 } value

This is a question for C#. I suggest opening an issue/proposal in the csharplang repo, if there isn't one already.

@stephentoub
Copy link
Member

stephentoub commented Feb 6, 2021

We can decide if we want a fixer or not in the API review meeting.

We do :-)

@carlossanlop carlossanlop added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Feb 6, 2021
@AraHaan
Copy link
Member

AraHaan commented Apr 4, 2021

I would prefer this for other things like short[], and ushort[] on static fields/properties as well so that way ``new`ing them up can be avoided for fixed data.

But that could be for really much any type[] arrays where type is any type the developer is returning as an array on a property.

@stephentoub
Copy link
Member

stephentoub commented Apr 4, 2021

You can't store a span in a field, which this does:

    internal static ReadOnlySpan<byte> BlOrder { get; } = CreateBlOrder();

This would need to be:

    internal static ReadOnlySpan<byte> BlOrder => CreateBlOrder();

@bartonjs
Copy link
Member

bartonjs commented Jun 3, 2021

Video

Category: Performance
Level: Info

  • The analyzer should look at all static readonly field declarations where the field type is an array of byte/sbyte/bool, the array is initialized with a literal array initializer. (Note that in the future some other types may also be allowed, but that will depend on the presence of a RuntimeFeatures feature)
    • Then do a candidate elimination round: Any time that the array was passed to some other method as an array (vs going through AsSpan explicitly or implicitly) then do not report on this field.
  • Any remaining fields can be changed to the new pattern.

@bartonjs bartonjs added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Jun 3, 2021
@carlossanlop carlossanlop added the help wanted [up-for-grabs] Good issue for external contributors label Sep 14, 2021
@NewellClark
Copy link
Contributor

I would like to be assigned to this, please.

@Youssef1313
Copy link
Member

Youssef1313 commented Oct 5, 2021

it can be changed instead to static ReadOnlySpan<byte> Data => new byte[] { ... },

@terrajobst Should be { get; } = instead of =>? (this was caught by @jaredpar in dotnet/roslyn#56845 (comment))

Edit: Actually this might be intentional per #33780 (comment). I'm not sure.

@Youssef1313
Copy link
Member

@jaredpar It seems like static ReadOnlySpan<byte> Data => new byte[] { 0, 0, 1, 2, 3 }; doesn't actually allocate?

internal class C
{
    private unsafe static ReadOnlySpan<byte> Data
    {
        get
        {
            return new ReadOnlySpan<byte>(System.Runtime.CompilerServices.Unsafe.AsPointer(ref <PrivateImplementationDetails>.A498EFA8D0759E7B704095853DB9BC1EF8CF65AF37BED9D006C4B2917E695061), 5);
        }
    }
}
[CompilerGenerated]
internal sealed class <PrivateImplementationDetails>
{
    [StructLayout(LayoutKind.Explicit, Pack = 1, Size = 5)]
    private struct __StaticArrayInitTypeSize=5
    {
    }

    internal static readonly __StaticArrayInitTypeSize=5 A498EFA8D0759E7B704095853DB9BC1EF8CF65AF37BED9D006C4B2917E695061/* Not supported: data(00 00 01 02 03) */;
}

@jaredpar
Copy link
Member

jaredpar commented Oct 5, 2021

That's correct. I frequently forget that this pattern is optimized to be non-allocating.

@AraHaan
Copy link
Member

AraHaan commented Oct 5, 2021

That is until you turn the span into a list or array, then it allocates a copy.

However what if I did not want to allocate an array but instead operate on the data in the span, (and modify it) I am assuming then it would modify it for then whenever the application ever uses that property again to get a span, it would get not the original one, but the modified one which would be an regression on my end that I would have to be careful with while at the same time I want to reduce allocations to as little to nothing if possible.

@gfoidl
Copy link
Member

gfoidl commented Oct 6, 2021

instead operate on the data in the span, (and modify it)

It's a ReadOnlySpan (or sbyte, bool), so any modification should be prohibited in safe code.

@skyoxZ
Copy link
Contributor

skyoxZ commented Oct 22, 2021

It seems that this change can also help to avoid some potential bug:

byte[] arr = new DataWrapper().GetArray();
arr[0] = 2;
Console.WriteLine(new DataWrapper().GetArray()[0]);

class DataWrapper
{
    private static readonly byte[] array = new byte[] { 1 };

    public byte[] GetArray() => array;
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-approved API was approved in API review, it can be implemented area-System.Runtime code-analyzer Marks an issue that suggests a Roslyn analyzer code-fixer Marks an issue that suggests a Roslyn code fixer in-pr There is an active PR which will close this issue when it is merged
Projects
None yet
Development

Successfully merging a pull request may close this issue.