Skip to content

Commit

Permalink
Merge 47cb135 into 4565ef7
Browse files Browse the repository at this point in the history
  • Loading branch information
axunonb committed Aug 27, 2021
2 parents 4565ef7 + 47cb135 commit 2c362c7
Show file tree
Hide file tree
Showing 108 changed files with 1,233 additions and 238 deletions.
41 changes: 36 additions & 5 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,40 @@ v3.0.0-alpha.1

### Current changes merged into the `version/v3.0` branch:

#### Parser does not allocate any strings
#### Target frameworks

* Changed `netstandard2.0` to `netstandard2.1`.
* `net461` support unchanged.
* Added package `System.Memory`

#### Remove repetitive substring allocations

Connected modifications:

* Added method `Write(ReadOnlySpan<char> text)` to `IFormattingInfo`
* Generated substrings are cached in classes `Format`, `FormatItem`, `LiteralText`, `Placeholder` and `Selector`.
* Evaluating escaped characters for `Placeholder.FormatterOptions` and `LiteralText` work without heap memory allocation.

#### Alignment operator inheritance is optimized

* Alignment implementation introduced with PR [#174](https://github.com/axuno/SmartFormat/pull/174) is modified for better performance
* Added method `Placeholder.AddSelector`
* `Placeholder.Selectors` is now internal. Selectors are accessible with `IReadOnlyList<Selector> Placeholder.GetSelectors()`.

#### DictionarySource performance improved

* Implemented suggestion in issue [#186](https://github.com/axuno/SmartFormat/issues/186) for better speed and less GC pressure.
* Side effect: We're using the `CaseSensitivityType` of the dictionary for getting the value for a key. `Settings.GetCaseSensitivityComparison()` will not be applied.

#### Parser does not allocate any strings ([#187](https://github.com/axuno/SmartFormat/pull/187))

* Reducing GC pressure by avoiding temporary string assignments. Depending on the input string, GC is reduced by 50-80%.
* ParserSettings: All internal character lists are returned as ```List<char>```.
* ParserSettings: All internal character lists are returned as `List<char>`.
* Internal character lists are cached in the parser for better performance
* Connected modifications
* New performance tests for ```Parser```
* ```Placeholder``` property ```Placeholder?.Parent``` is renamed to ```Placeholder?ParentPlaceholder``` to avoid confusion with ```Format``` property ```Format?.Parent```.
* ```Placeholder``` has additional internal properties ```FormatterNameStartindex```, ```FormatterNameLength```, ```FormatterOptionsStartindex``` and ```FormatterOptionsLength```
* New performance tests for `Parser`
* `Placeholder` property `Placeholder?.Parent` is renamed to `Placeholder?ParentPlaceholder` to avoid confusion with `Format` property `Format?.Parent`.
* `Placeholder` has additional internal properties `FormatterNameStartindex`, `FormatterNameLength`, `FormatterOptionsStartindex` and `FormatterOptionsLength`

#### `IFormatter`s have one single, unique name ([#185](https://github.com/axuno/SmartFormat/pull/185))

Expand Down Expand Up @@ -50,6 +75,12 @@ public class FormatCache
The class `FormatCache` is now replaced by `Format`. `Format` stores the result from the `Parser` parsing the input string. The `SmartFormatter` has additional overloads for using the cached `Format` as an argument, instead of an additional wrapper around `Format`.

It is **highly recommended** to use these `SmartFormatter` methods whenever there is a **constant input string** with **different data arguments**.
Other changes:

* `SmartSettings` has a public CTOR.
* `SmartFormatter` has a CTOR which takes `SmartSettings` as an optional argument.
* `Smart.CreateDefaultSmartFormat()` takes `SmartSettings` as an optional argument.
* `Parser` has a CTOR which takes `SmartSettings` as an optional argument.

#### Bugfix for plural rule ([#182](https://github.com/axuno/SmartFormat/pull/182))
* Fixes #179 (DualFromZeroToTwo plural rule). Thanks to @OhSoGood
Expand Down
2 changes: 1 addition & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ for:
build_script:
- cd $APPVEYOR_BUILD_FOLDER/src
- dotnet --version
- dotnet sln remove ./SmartFormat.Demo/SmartFormat.Demo.csproj ./SmartFormat.Demo.NetFramework/SmartFormat.Demo.NetFramework.csproj
- dotnet sln remove ./Demo/Demo.csproj ./Demo.NetFramework/Demo.NetFramework.csproj
- dotnet restore --verbosity quiet
- dotnet add ./SmartFormat.Tests/SmartFormat.Tests.csproj package AltCover
- dotnet build SmartFormat.sln /verbosity:minimal /t:rebuild /p:configuration=release /nowarn:CS1591,CS0618
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ public void Write(string text, IFormattingInfo formattingInfo)
{
Write(text, 0, text.Length, formattingInfo);
}
public void Write(ReadOnlySpan<char> text, IFormattingInfo formattingInfo)
{
Write(text.ToString(), 0, text.Length, formattingInfo);
}

public void Write(string text, int startIndex, int length, IFormattingInfo formattingInfo)
{
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ public void Write(string text, IFormattingInfo formattingInfo)
Write(text, 0, text.Length, formattingInfo);
}

public void Write(ReadOnlySpan<char> text, IFormattingInfo formattingInfo)
{
Write(text.ToString(), 0, text.Length, formattingInfo);
}

public void Write(string text, int startIndex, int length, IFormattingInfo formattingInfo)
{
// Depending on the nested level, we will color this item differently:
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
141 changes: 141 additions & 0 deletions src/Performance/FormatTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using SmartFormat.Core.Output;
using SmartFormat.Core.Parsing;
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;

namespace SmartFormat.Performance
{
/*
BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.1165 (21H1/May2021Update)
AMD Ryzen 9 3900X, 1 CPU, 24 logical and 12 physical cores
.NET SDK=5.0.302
[Host] : .NET 5.0.8 (5.0.821.31504), X64 RyuJIT
.NET 5.0 : .NET 5.0.8 (5.0.821.31504), X64 RyuJIT
Job=.NET 5.0 Runtime=.NET 5.0
| Method | N | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|------------------------- |----- |-------------:|-----------:|-----------:|------:|--------:|---------:|------:|------:|----------:|
| Placeholder | 10 | 4.027 us | 0.0308 us | 0.0240 us | 3.98 | 0.04 | 0.6638 | - | - | 5 KB |
| Placeholder0005 | 10 | 18.131 us | 0.0702 us | 0.0656 us | 17.93 | 0.11 | 1.8311 | - | - | 15 KB |
| PlaceholderFormatter0002 | 10 | 9.211 us | 0.0916 us | 0.0812 us | 9.11 | 0.08 | 1.1749 | - | - | 10 KB |
| Literal0010Char | 10 | 1.011 us | 0.0062 us | 0.0058 us | 1.00 | 0.00 | 0.4482 | - | - | 4 KB |
| Literal3000Char | 10 | 67.525 us | 0.1731 us | 0.1619 us | 66.77 | 0.46 | 0.3662 | - | - | 4 KB |
| Literal3000Placeholder | 10 | 76.442 us | 0.1780 us | 0.1665 us | 75.58 | 0.49 | 0.6104 | - | - | 6 KB |
| | | | | | | | | | | |
| Placeholder | 1000 | 376.009 us | 2.9151 us | 2.7268 us | 3.77 | 0.03 | 66.8945 | - | - | 547 KB |
| Placeholder0005 | 1000 | 1,796.686 us | 9.3210 us | 8.7189 us | 18.03 | 0.12 | 183.5938 | - | - | 1,508 KB |
| PlaceholderFormatter0002 | 1000 | 865.751 us | 2.7868 us | 2.4704 us | 8.68 | 0.07 | 118.1641 | - | - | 969 KB |
| Literal0010Char | 1000 | 99.653 us | 0.6222 us | 0.5820 us | 1.00 | 0.00 | 44.9219 | - | - | 367 KB |
| Literal3000Char | 1000 | 6,757.903 us | 13.6353 us | 12.0874 us | 67.79 | 0.43 | 39.0625 | - | - | 367 KB |
| Literal3000Placeholder | 1000 | 7,097.778 us | 16.7876 us | 14.0184 us | 71.20 | 0.36 | 70.3125 | - | - | 586 KB |
*/

[SimpleJob(RuntimeMoniker.Net50)]
[MemoryDiagnoser]
// [RPlotExporter]
public class FormatTests
{
private const string LoremIpsum = "Us creeping good grass multiply seas under hath sixth fowl heaven days. Third. Deep abundantly all after also meat day. Likeness. Lesser saying meat sea in over likeness land. Meat own, made given stars. Form the. And his. So gathered fish god firmament, great seasons. Give sixth doesn't beast our fourth creature years isn't you you years moving, earth you every a male night replenish fruit. Set. Deep so, let void midst won't first. Second very after all god from night itself shall air had gathered firmament was cattle itself every great first. Let dry it unto. Creepeth don't rule fruit there creature second their whose seas without every man it darkness replenish made gathered you saying over set created. Midst meat light without bearing. Our him given his thing fowl blessed rule that evening let man beginning light forth tree she'd won't light. Moving evening shall have may beginning kind appear, also the kind living whose female hath void fifth saw isn't. Green you'll from. Grass fowl saying yielding heaven. I. Above which. Isn't i. They're moving. Can't cattle i. Gathering shall set darkness multiply second whales meat she'd form, multiply be meat deep bring forth land can't own she'd upon hath appear years let above, for days divided greater first was.\r\n\r\nPlace living all it Air you evening us don't fourth second them own which fish made. Subdue don't you'll the, the bearing said dominion in man have deep abundantly night she'd and place sixth the gathered lesser creeping subdue second fish multiply was created. Cattle wherein meat female fruitful set, earth them subdue seasons second, man forth over, be greater grass. Light unto, over bearing hath thing yielding be, spirit you'll given was set had let their abundantly you're beginning beginning divided replenish moved. Evening own heaven waters, their it of them cattle fruitful is, light after don't air fish multiply which moveth face the dominion fifth open, hath i evening from. Give from every waters two. That forth, bearing dry fly in may fish. Multiply Tree cattle.\r\n\r\nThing. Great saying good face gathered Forth over fowl moved Fourth upon form seasons over lights greater saw can't over saying beginning. Can't in moveth fly created subdue fourth. Them creature one moving living living thing Itself one after one darkness forth divided thing gathered earth there days seas fourth, stars herb. All from third dry have forth. Our third sea all. Male years you. Over fruitful they're. Have she'd their our image dry sixth void meat subdue face moved. Herb moved multiply tree, there likeness first won't there one dry it hath kind won't you seas of make day moving second thing were. Hath, had winged hath creature second had you. Upon. Appear image great place fourth the in, waters abundantly, deep hath void Him heaven divided heaven greater let so. Open replenish Wherein. Be created. The and was of. Signs cattle midst. Is she'd every saying bring there doesn't and. Rule. Stars green divide";

private Parser _parser = new (new SmartSettings());
private Format _placeholderFormat;
private Format _placeholder0005Format;
private Format _literal0010CharFormat;
private Format _literal3000CharFormat;
private Format _literal6000PlaceholderFormat;
private Format _literal18kEscPlaceholderFormat;
private SmartFormatter _literalFormatter;

public FormatTests()
{
_placeholderFormat = _parser.ParseFormat("{0}");
_placeholder0005Format = _parser.ParseFormat("{0}{1}{2}{3}{4}");
_literal0010CharFormat = _parser.ParseFormat("1234567890");
_literal3000CharFormat = _parser.ParseFormat(LoremIpsum);
_literal6000PlaceholderFormat = _parser.ParseFormat(LoremIpsum + "{0}" + LoremIpsum);
_literal18kEscPlaceholderFormat = _parser.ParseFormat(LoremIpsum + @"\n" + LoremIpsum + "{0}" + LoremIpsum);

_literalFormatter = new SmartFormatter();
_literalFormatter.AddExtensions(
new DefaultSource()
);
_literalFormatter.AddExtensions(
new DefaultFormatter()
);
}

public IOutput GetOutput(int capacity)
{
// Note: a good estimation of the expected output length is essential for performance and GC pressure
//return new NullOutput();
return new StringOutput(capacity);
}

[Params(100, 10000)]
public int N;

[GlobalSetup]
public void Setup()
{
}

[Benchmark]
public void Placeholder()
{
for (var i = 0; i < N; i++)
{
_literalFormatter.FormatInto(GetOutput(_placeholderFormat.Length + _placeholderFormat.Items.Count * 8), _placeholderFormat, "ph1");
}
}

[Benchmark]
public void Placeholder0005()
{
for (var i = 0; i < N; i++)
{
_literalFormatter.FormatInto(GetOutput(_placeholder0005Format.Length + _placeholder0005Format.Items.Count * 8), _placeholder0005Format, "ph1", "ph2", "ph3", "ph4", "ph5");
}
}

[Benchmark(Baseline = true)]
public void Literal0010Char()
{
for (var i = 0; i < N; i++)
{
_literalFormatter.FormatInto(GetOutput(_literal0010CharFormat.Length + _literal0010CharFormat.Items.Count * 8), _literal0010CharFormat);
}
}

[Benchmark]
public void Literal3000Char()
{
for (var i = 0; i < N; i++)
{
_literalFormatter.FormatInto(GetOutput(_literal3000CharFormat.Length + _literal3000CharFormat.Items.Count * 8), _literal3000CharFormat);
}
}

[Benchmark]
public void Literal6000Placeholder()
{
for (var i = 0; i < N; i++)
{
_literalFormatter.FormatInto(GetOutput(_literal6000PlaceholderFormat.Length + _literal6000PlaceholderFormat.Items.Count * 8), _literal6000PlaceholderFormat, "ph1");
}
}

[Benchmark]
public void Literal18kUniPlaceholder()
{
for (var i = 0; i < N; i++)
{
_literalFormatter.FormatInto(GetOutput(_literal18kEscPlaceholderFormat.Length + _literal18kEscPlaceholderFormat.Items.Count * 8), _literal6000PlaceholderFormat, "ph1");
}
}
}
}
File renamed without changes.
File renamed without changes.
21 changes: 21 additions & 0 deletions src/Performance/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Running;

namespace SmartFormat.Performance
{
public class Program
{
public static void Main()
{
//BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, new DebugInProcessConfig());
//BenchmarkRunner.Run<SourcePerformanceTests>();
BenchmarkRunner.Run<FormatTests>();
//BenchmarkRunner.Run<ParserTests>();

//BenchmarkRunner.Run<SimpleSpanParserTests>();
//BenchmarkRunner.Run<NullFormatterChooseFormatterTests>();
//BenchmarkRunner.Run<ReflectionVsStringSourceTests>();
}
}
}
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 2c362c7

Please sign in to comment.