Reducing allocations in parsing#2001
Conversation
… when it's actually null
* don't allocate new string to remove brackets * don't allocate an array of characters (string.Split argument) * don't allocate an array of strings (result of string.Split)
…nternal methods that we control)
…it's creating a copy every time it's called!
| /// </summary> | ||
| /// <remarks>If <see cref="CommandLineConfiguration.EnableDirectives"/> is set to <see langword="false"/>, then this collection will be empty.</remarks> | ||
| public IReadOnlyDictionary<string, IReadOnlyList<string>> Directives { get; } | ||
| public IReadOnlyDictionary<string, IReadOnlyList<string>> Directives => _directives ??= new (); |
There was a problem hiding this comment.
in the future we are going to use IReadOnlyDictionary<TKey, TValue>.Empty introduced very recently in .NET 8: dotnet/runtime#76028
There was a problem hiding this comment.
Static Empty properties were not added to the interfaces after all, but ReadOnlyDictionary<TKey, TValue>.Empty was added.
| public void ProvideSuggestions(ParseResult result, IConsole console) | ||
| { | ||
| for (var i = 0; i < result.UnmatchedTokens.Count; i++) | ||
| var unmatchedTokens = result.UnmatchedTokens; |
There was a problem hiding this comment.
we need to store the result, because currently it's always allocating:
ofc I am going to propose a change to this API
| { | ||
| throw new ArgumentException($"Incorrect token type: {token}"); | ||
| } | ||
| Debug.Assert(token.Type == TokenType.Argument, $"Incorrect token type: {token}"); |
There was a problem hiding this comment.
these types are internal and we control the input, so there is no need to the throw condition. Debug.Assert will keep us safe (the CI runs all the tests in debug) with 0 cost for the Release build (less IL to compile, possibility to inline the method)
| /// <returns>A <see cref="ParseResult"/> providing details about the parse operation.</returns> | ||
| public ParseResult Parse( | ||
| IReadOnlyList<string> arguments, | ||
| IReadOnlyList<string>? arguments, |
There was a problem hiding this comment.
there is a test that verifies that the user can pass null here. So I made it more visible.
| var knownTokens = configuration.RootCommand.ValidTokens(); | ||
|
|
||
| for (var i = 0; i < argList.Count; i++) | ||
| int i = FirstArgumentIsRootCommand(args, configuration.RootCommand, inferRootCommand) |
There was a problem hiding this comment.
the NormalizeRootCommand was allocating a new list just to ensure that the list of arguments starts with the root command alias.
| argList.InsertRange(i + 1, newTokens ?? Array.Empty<string>()); | ||
| if (newTokens is not null && newTokens.Count > 0) | ||
| { | ||
| List<string> listWithReplacedTokens = args.ToList(); |
| var errorList = new List<string>(); | ||
| const int FirstArgIsNotRootCommand = -1; | ||
|
|
||
| List<string>? errorList = null; |
There was a problem hiding this comment.
usually there are no errors, so we delay the allocation until first error is found
… the parsing, we take advantage of its mutability and remove the root command token instead of creating a copy of the whole list.
…allocates a list)
… allocates a list)
…ntResult> duplicate the code rather than introducing new public type like IdentifierSymbolResult
| if (HasSubcommands) | ||
| { | ||
| AddCompletionsFor(commands[i]); | ||
| var commands = Subcommands; |
There was a problem hiding this comment.
For all the vars that don't comply with dotnet/runtime guidelines, are you planning on addressing them all at once in the future?
| var commands = Subcommands; | |
| IList<Command> commands = Subcommands; |
There was a problem hiding this comment.
We need to discuss it with other contributors. And the usage of is { } instead is not null which drives me crazy ;p
| /// </summary> | ||
| /// <remarks>If <see cref="CommandLineConfiguration.EnableDirectives"/> is set to <see langword="false"/>, then this collection will be empty.</remarks> | ||
| public IReadOnlyDictionary<string, IReadOnlyList<string>> Directives { get; } | ||
| public IReadOnlyDictionary<string, IReadOnlyList<string>> Directives => _directives ??= new (); |
There was a problem hiding this comment.
| public IReadOnlyDictionary<string, IReadOnlyList<string>> Directives => _directives ??= new (); | |
| public IReadOnlyDictionary<string, IReadOnlyList<string>> Directives => _directives ??= new(); |
There was a problem hiding this comment.
I think that this is a matter of personal preference. We should discuss it with others, choose one policy and enforce it with static code analysis tools.
There was a problem hiding this comment.
Is there an analyzer for that yet? I don't see one listed at https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/csharp-formatting-options#spacing-options. (There is IDE0090 "Simplify new expression" for target-typed new, though.)
There was a problem hiding this comment.
…ting a Union of the same hash set
While reading parsing logic I found plenty of places where we could remove some small allocations.