Skip to content

Commit 77c0499

Browse files
authored
SepWriter.Col: Replace StringBuilder with ArrayPool array and DefaultInterpolatedStringHandler (#216)
Use UnsafeAccessor for DefaultInterpolatedStringHandler to let this handle array from ArrayPool incl. growing size (much simpler)
1 parent accd807 commit 77c0499

File tree

9 files changed

+205
-97
lines changed

9 files changed

+205
-97
lines changed

.github/workflows/dotnet.yml

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ jobs:
3535
uses: actions/setup-dotnet@v4
3636
with:
3737
dotnet-version: |
38-
7.0.x
3938
8.0.x
4039
- name: Setup .NET (global.json)
4140
uses: actions/setup-dotnet@v4
@@ -78,7 +77,6 @@ jobs:
7877
strategy:
7978
matrix:
8079
os: [ubuntu-latest, windows-latest, macos-latest]
81-
configuration: [Debug, Release]
8280

8381
runs-on: ${{ matrix.os }}
8482

@@ -88,16 +86,13 @@ jobs:
8886
uses: actions/setup-dotnet@v4
8987
with:
9088
dotnet-version: |
91-
7.0.x
9289
8.0.x
9390
- name: Setup .NET (global.json)
9491
uses: actions/setup-dotnet@v4
9592
with:
9693
global-json-file: global.json
9794
- name: Restore dependencies
9895
run: dotnet restore
99-
- name: Build
100-
run: dotnet build -c ${{ matrix.configuration }} --no-restore
10196
- name: Test Parsers
10297
shell: pwsh
10398
run: ./test-parsers.ps1
@@ -116,7 +111,6 @@ jobs:
116111
uses: actions/setup-dotnet@v4
117112
with:
118113
dotnet-version: |
119-
7.0.x
120114
8.0.x
121115
- name: Setup .NET (global.json)
122116
uses: actions/setup-dotnet@v4

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2130,8 +2130,12 @@ namespace nietras.SeparatedValues
21302130
public void Set(System.IFormatProvider? provider, [System.Runtime.CompilerServices.InterpolatedStringHandlerArgument(new string?[]?[] {
21312131
"",
21322132
"provider"})] ref nietras.SeparatedValues.SepWriter.Col.FormatInterpolatedStringHandler handler) { }
2133+
[System.Obsolete(("Types with embedded references are not supported in this version of your compiler" +
2134+
"."), true)]
2135+
[System.Runtime.CompilerServices.CompilerFeatureRequired("RefStructs")]
21332136
[System.Runtime.CompilerServices.InterpolatedStringHandler]
2134-
public readonly struct FormatInterpolatedStringHandler
2137+
[System.Runtime.CompilerServices.IsByRefLike]
2138+
public struct FormatInterpolatedStringHandler
21352139
{
21362140
public FormatInterpolatedStringHandler(int literalLength, int formattedCount, nietras.SeparatedValues.SepWriter.Col col) { }
21372141
public FormatInterpolatedStringHandler(int literalLength, int formattedCount, nietras.SeparatedValues.SepWriter.Col col, System.IFormatProvider? provider) { }

src/Sep.Benchmarks/Program.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Collections.Generic;
55
using System.Diagnostics;
66
using System.Linq;
7+
using System.Reflection;
78
using System.Threading;
89
using BenchmarkDotNet.Columns;
910
using BenchmarkDotNet.Configs;
@@ -22,16 +23,16 @@
2223
{
2324
var config = (Debugger.IsAttached ? new DebugInProcessConfig() : DefaultConfig.Instance)
2425
.WithSummaryStyle(SummaryStyle.Default.WithMaxParameterColumnWidth(200))
25-
.AddColumn(MBPerSecFromCharsLength())
26+
//.AddColumn(MBPerSecFromCharsLength())
2627
;
27-
//BenchmarkSwitcher.FromAssembly(Assembly.GetExecutingAssembly()).Run(args, config);
28+
BenchmarkSwitcher.FromAssembly(Assembly.GetExecutingAssembly()).Run(args, config);
2829
//BenchmarkRunner.Run(typeof(SepReaderBench), config, args);
2930
//BenchmarkRunner.Run(typeof(SepWriterBench), config, args);
3031
//BenchmarkRunner.Run(typeof(SepReaderWriterBench), config, args);
3132
//BenchmarkRunner.Run(typeof(SepEndToEndBench), config, args);
3233
//BenchmarkRunner.Run(typeof(SepHashBench), config, args);
3334
//BenchmarkRunner.Run(typeof(SepParseSeparatorsMaskBench), config, args);
34-
BenchmarkRunner.Run(typeof(SepParserBench), config, args);
35+
//BenchmarkRunner.Run(typeof(SepParserBench), config, args);
3536
//BenchmarkRunner.Run(typeof(StopwatchBench), config, args);
3637
}
3738
else
@@ -46,8 +47,10 @@
4647
}
4748
}
4849

50+
#pragma warning disable CS8321 // Local function is declared but never used
4951
static IColumn MBPerSecFromCharsLength() => new BytesStatisticColumn("MB/s",
5052
BytesFromCharsLength, BytesStatisticColumn.FormatMBPerSec);
53+
#pragma warning restore CS8321 // Local function is declared but never used
5154

