Skip to content

Avoid implicitly allocating params arrays in loops #33793

Open

Description

@terrajobst

Find calls to System.* methods inside loops, where those methods take params arrays and those params arrays are being allocated, and hoist the allocation to before the loop if possible.

Category: Performance

Activity

  1. added this to the Future milestone on Mar 20, 2020
  2. jeffhandley commented on Mar 21, 2020

    @jeffhandley
    Member

    Estimates:

    • Analyzer: Medium
    • Fixer: Large
  3. removed
    untriagedNew issue has not been triaged by the area owner
    on Mar 21, 2020
  4. carlossanlop commented on Jan 8, 2021

    @carlossanlop
    Contributor

    @terrajobst can you please provide a code example?

  5. carlossanlop commented on Mar 30, 2021

    @carlossanlop
    Contributor
    • Suggested severity: Info
    • Suggested category: Performance
    • Suggested milestone: Future

    Examples

    Let's use the Console.WriteLine overload that takes a params array:

    public static class Console
    {
      public static void WriteLine(string format, params object?[]? arg);
    }

    Case 1

    Flag

    Before:

    int i = 0;
    while (i++ < 10)
    {
        Console.WriteLine("{0}, {1}, {2}, {3}", 1, 2, 3, 4);
    }

    After:

    int i = 0;
    object[] arg = new object[] { 1, 2, 3, 4 };
    while (i++ < 10)
    {
        Console.WriteLine("{0}, {1}, {2}, {3}", arg);
    }

    Case 2

    Do not flag: An element in the passed params array has variable data

    int i = 0;
    while (i++ < 10)
    {
        Console.WriteLine("{0}, {1}, {2}, {3}", i, 2, 3, 4);
    }

    Case 3

    Flag

    Before: Variables are used inside the loop, but they weren't modified between their creation and their use:

    short a = 10, b = 20, c = 30, d = 40;
    int i = 0;
    while (i++ < 10)
    {
        Console.WriteLine("{0}, {1}, {2}, {3}", a, b, c, d);
    }

    After:

    short a = 10, b = 20, c = 30, d = 40;
    int i = 0;
    object[] arg = new object[] { a, b, c, d };
    while (i++ < 10)
    {
        Console.WriteLine("{0}, {1}, {2}, {3}", arg);
    }

    Case 4

    Having this class:

    class C
    {
        private string _name;
        public C(string name) => _name = name;
        public override string ToString() => _name;
    }

    Flag

    Before:

    int i = 0;
    while (i++ < 10)
    {
        Console.WriteLine("Format {0}, {1}", new C("a"), new C("b"), new C("c"), new C("d"));
    }

    After: Notice the data type of the array can be preserved as object[]

    int i = 0;
    object[] arg = new object[] { new C("a"), new C("b"), new C("c"), new C("d") };
    while (i++ < 10)
    {
        Console.WriteLine("{0}, {1}, {2}, {3}", arg);
    }

    Case 5

    Do not flag: An element in the passed params array has variable data

    int i = 0;
    while (i++ < 10)
    {
        Console.WriteLine("{0}, {1}, {2}, {3}", new C($"{i}"), new C("b"), new C("c"), new C("d"));
    }

    Case 6

    Do not flag: Method does not belong to System.*

    class Program
    {
        static void Main()
        {
            int i = 0;
            while (i++ < 10)
            {
                MyOwnMethod(1, 2, 3, 4);
            }
        }
    
        static void MyOwnMethod(params object[] myargs){ }
    }

    Things to consider:

    • We need to check that the array values do not get modified between iterations.
    • If the analyzer flags a case, the fixer should create the array immediately before the beginning of the loop, using the data type of the method's params array parameter.
    • The new array should be named after the method parameter's name, unless a variable with that name exist, then append a number.
    • Restrict the analyzer to only check invocations of methods that live inside System.*.
    • CS0231: params is always the last parameter in a method signature, so the analyzer should only check if the last parameter is a params array.
  6. added
    api-ready-for-reviewAPI is ready for review, it is NOT ready for implementation
    and removed
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Mar 30, 2021
  7. 2 remaining items

  8. added
    api-approvedAPI was approved in API review, it can be implemented
    and removed
    code-fixerMarks an issue that suggests a Roslyn code fixer
    on Jul 27, 2021
  9. NewellClark commented on Sep 19, 2021

    @NewellClark
    Contributor

    I would like to be assigned to this, please.

  10. removed
    help wanted[up-for-grabs] Good issue for external contributors
    on Oct 26, 2021
  11. added
    in-prThere is an active PR which will close this issue when it is merged
    and removed
    in-prThere is an active PR which will close this issue when it is merged
    on Nov 23, 2021
  12. Youssef1313 commented on Jan 7, 2022

    @Youssef1313
    Member

    I think this is ALREADY implemented (except the "in loop" part and the attribute requirement).

    https://github.com/dotnet/roslyn-analyzers/blob/a46689a39bd6bbea9761961b4683bb85c84e3be8/src/PerformanceSensitiveAnalyzers/Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers.sarif#L12-L24

    An implementation of this as a "CAxxxx" can benefit from the existing implementation.

  13. modified the milestones: Future, 8.0.0 on Nov 9, 2022
  14. huoyaoyuan commented on Apr 27, 2024

    @huoyaoyuan
    Member

    With params ReadOnlySpan<T> coming, is this still valuable?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

api-approvedAPI was approved in API review, it can be implementedarea-System.Runtimecode-analyzerMarks an issue that suggests a Roslyn analyzerpartner-impactThis issue impacts a partner who needs to be kept updated

Type

No type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions