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

Improve performance by more than 400X #350

Merged
merged 2 commits into from
Oct 19, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions src/CommandLine/Core/InstanceBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ public static ParserResult<T> Build<T>(
var typeInfo = factory.MapValueOrDefault(f => f().GetType(), typeof(T));

var specProps = typeInfo.GetSpecifications(pi => SpecificationProperty.Create(
Specification.FromProperty(pi), pi, Maybe.Nothing<object>()));
Specification.FromProperty(pi), pi, Maybe.Nothing<object>()))
.Memorize();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used .Memorize() throughout this PR since that seems to be the dominant existing convention.

However, it feels like an anti-pattern to me to pass IEnumerable everywhere rather than, say, IReadOnlyCollection. It makes it difficult to detect multi-enumeration issues visually or via static analysis such as Resharper. IEnumerable definitely has its place, it just feels overused.

What is the rationale behind the current approach?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the rationale behind the current approach?

IReadOnlyCollection was introduced in .NET 4.5. This library still targets 4.0 on the fat framework.

That, plus the developer was really inspired by functional languages and the "lazy evaluation" that's built in to many of them. Unfortunately, there are (perf) penalties for using C# as if it were a functional language.


var specs = from pt in specProps select pt.Specification;

var optionSpecs = specs
.ThrowingValidate(SpecificationGuards.Lookup)
.OfType<OptionSpecification>();
.OfType<OptionSpecification>()
.Memorize();