5255
static long BytesFromCharsLength(IReadOnlyList<ParameterInstance> parameters)
5356
{

src/Sep.Benchmarks/SepWriterBench.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ public class SepWriterBench
1111
[IterationSetup]
1212
public void Setup()
1313
{
14-
_writer = Sep.Writer().ToText(256 * 1024 * 1024);
14+
_writer = Sep.Writer(o => o with { WriteHeader = false }).ToText(256 * 1024 * 1024);
1515
}
1616

1717
[IterationCleanup]
@@ -36,4 +36,14 @@ public void SetByColName()
3636
writeRow["C"].Set("cccccccccccccccccccccccc");
3737
writeRow["D"].Set("ddddddddddddddddddddddddddddddd");
3838
}
39+
40+
[Benchmark]
41+
public void SetByColIndex()
42+
{
43+
using var writeRow = _writer!.NewRow();
44+
writeRow[0].Set("aaaaaaaaaaaaaaaaaaa");
45+
writeRow[1].Set("bbbbbbbbbbbbbbbbbbbbbb");
46+
writeRow[2].Set("cccccccccccccccccccccccc");
47+
writeRow[3].Set("ddddddddddddddddddddddddddddddd");
48+
}
3949
}

src/Sep.Test/Internals/InterpolatedStringHandlerTest.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@ namespace nietras.SeparatedValues.Test.Internals;
1010
[TestClass]
1111
public class InterpolatedStringHandlerTest
1212
{
13+
#if NET8_0_OR_GREATER
14+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
15+
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_pos")]
16+
static extern ref int Position(ref DefaultInterpolatedStringHandler handler);
17+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
18+
[UnsafeAccessor(UnsafeAccessorKind.Field, Name = "_arrayToReturnToPool")]
19+
static extern ref char[]? ArrayToReturnToPool(ref DefaultInterpolatedStringHandler handler);
20+
21+
[TestMethod]
22+
public void InterpolatedStringHandlerTest_DefaultInterpolatedStringHandler_Accessor()
23+
{
24+
var buffer = new char[16];
25+
var handler = new DefaultInterpolatedStringHandler(literalLength: 10, formattedCount: 2, provider: null, buffer);
26+
ref var position = ref Position(ref handler);
27+
position = 2;
28+
handler.AppendFormatted(42);
29+
var text = handler.ToStringAndClear();
30+
Assert.IsNotNull(text);
31+
}
32+
#endif
33+
1334
[TestMethod]
1435
public void InterpolatedStringHandlerTest_Log()
1536
{

src/Sep.Test/SepWriterColTest.cs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public class SepWriterColTest
1010
const string ColName = "A";
1111
const int ColValue = 123456;
1212
const string ColText = "123456";
13+
static readonly string ColTextLong = new('a', 2048);
1314

1415
static readonly string NL = Environment.NewLine;
1516

@@ -31,12 +32,24 @@ public void SepWriterColTest_Set_String()
3132
Run(col => col.Set(ColText));
3233
}
3334

35+
[TestMethod]
36+
public void SepWriterColTest_Set_String_Long()
37+
{
38+
Run(col => col.Set(ColTextLong), ColTextLong);
39+
}
40+
3441
[TestMethod]
3542
public void SepWriterColTest_Set_Span()
3643
{
3744
Run(col => col.Set(ColText.AsSpan()));
3845
}
3946

47+
[TestMethod]
48+
public void SepWriterColTest_Set_Span_Long()
49+
{
50+
Run(col => col.Set(ColTextLong.AsSpan()), ColTextLong);
51+
}
52+
4053
[TestMethod]
4154
public void SepWriterColTest_Set_InterpolatedString()
4255
{
@@ -61,6 +74,18 @@ public void SepWriterColTest_Set_InterpolatedString_F2_CultureInfoAsParam()
6174
Run(col => col.Set(CultureInfo.GetCultureInfo("da-DK"), $"{ColValue:F2}"), ColText + ",00");
6275
}
6376

77+
[TestMethod]
78+
public void SepWriterColTest_Set_InterpolatedString_F2_CultureInfoAsConfig_Null()
79+
{
80+
Run(col => col.Set($"{ColValue:F2}"), ColText + ".00", null);
81+
}
82+
83+
[TestMethod]
84+
public void SepWriterColTest_Set_InterpolatedString_F2_CultureInfoAsParam_Null()
85+
{
86+
Run(col => col.Set(provider: null, $"{ColValue:F2}"), ColText + ".00");
87+
}
88+
6489
[TestMethod]
6590
public void SepWriterColTest_Set_InterpolatedString_AppendLiteral()
6691
{
@@ -111,6 +136,26 @@ public void SepWriterColTest_Format()
111136
Run(col => col.Format(ColValue));
112137
}
113138

139+
[TestMethod]
140+
public void SepWriterColTest_Format_Long()
141+
{
142+
var f = new LongSpanFormattable();
143+
Run(col => col.Format(f), f.Text);
144+
}
145+
146+
public class LongSpanFormattable : ISpanFormattable
147+
{
148+
public string Text { get; } = ColTextLong;
149+
150+
public string ToString(string? format, IFormatProvider? formatProvider) => Text;
151+
152+
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider)
153+
{
154+
charsWritten = Text.Length;
155+
return Text.TryCopyTo(destination);
156+
}
157+
}
158+
114159
// No escaping needed
115160
[DataRow("", "")]
116161
[DataRow(" ", " ")]

0 commit comments

Comments
 (0)