Skip to content

Commit 56b1505

Browse files
authored
Perf improvements part 3 (#1510)
* avoid a string allocation and don't store the UnprefixedValue * ensure Token is implementing IEquatable<Token> interface correctly * make Token a struct to reduce managed memory allocations * always print allocated memory in bytes (to avoid precision when they are rounded to kb) * make ArgumentArity a struct * don't create a copy of RootCommand symbols * don't store an additional copy of aliases in AliasedSet * optimize IsGreedy * avoid casting performedd by ResetIndex if there is nothing to reset * replace TryAdd (calls Contains and Add) with overriding * delay Validators list allocaiton until we actually need it * extend Token with Symbol to avoid mapping name to Symbol when parsing Commands and Options remove WasBundled as it was not used anywhere * remove Obsolete methods that were calling GetByAlias * remove SymbolResultSet, just use List<SymbolResult> instead remove AliasedSet, don't verify aliases at model creation time remove ContainsAlias and GetByAlias as they are not needed since Token got extended with Symbol * both Command and Option derive from IdentifierSymbol, so there is no need to duplicate the logic around aliases * optimize DefaultName * optimize unbundling * don't try to add children commands more than once * RootCommands should not be nested? * don't copy all global options when creating CommandLineConfiguration * implement Command.Validate to allow the users to validate their commands when they need to * don't duplicate GlobalOptions, just treat them as special Options * simplify the Parent hierarchy * address code review feedback: * make Matches internal * make IsGlobal internal * make SymbolResult.Children readonly * add comment about complexity and perf * move Command.Validate to CommandLineConfiguration, delete the tests for now
1 parent 17b1f03 commit 56b1505

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+589
-1017
lines changed

src/System.CommandLine.ApiCompatibility.Tests/ApiCompatibilityApprovalTests.System_CommandLine_api_is_not_changed.approved.txt

Lines changed: 29 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
public class Argument : Symbol, System.CommandLine.Binding.IValueDescriptor, System.CommandLine.Completions.ICompletionSource, IArgument, ISymbol
33
.ctor()
44
.ctor(System.String name = null, System.String description = null)
5-
public IArgumentArity Arity { get; set; }
5+
public ArgumentArity Arity { get; set; }
66
public CompletionSourceList Completions { get; }
77
public System.Boolean HasDefaultValue { get; }
88
public System.String HelpName { get; set; }
@@ -22,15 +22,18 @@
2222
.ctor(System.String name, ParseArgument<T> parse, System.Boolean isDefault = False, System.String description = null)
2323
.ctor(ParseArgument<T> parse, System.Boolean isDefault = False)
2424
public System.Type ValueType { get; set; }
25-
public class ArgumentArity, IArgumentArity
26-
public static IArgumentArity ExactlyOne { get; }
27-
public static IArgumentArity OneOrMore { get; }
28-
public static IArgumentArity Zero { get; }
29-
public static IArgumentArity ZeroOrMore { get; }
30-
public static IArgumentArity ZeroOrOne { get; }
25+
public struct ArgumentArity : System.ValueType, System.IEquatable<ArgumentArity>
26+
public static ArgumentArity ExactlyOne { get; }
27+
public static ArgumentArity OneOrMore { get; }
28+
public static ArgumentArity Zero { get; }
29+
public static ArgumentArity ZeroOrMore { get; }
30+
public static ArgumentArity ZeroOrOne { get; }
3131
.ctor(System.Int32 minimumNumberOfValues, System.Int32 maximumNumberOfValues)
3232
public System.Int32 MaximumNumberOfValues { get; }
3333
public System.Int32 MinimumNumberOfValues { get; }
34+
public System.Boolean Equals(ArgumentArity other)
35+
public System.Boolean Equals(System.Object obj)
36+
public System.Int32 GetHashCode()
3437
public static class ArgumentExtensions
3538
public static TArgument AddCompletions<TArgument>(this TArgument argument, System.String[] values)
3639
public static TArgument AddCompletions<TArgument>(this TArgument argument, System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.String>> complete)
@@ -47,13 +50,11 @@
4750
public class Command : IdentifierSymbol, System.Collections.Generic.IEnumerable<Symbol>, System.Collections.IEnumerable, System.CommandLine.Completions.ICompletionSource, ICommand, IIdentifierSymbol, ISymbol
4851
.ctor(System.String name, System.String description = null)
4952
public System.Collections.Generic.IReadOnlyList<Argument> Arguments { get; }
50-
public System.Collections.Generic.IReadOnlyList<Option> GlobalOptions { get; }
5153
public System.CommandLine.Invocation.ICommandHandler Handler { get; set; }
5254
public System.Collections.Generic.IReadOnlyList<Option> Options { get; }
5355
public System.Boolean TreatUnmatchedTokensAsErrors { get; set; }
5456
public System.Void Add(Symbol symbol)
5557
public System.Void Add(Argument argument)
56-
public System.Void AddAlias(System.String alias)
5758
public System.Void AddArgument(Argument argument)
5859
public System.Void AddCommand(Command command)
5960
public System.Void AddGlobalOption(Option option)
@@ -74,7 +75,7 @@
7475
public System.Boolean EnablePosixBundling { get; }
7576
public LocalizationResources LocalizationResources { get; }
7677
public ICommand RootCommand { get; }
77-
public System.CommandLine.Collections.ISymbolSet Symbols { get; }
78+
public System.CommandLine.Collections.SymbolSet Symbols { get; }
7879
public static class CompletionSourceExtensions
7980
public static System.Void Add(this CompletionSourceList completionSources, System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.String>> complete)
8081
public static System.Void Add(this CompletionSourceList completionSources, System.CommandLine.Completions.CompletionDelegate complete)
@@ -125,10 +126,7 @@
125126
public static System.Void SetHandler<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>(this Command command, Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,System.Threading.Tasks.Task> handle, System.CommandLine.Binding.IValueDescriptor[] symbols)
126127
public static System.Void SetHandler<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>(this Command command, Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,System.Threading.Tasks.Task> handle, System.CommandLine.Binding.IValueDescriptor[] symbols)
127128
public interface IArgument : System.CommandLine.Binding.IValueDescriptor, System.CommandLine.Completions.ICompletionSource, ISymbol
128-
public IArgumentArity Arity { get; }
129-
public interface IArgumentArity
130-
public System.Int32 MaximumNumberOfValues { get; }
131-
public System.Int32 MinimumNumberOfValues { get; }
129+
public ArgumentArity Arity { get; }
132130
public interface ICommand : System.CommandLine.Completions.ICompletionSource, IIdentifierSymbol, ISymbol
133131
public System.Collections.Generic.IReadOnlyList<IArgument> Arguments { get; }
134132
public System.Collections.Generic.IReadOnlyList<IOption> Options { get; }
@@ -137,6 +135,7 @@
137135
public abstract class IdentifierSymbol : Symbol, System.CommandLine.Completions.ICompletionSource, IIdentifierSymbol, ISymbol
138136
public System.Collections.Generic.IReadOnlyCollection<System.String> Aliases { get; }
139137
public System.String Name { get; set; }
138+
public System.Void AddAlias(System.String alias)
140139
public System.Boolean HasAlias(System.String alias)
141140
public interface IDirectiveCollection : System.Collections.Generic.IEnumerable<System.Collections.Generic.KeyValuePair<System.String,System.Collections.Generic.IEnumerable<System.String>>>, System.Collections.IEnumerable
142141
public System.Boolean Contains(System.String name)
@@ -149,11 +148,11 @@
149148
public IArgument Argument { get; }
150149
public System.Boolean IsRequired { get; }
151150
public interface ISymbol : System.CommandLine.Completions.ICompletionSource
152-
public System.CommandLine.Collections.ISymbolSet Children { get; }
151+
public System.CommandLine.Collections.SymbolSet Children { get; }
153152
public System.String Description { get; }
154153
public System.Boolean IsHidden { get; }
155154
public System.String Name { get; }
156-
public System.CommandLine.Collections.ISymbolSet Parents { get; }
155+
public System.Collections.Generic.IEnumerable<Symbol> Parents { get; }
157156
public class LocalizationResources
158157
public static LocalizationResources Instance { get; }
159158
public System.String ArgumentConversionCannotParse(System.String value, System.Type expectedType)
@@ -195,14 +194,13 @@
195194
public System.String VersionOptionCannotBeCombinedWithOtherArguments(System.String optionAlias)
196195
public System.String VersionOptionDescription()
197196
public class Option : IdentifierSymbol, System.CommandLine.Binding.IValueDescriptor, System.CommandLine.Completions.ICompletionSource, IIdentifierSymbol, IOption, ISymbol
198-
.ctor(System.String name, System.String description = null, System.Type argumentType = null, System.Func<System.Object> getDefaultValue = null, IArgumentArity arity = null)
199-
.ctor(System.String[] aliases, System.String description = null, System.Type argumentType = null, System.Func<System.Object> getDefaultValue = null, IArgumentArity arity = null)
197+
.ctor(System.String name, System.String description = null, System.Type argumentType = null, System.Func<System.Object> getDefaultValue = null, ArgumentArity arity = null)
198+
.ctor(System.String[] aliases, System.String description = null, System.Type argumentType = null, System.Func<System.Object> getDefaultValue = null, ArgumentArity arity = null)
200199
public System.Boolean AllowMultipleArgumentsPerToken { get; set; }
201200
public System.String ArgumentHelpName { get; set; }
202-
public IArgumentArity Arity { get; set; }
201+
public ArgumentArity Arity { get; set; }
203202
public System.Boolean IsRequired { get; set; }
204203
public System.Type ValueType { get; }
205-
public System.Void AddAlias(System.String alias)
206204
public System.Void AddValidator(System.CommandLine.Parsing.ValidateSymbolResult<System.CommandLine.Parsing.OptionResult> validate)
207205
public System.Boolean HasAliasIgnoringPrefix(System.String alias)
208206
public System.Void SetDefaultValue(System.Object value)
@@ -214,7 +212,7 @@
214212
.ctor(System.String[] aliases, ParseArgument<T> parseArgument, System.Boolean isDefault = False, System.String description = null)
215213
.ctor(System.String name, Func<T> getDefaultValue, System.String description = null)
216214
.ctor(System.String[] aliases, Func<T> getDefaultValue, System.String description = null)
217-
public IArgumentArity Arity { get; set; }
215+
public ArgumentArity Arity { get; set; }
218216
public static class OptionExtensions
219217
public static TOption AddCompletions<TOption>(this TOption option, System.String[] values)
220218
public static TOption AddCompletions<TOption>(this TOption option, System.Func<System.CommandLine.Completions.CompletionContext,System.Collections.Generic.IEnumerable<System.String>> complete)
@@ -237,7 +235,7 @@
237235
public System.String Description { get; set; }
238236
public System.Boolean IsHidden { get; set; }
239237
public System.String Name { get; set; }
240-
public System.CommandLine.Collections.ISymbolSet Parents { get; }
238+
public System.Collections.Generic.IEnumerable<Symbol> Parents { get; }
241239
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions()
242240
public System.Collections.Generic.IEnumerable<System.CommandLine.Completions.CompletionItem> GetCompletions(System.CommandLine.Completions.CompletionContext context)
243241
public System.String ToString()
@@ -302,18 +300,11 @@ System.CommandLine.Builder
302300
public static CommandLineBuilder UseVersionOption(this CommandLineBuilder builder)
303301
public static CommandLineBuilder UseVersionOption(this CommandLineBuilder builder, System.String[] aliases)
304302
System.CommandLine.Collections
305-
public abstract class AliasedSet<T>, IReadOnlyList<T>, IReadOnlyCollection<T>, IEnumerable<T>, System.Collections.IEnumerable
306-
public System.Int32 Count { get; }
307-
public T Item { get; }
308-
public System.Boolean ContainsAlias(System.String alias)
309-
protected System.Collections.Generic.IReadOnlyCollection<System.String> GetAliases(T item)
310-
public T GetByAlias(System.String alias)
311-
public IEnumerator<T> GetEnumerator()
312-
public interface ISymbolSet : System.Collections.Generic.IEnumerable<System.CommandLine.ISymbol>, System.Collections.Generic.IReadOnlyCollection<System.CommandLine.ISymbol>, System.Collections.Generic.IReadOnlyList<System.CommandLine.ISymbol>, System.Collections.IEnumerable
313-
public System.CommandLine.ISymbol GetByAlias(System.String alias)
314-
public class SymbolSet : AliasedSet<System.CommandLine.ISymbol>, System.Collections.Generic.IEnumerable<System.CommandLine.ISymbol>, System.Collections.Generic.IReadOnlyCollection<System.CommandLine.ISymbol>, System.Collections.Generic.IReadOnlyList<System.CommandLine.ISymbol>, System.Collections.IEnumerable, ISymbolSet
303+
public class SymbolSet, System.Collections.Generic.IEnumerable<System.CommandLine.Symbol>, System.Collections.IEnumerable
315304
.ctor()
316-
protected System.Collections.Generic.IReadOnlyCollection<System.String> GetAliases(System.CommandLine.ISymbol item)
305+
public System.Int32 Count { get; }
306+
public System.CommandLine.Symbol Item { get; }
307+
public System.Collections.Generic.IEnumerator<System.CommandLine.Symbol> GetEnumerator()
317308
System.CommandLine.Completions
318309
public abstract class CompletionContext
319310
public System.CommandLine.Parsing.ParseResult ParseResult { get; }
@@ -528,7 +519,7 @@ System.CommandLine.Parsing
528519
ParseArgsAsSpaceSeparated=1
529520
Disabled=2
530521
public abstract class SymbolResult
531-
public SymbolResultSet Children { get; }
522+
public System.Collections.Generic.IReadOnlyList<SymbolResult> Children { get; }
532523
public System.String ErrorMessage { get; set; }
533524
public System.CommandLine.LocalizationResources LocalizationResources { get; set; }
534525
public SymbolResult Parent { get; }
@@ -538,16 +529,15 @@ System.CommandLine.Parsing
538529
public CommandResult FindResultFor(System.CommandLine.ICommand command)
539530
public OptionResult FindResultFor(System.CommandLine.IOption option)
540531
public System.String ToString()
541-
public class SymbolResultSet : System.CommandLine.Collections.AliasedSet<SymbolResult>, System.Collections.Generic.IEnumerable<SymbolResult>, System.Collections.Generic.IReadOnlyCollection<SymbolResult>, System.Collections.Generic.IReadOnlyList<SymbolResult>, System.Collections.IEnumerable
542-
.ctor()
543-
protected System.Collections.Generic.IReadOnlyCollection<System.String> GetAliases(SymbolResult result)
544-
public class Token
532+
public struct Token : System.ValueType, System.IEquatable<Token>
545533
public static System.Boolean op_Equality(Token left, Token right)
546534
public static System.Boolean op_Inequality(Token left, Token right)
547-
.ctor(System.String value, TokenType type)
535+
.ctor(System.String value, TokenType type, System.CommandLine.ISymbol symbol)
536+
public System.CommandLine.ISymbol Symbol { get; }
548537
public TokenType Type { get; }
549538
public System.String Value { get; }
550539
public System.Boolean Equals(System.Object obj)
540+
public System.Boolean Equals(Token other)
551541
public System.Int32 GetHashCode()
552542
public System.String ToString()
553543
public class TokenizeError

src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_Bare.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class Perf_Parser_Options_Bare
1919
private string _testSymbolsAsString;
2020
private Parser _testParser;
2121

22-
private IEnumerable<Option> GenerateTestOptions(int count, IArgumentArity arity)
22+
private IEnumerable<Option> GenerateTestOptions(int count, ArgumentArity arity)
2323
=> Enumerable.Range(0, count)
2424
.Select(i =>
2525
new Option($"-option{i}", arity: arity)

src/System.CommandLine.Benchmarks/CommandLine/Perf_Parser_Options_With_Arguments.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public class Perf_Parser_Options_With_Arguments
1818
private string _testSymbolsAsString;
1919
private Parser _testParser;
2020

21-
private IEnumerable<Option> GenerateTestOptions(int count, IArgumentArity arity)
21+
private IEnumerable<Option> GenerateTestOptions(int count, ArgumentArity arity)
2222
=> Enumerable.Range(0, count)
2323
.Select(i => new Option($"-option{i}", arity: arity)
2424
{

src/System.CommandLine.Benchmarks/RecommendedConfig.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using BenchmarkDotNet.Diagnosers;
99
using BenchmarkDotNet.Exporters.Json;
1010
using BenchmarkDotNet.Jobs;
11+
using BenchmarkDotNet.Reports;
1112
using Perfolizer.Horology;
1213

1314
namespace System.CommandLine.Benchmarks
@@ -28,7 +29,8 @@ public static IConfig Create(DirectoryInfo artifactsPath, ImmutableHashSet<strin
2829
.WithArtifactsPath(artifactsPath.FullName)
2930
.With(MemoryDiagnoser.Default)
3031
.With(JsonExporter.Full)
31-
.With(StatisticColumn.Median, StatisticColumn.Min, StatisticColumn.Max);
32+
.With(StatisticColumn.Median, StatisticColumn.Min, StatisticColumn.Max)
33+
.WithSummaryStyle(SummaryStyle.Default.WithSizeUnit(SizeUnit.B));
3234
#pragma warning restore CA1062 // Validate arguments of public methods
3335
}
3436
}

src/System.CommandLine.Tests/CommandTests.cs

Lines changed: 0 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -263,48 +263,6 @@ public void It_retains_argument_name_when_it_is_provided()
263263

264264
command.Arguments.Single().Name.Should().Be("arg");
265265
}
266-
267-
[Fact]
268-
public void When_multiple_arguments_are_configured_then_they_must_differ_by_name()
269-
{
270-
var command = new Command("the-command")
271-
{
272-
new Argument<string>
273-
{
274-
Name = "same"
275-
}
276-
};
277-
278-
command
279-
.Invoking(c => c.Add(new Argument<string>
280-
{
281-
Name = "same"
282-
}))
283-
.Should()
284-
.Throw<ArgumentException>()
285-
.And
286-
.Message
287-
.Should()
288-
.Be("Alias 'same' is already in use.");
289-
}
290-
291-
[Fact]
292-
public void When_multiple_options_are_configured_then_they_must_differ_by_name()
293-
{
294-
var command = new Command("the-command")
295-
{
296-
new Option("--same")
297-
};
298-
299-
command
300-
.Invoking(c => c.Add(new Option("--same")))
301-
.Should()
302-
.Throw<ArgumentException>()
303-
.And
304-
.Message
305-
.Should()
306-
.Be("Alias '--same' is already in use.");
307-
}
308266

309267
[Fact]
310268
public void When_Name_is_set_to_its_current_value_then_it_is_not_removed_from_aliases()
@@ -467,10 +425,6 @@ public void AddGlobalOption_updates_Options_and_GlobalOptions_property()
467425
var command = new Command("mycommand");
468426
command.AddGlobalOption(option);
469427

470-
command.GlobalOptions
471-
.Should()
472-
.Contain(option);
473-
474428
command.Options
475429
.Should()
476430
.Contain(option);
@@ -491,16 +445,11 @@ public void When_Options_is_referenced_before_a_global_option_is_added_then_addi
491445

492446
command.AddGlobalOption(option);
493447

494-
command.GlobalOptions
495-
.Should()
496-
.Contain(option);
497-
498448
command.Options
499449
.Should()
500450
.Contain(option);
501451
}
502452

503-
504453
protected override Symbol CreateSymbol(string name) => new Command(name);
505454
}
506455
}