Func<T> makeDefault = () =>
typeof(T).IsMutable()
Expand All @@ -45,18 +47,19 @@ public static ParserResult<T> Build<T>(
Func<IEnumerable<Error>, ParserResult<T>> notParsed =
errs => new NotParsed<T>(makeDefault().GetType().ToTypeInfo(), errs);

var argumentsList = arguments.Memorize();
Func<ParserResult<T>> buildUp = () =>
{
var tokenizerResult = tokenizer(arguments, optionSpecs);
var tokenizerResult = tokenizer(argumentsList, optionSpecs);

var tokens = tokenizerResult.SucceededWith();
var tokens = tokenizerResult.SucceededWith().Memorize();

var partitions = TokenPartitioner.Partition(
tokens,
name => TypeLookup.FindTypeDescriptorAndSibling(name, optionSpecs, nameComparer));
var optionsPartition = partitions.Item1;
var valuesPartition = partitions.Item2;
var errorsPartition = partitions.Item3;
var optionsPartition = partitions.Item1.Memorize();
var valuesPartition = partitions.Item2.Memorize();
var errorsPartition = partitions.Item3.Memorize();

var optionSpecPropsResult =
OptionMapper.MapValues(
Expand All @@ -68,7 +71,7 @@ public static ParserResult<T> Build<T>(
var valueSpecPropsResult =
ValueMapper.MapValues(
(from pt in specProps where pt.Specification.IsValue() orderby ((ValueSpecification)pt.Specification).Index select pt),
valuesPartition,
valuesPartition,
(vals, type, isScalar) => TypeConverter.ChangeType(vals, type, isScalar, parsingCulture, ignoreValueCase));

var missingValueErrors = from token in errorsPartition
Expand All @@ -78,7 +81,7 @@ public static ParserResult<T> Build<T>(
.FromOptionSpecification());

var specPropsWithValue =
optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith());
optionSpecPropsResult.SucceededWith().Concat(valueSpecPropsResult.SucceededWith()).Memorize();

var setPropertyErrors = new List<Error>();

Expand Down Expand Up @@ -130,11 +133,13 @@ join sp in specPropsWithValue on prms.Name.ToLower() equals sp.Property.Name.ToL
return allErrors.Except(warnings).ToParserResult(instance);
};

var preprocessorErrors = arguments.Any()
? arguments.Preprocess(PreprocessorGuards.Lookup(nameComparer))
: Enumerable.Empty<Error>();
var preprocessorErrors = (
argumentsList.Any()
? argumentsList.Preprocess(PreprocessorGuards.Lookup(nameComparer))
: Enumerable.Empty<Error>()
).Memorize();

var result = arguments.Any()
var result = argumentsList.Any()
? preprocessorErrors.Any()
? notParsed(preprocessorErrors)
: buildUp()
Expand Down
2 changes: 1 addition & 1 deletion src/CommandLine/Core/OptionMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ select Tuple.Create(
((OptionSpecification)pt.Specification).FromOptionSpecification()))))
: Tuple.Create(pt, Maybe.Nothing<Error>());
}
);
).Memorize();
return Result.Succeed(
sequencesAndErrors.Select(se => se.Item1),
sequencesAndErrors.Select(se => se.Item2).OfType<Just<Error>>().Select(se => se.Value));
Expand Down
20 changes: 9 additions & 11 deletions src/CommandLine/Core/TokenPartitioner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,20 @@ namespace CommandLine.Core
static class TokenPartitioner
{
public static
Tuple<
IEnumerable<KeyValuePair<string, IEnumerable<string>>>, // options
IEnumerable<string>, // values
IEnumerable<Token> // errors
> Partition(
Tuple<IEnumerable<KeyValuePair<string, IEnumerable<string>>>, IEnumerable<string>, IEnumerable<Token>> Partition(
IEnumerable<Token> tokens,
Func<string, Maybe<TypeDescriptor>> typeLookup)
{
IEqualityComparer<Token> tokenComparer = ReferenceEqualityComparer.Default;

var tokenList = tokens.Memorize();
var switches = Switch.Partition(tokenList, typeLookup).Memorize();
var scalars = Scalar.Partition(tokenList, typeLookup).Memorize();
var sequences = Sequence.Partition(tokenList, typeLookup).Memorize();
var switches = new HashSet<Token>(Switch.Partition(tokenList, typeLookup), tokenComparer);
var scalars = new HashSet<Token>(Scalar.Partition(tokenList, typeLookup), tokenComparer);
var sequences = new HashSet<Token>(Sequence.Partition(tokenList, typeLookup), tokenComparer);
var nonOptions = tokenList
.Where(t => !switches.Contains(t, ReferenceEqualityComparer.Default))
.Where(t => !scalars.Contains(t, ReferenceEqualityComparer.Default))
.Where(t => !sequences.Contains(t, ReferenceEqualityComparer.Default)).Memorize();
.Where(t => !switches.Contains(t))
.Where(t => !scalars.Contains(t))
.Where(t => !sequences.Contains(t)).Memorize();
var values = nonOptions.Where(v => v.IsValue()).Memorize();
var errors = nonOptions.Except(values, (IEqualityComparer<Token>)ReferenceEqualityComparer.Default).Memorize();

Expand Down
6 changes: 3 additions & 3 deletions src/CommandLine/Core/Tokenizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public static Result<IEnumerable<Token>, Error> Tokenize(
select token)
.Memorize();

var normalized = normalize(tokens);
var normalized = normalize(tokens).Memorize();

var unkTokens = (from t in normalized where t.IsName() && nameLookup(t.Text) == NameLookupResult.NoOptionFound select t).Memorize();

Expand All @@ -60,12 +60,12 @@ public static Result<IEnumerable<Token>, Error> ExplodeOptionList(
Result<IEnumerable<Token>, Error> tokenizerResult,
Func<string, Maybe<char>> optionSequenceWithSeparatorLookup)
{
var tokens = tokenizerResult.SucceededWith();
var tokens = tokenizerResult.SucceededWith().Memorize();

var replaces = tokens.Select((t, i) =>
optionSequenceWithSeparatorLookup(t.Text)
.MapValueOrDefault(sep => Tuple.Create(i + 1, sep),
Tuple.Create(-1, '\0'))).SkipWhile(x => x.Item1 < 0);
Tuple.Create(-1, '\0'))).SkipWhile(x => x.Item1 < 0).Memorize();

var exploded = tokens.Select((t, i) =>
replaces.FirstOrDefault(x => x.Item1 == i).ToMaybe()
Expand Down