src/System.CommandLine.Tests/GlobalOptionTests.cs

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,40 +25,6 @@ public void Global_options_may_be_added_with_aliases_that_conflict_with_local_op
2525
.NotThrow<ArgumentException>();
2626
}
2727

28-
[Fact]
29-
public void Global_options_may_not_have_aliases_conflicting_with_other_global_option_aliases()
30-
{
31-
var command = new Command("the-command");
32-
33-
command.AddGlobalOption(new Option("--same"));
34-
35-
command
36-
.Invoking(c => c.AddGlobalOption(new Option("--same")))
37-
.Should()
38-
.Throw<ArgumentException>()
39-
.Which
40-
.Message
41-
.Should()
42-
.Be("Alias '--same' is already in use.");
43-
}
44-
45-
[Fact]
46-
public void When_local_options_are_added_then_they_must_differ_from_global_options_by_name()
47-
{
48-
var command = new Command("the-command");
49-
50-
command.AddGlobalOption(new Option("--same"));
51-
52-
command
53-
.Invoking(c => c.Add(new Option("--same")))
54-
.Should()
55-
.Throw<ArgumentException>()
56-
.And
57-
.Message
58-
.Should()
59-
.Be("Alias '--same' is already in use.");
60-
}
61-
6228
[Fact]
6329
public void Global_options_appear_in_options_list()
6430
{

src/System.CommandLine.Tests/ParseDirectiveTests.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,7 @@ public async Task When_there_are_errors_then_parse_directive_sets_exit_code_to_c
8181
new Option<int>("-x")
8282
};
8383

84-
int exitCode = await new CommandLineBuilder(new RootCommand
85-
{
86-
command
87-
})
84+
int exitCode = await new CommandLineBuilder(command)
8885
.UseParseDirective(errorExitCode: 42)
8986
.Build()
9087
.InvokeAsync("[parse] -x not-an-int");

src/System.CommandLine.Tests/ParseResultTests.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,5 @@ public void Command_will_not_accept_a_command_if_a_sibling_command_has_already_b
8383
result2.CommandResult.Symbol.Name.Should().Be("inner-two");
8484
result2.Errors.Count.Should().Be(1);
8585
}
86-
87-
[Fact]
88-
public void ValueForOption_throws_with_empty_alias()
89-
{
90-
var command = new Command("one");
91-
92-
var result = command.Parse("");
93-
94-
Action action = () =>
95-
{
96-
result.ValueForOption<string>(string.Empty);
97-
};
98-
99-
action.Should().Throw<ArgumentException>("Value cannot be null or whitespace.");
100-
}
10186
}
10287
}

0 commit comments

Comments
 (0)