Skip to content
Open
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
27 changes: 27 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# EditorConfig is awesome: http://EditorConfig.org

# top-most EditorConfig file
root = true

[*]
end_of_line = unset
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4

[*.{proj,props,sln,targets,sql}]
indent_style = tab

[*.{dna,config,nuspec,xml,xsd,csproj,vcxproj,vcproj,targets,ps1,resx}]
indent_size = 2

[*.{cpp,h,def}]
indent_style = tab

[*.dotsettings]
end_of_line = lf

[*.sas]
indent_style = tab
end_of_line = lf
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ coverage.xml
.vscode/
.idea/
*.user
/_ReSharper.Caches/ReSharperPlatformVs17233_c49249f4.RecordParser.00
2 changes: 1 addition & 1 deletion RecordParser.Benchmark/Common.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private static T ProcessSequence<T>(ReadOnlySequence<byte> sequence, FuncSpanT<T

if (sequence.IsSingleSegment)
{
return Parse(sequence.FirstSpan, parser);
return Parse(sequence.First.Span, parser);
}

var length = (int)sequence.Length;
Expand Down
10 changes: 5 additions & 5 deletions RecordParser.Benchmark/CursivelyPersonVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public override void VisitEndOfField(ReadOnlySpan<byte> chunk)
if (_dataBufUsed != 0)
{
VisitPartialFieldContents(chunk);
chunk = _dataBuf.AsSpan(.._dataBufUsed);
chunk = _dataBuf.AsSpan(0, _dataBufUsed);
_dataBufUsed = 0;
}

Expand All @@ -55,8 +55,8 @@ public override void VisitEndOfField(ReadOnlySpan<byte> chunk)
case 3:
// M/d/yyyy format is not supported by this for some reason.
////_ = Utf8Parser.TryParse(chunk, out _person.birthday, out _);
Span<char> birthdayChars = _decodeBuf.AsSpan(..Encoding.UTF8.GetChars(chunk, _decodeBuf));
_person.birthday = DateTime.Parse(birthdayChars, DateTimeFormatInfo.InvariantInfo);
Span<char> birthdayChars = _decodeBuf.AsSpan(0, Encoding.UTF8.GetChars(chunk, _decodeBuf));
_person.birthday = Parse.DateTime(birthdayChars, DateTimeFormatInfo.InvariantInfo);
break;

case 4:
Expand All @@ -67,7 +67,7 @@ public override void VisitEndOfField(ReadOnlySpan<byte> chunk)
// N.B.: there are ways to improve the efficiency of this for earlier
// targets, but I think it's fine for performance-sensitive applications to
// have to upgrade to .NET 6.0 or higher...
_person.gender = Enum.Parse<Gender>(Encoding.UTF8.GetString(chunk));
_person.gender = Parse.Enum<Gender>(Encoding.UTF8.GetString(chunk).AsSpan());
#endif
break;

Expand All @@ -91,7 +91,7 @@ public override void VisitEndOfRecord()
public override void VisitPartialFieldContents(ReadOnlySpan<byte> chunk)
{
EnsureCapacity(_dataBufUsed + chunk.Length);
chunk.CopyTo(_dataBuf.AsSpan(_dataBufUsed..));
chunk.CopyTo(_dataBuf.AsSpan(_dataBufUsed));
_dataBufUsed += chunk.Length;
}

Expand Down
14 changes: 7 additions & 7 deletions RecordParser.Benchmark/FixedLengthReaderBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public async Task Read_FixedLength_ManualString()
name = line.Substring(2, 30).Trim(),
age = int.Parse(line.Substring(32, 2)),
birthday = DateTime.Parse(line.Substring(39, 10), CultureInfo.InvariantCulture),
gender = Enum.Parse<Gender>(line.Substring(85, 6)),
gender = Parse.Enum<Gender>(line.Substring(85, 6).AsSpan()),
email = line.Substring(92, 22).Trim(),
children = bool.Parse(line.Substring(121, 5))
};
Expand Down Expand Up @@ -151,12 +151,12 @@ await ProcessFlatFile((ReadOnlySpan<char> line) =>
return new Person
{
alfa = line[0],
name = new string(line.Slice(2, 30).Trim()),
age = int.Parse(line.Slice(32, 2)),
birthday = DateTime.Parse(line.Slice(39, 10), CultureInfo.InvariantCulture),
gender = Enum.Parse<Gender>(line.Slice(85, 6)),
email = new string(line.Slice(92, 22).Trim()),
children = bool.Parse(line.Slice(121, 5))
name = line.Slice(2, 30).Trim().ToString(),
age = Parse.Int32(line.Slice(32, 2)),
birthday = Parse.DateTime(line.Slice(39, 10), CultureInfo.InvariantCulture),
gender = Parse.Enum<Gender>(line.Slice(85, 6)),
email = line.Slice(92, 22).Trim().ToString(),
children = Parse.Boolean(line.Slice(121, 5))
};
});

Expand Down
53 changes: 53 additions & 0 deletions RecordParser.Benchmark/Parse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Globalization;
using System.Runtime.CompilerServices;

namespace RecordParser.Benchmark
{
internal static class Parse
{
#if NETSTANDARD2_0 || NETFRAMEWORK
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string ProcessSpan(ReadOnlySpan<char> span) => span.ToString();
#else
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static ReadOnlySpan<char> ProcessSpan(ReadOnlySpan<char> span) => span;
#endif

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte Byte(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => byte.Parse(ProcessSpan(utf8Text), NumberStyles.Integer, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static sbyte SByte(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => sbyte.Parse(ProcessSpan(utf8Text), NumberStyles.Integer, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static double Double(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => double.Parse(ProcessSpan(utf8Text), NumberStyles.AllowThousands | NumberStyles.Float, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static float Single(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => float.Parse(ProcessSpan(utf8Text), NumberStyles.AllowThousands | NumberStyles.Float, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int Int32(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => int.Parse(ProcessSpan(utf8Text), NumberStyles.Integer, provider);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Guid Guid(ReadOnlySpan<char> utf8Text) => System.Guid.Parse(ProcessSpan(utf8Text));

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static DateTime DateTime(ReadOnlySpan<char> utf8Text, IFormatProvider provider = null) => System.DateTime.Parse(ProcessSpan(utf8Text), provider, DateTimeStyles.AllowWhiteSpaces);

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Boolean(ReadOnlySpan<char> utf8Text) => bool.Parse(ProcessSpan(utf8Text));

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TEnum Enum<TEnum>(ReadOnlySpan<char> utf8Text) where TEnum : struct, Enum
{
#if NETSTANDARD2_0 || NETFRAMEWORK
return (TEnum)System.Enum.Parse(typeof(TEnum), utf8Text.ToString());
#elif NETSTANDARD2_1
return System.Enum.Parse<TEnum>(utf8Text.ToString());
#else
return System.Enum.Parse<TEnum>(utf8Text);
#endif
}
}
}
13 changes: 12 additions & 1 deletion RecordParser.Benchmark/RecordParser.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net8.0</TargetFrameworks>
<TargetFrameworks>net472;net6.0;net7.0;net8.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<!--<DefineConstants>TEST_ALL</DefineConstants>-->
</PropertyGroup>

<PropertyGroup Condition="'$(TargetFramework)'=='net472'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Ben.StringIntern" Version="0.1.8" />
<PackageReference Include="Cursively" Version="1.2.0" />
Expand All @@ -19,6 +23,13 @@
<PackageReference Include="ZString" Version="2.5.0" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)'=='net472'">
<PackageReference Include="PolySharp" Version="1.14.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\RecordParser\RecordParser.csproj" />
</ItemGroup>
Expand Down
72 changes: 72 additions & 0 deletions RecordParser.Benchmark/Shims.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#if NETSTANDARD2_0 || NETFRAMEWORK

using System;
using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace RecordParser.Benchmark
{
internal static class Shims
{
public static int GetChars(this Encoding encoding, ReadOnlySpan<byte> bytes, Span<char> chars)
{
unsafe
{
fixed (byte* b = &MemoryMarshal.GetReference(bytes))
{
int charCount = encoding.GetCharCount(b, bytes.Length);
if (charCount > chars.Length) return 0;

fixed (char* c = &MemoryMarshal.GetReference(chars))
{
return encoding.GetChars(b, bytes.Length, c, chars.Length);
}
}
}
}

public static string GetString(this Encoding encoding, scoped ReadOnlySpan<byte> bytes)
{
if (bytes.IsEmpty) return string.Empty;

unsafe
{
fixed (byte* pB = &MemoryMarshal.GetReference(bytes))
{
return encoding.GetString(pB, bytes.Length);
}
}
}

public static Task WriteLineAsync(this TextWriter writer, ReadOnlyMemory<char> value, CancellationToken cancellationToken = default)
{
if (MemoryMarshal.TryGetArray(value, out var arraySegment))
{
return arraySegment.Array is null ? Task.CompletedTask : writer.WriteLineAsync(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
}

return Impl(writer, value);

static async Task Impl(TextWriter writer, ReadOnlyMemory<char> value)
{
var pool = ArrayPool<char>.Shared;
var array = pool.Rent(value.Length);
try
{
value.CopyTo(array.AsMemory());
await writer.WriteLineAsync(array, 0, value.Length);
}
finally
{
pool.Return(array);
}
}
}
}
}

#endif
16 changes: 8 additions & 8 deletions RecordParser.Benchmark/VariableLengthReaderBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ public async Task Read_VariableLength_ManualString()
{
if (i++ == LimitRecord) return;

var coluns = line.Split(",");
var coluns = line.Split(',');
var person = new Person()
{
id = Guid.Parse(coluns[0]),
name = coluns[1].Trim(),
age = int.Parse(coluns[2]),
birthday = DateTime.Parse(coluns[3], CultureInfo.InvariantCulture),
gender = Enum.Parse<Gender>(coluns[4]),
gender = Parse.Enum<Gender>(coluns[4].AsSpan()),
email = coluns[5].Trim(),
children = bool.Parse(coluns[7])
};
Expand Down Expand Up @@ -203,7 +203,7 @@ Person PersonFactory(Func<int, string> getColumnValue)
name = getColumnValue(1).Trim(),
age = int.Parse(getColumnValue(2)),
birthday = DateTime.Parse(getColumnValue(3), CultureInfo.InvariantCulture),
gender = Enum.Parse<Gender>(getColumnValue(4)),
gender = Parse.Enum<Gender>(getColumnValue(4).AsSpan()),
email = getColumnValue(5).Trim(),
children = bool.Parse(getColumnValue(7))
};
Expand Down Expand Up @@ -262,13 +262,13 @@ await ProcessCSVFile((ReadOnlySpan<char> line) =>

return new Person
{
id = Guid.Parse(id),
id = Parse.Guid(id),
name = name.ToString(),
age = int.Parse(age),
birthday = DateTime.Parse(birthday, DateTimeFormatInfo.InvariantInfo),
gender = Enum.Parse<Gender>(gender),
age = Parse.Int32(age),
birthday = Parse.DateTime(birthday, DateTimeFormatInfo.InvariantInfo),
gender = Parse.Enum<Gender>(gender),
email = email.ToString(),
children = bool.Parse(children)
children = Parse.Boolean(children)
};
});
}
Expand Down
2 changes: 1 addition & 1 deletion RecordParser.Benchmark/VariableLengthWriterBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public async Task Write_VariableLength_ManualString()
sb.Append(";");
sb.Append(person.children);

await streamWriter.WriteLineAsync(sb);
await streamWriter.WriteLineAsync(sb.ToString());
sb.Clear();
}
}
Expand Down
6 changes: 3 additions & 3 deletions RecordParser.Test/FileReaderTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ public void Read_csv_file_all_fields_quoted(string fileContent, bool hasHeader,
new () { id = new Guid("63858071-cbb3-5abd-9f88-3dfd565cc4ab"), name = "Lucy Berry", age = 49, birthday = DateTime.Parse("11/12/1961"), gender = Gender.Female, email = "vanvo@ro.pk", children = false },
new () { id = new Guid("203804f9-93e7-5510-8bb2-177296bafe6a"), name = "Frank Fox", age = 36, birthday = DateTime.Parse("3/19/1977"), gender = Gender.Male, email = "vav@ped.fj", children = true },
new () { id = new Guid("a8af66fb-bad4-51eb-810c-bf3ca22337c6"), name = "Isabel Todd", age = 51, birthday = DateTime.Parse("9/16/1999"), gender = Gender.Female, email = "gu@or.bz", children = false },
new () { id = new Guid("1a3d8a66-3e0c-50eb-99c1-a3926bce15ed"), name = $"Joseph {Environment.NewLine}Scott", age = 55, birthday = DateTime.Parse("10/26/1986"), gender = Gender.Male, email = "bup@vugeb.tt", children = false },
new () { id = new Guid("1a3d8a66-3e0c-50eb-99c1-a3926bce15ed"), name = "Joseph \r\nScott", age = 55, birthday = DateTime.Parse("10/26/1986"), gender = Gender.Male, email = "bup@vugeb.tt", children = false },
new () { id = new Guid("aa7d4395-f10f-5776-9912-e3d86c4b9d3c"), name = "Gilbert Brooks", age = 56, birthday = DateTime.Parse("3/1/1956"), gender = Gender.Female, email = "epiju@ba.ly", children = true },
new () { id = new Guid("1d25b811-4002-5744-ac40-93a50f2a442c"), name = "Louis \"Ronaldo\" Bennett", age = 25, birthday = DateTime.Parse("4/4/1967"), gender = Gender.Male, email = "ma@itrovive.tv", children = true },
new () { id = new Guid("8e963ae5-a9ed-5572-b11c-566abc6a8a56"), name = "Norman Parker", age = 57, birthday = DateTime.Parse("4/17/1969"), gender = Gender.Male, email = "omi@hewepa.bw", children = true },
Expand Down Expand Up @@ -291,7 +291,7 @@ public void Read_quoted_csv_file(string fileContent, bool hasHeader, bool parall
var expectedItems = new Quoted[]
{
new Quoted { Id = 1, Date = new DateTime(2010, 01, 02), Name = "Ana", Rate = "Good", Ranking = 56 },
new Quoted { Id = 2, Date = new DateTime(2011, 05, 12), Name = "Bob", Rate = $"Much {Environment.NewLine}Good", Ranking = 4 },
new Quoted { Id = 2, Date = new DateTime(2011, 05, 12), Name = "Bob", Rate = "Much \r\nGood", Ranking = 4 },
new Quoted { Id = 3, Date = new DateTime(2013, 12, 10), Name = "Carla", Rate = "\"Medium\"", Ranking = 5 },
new Quoted { Id = 4, Date = new DateTime(2015, 03, 03), Name = "Derik", Rate = "Absolute, Awesome", Ranking = 1 },
}
Expand Down Expand Up @@ -569,4 +569,4 @@ public void Read_plain_text_of_fixed_length_file(string fileContent, bool parall
result.Should().BeEquivalentTo(expected, cfg => cfg.WithStrictOrdering());
}
}
}
}
4 changes: 2 additions & 2 deletions RecordParser.Test/FileWriterTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void Write_csv_file(int repeat, bool parallel, bool ordered)

var reader = new VariableLengthReaderBuilder<(string Name, DateTime Birthday, decimal Money, Color Color, int Index)>()
.Map(x => x.Name, 0)
.Map(x => x.Birthday, 1, value => new DateTime(long.Parse(value)))
.Map(x => x.Birthday, 1, value => new DateTime(Parse.Int64(value)))
.Map(x => x.Money, 2)
.Map(x => x.Color, 3)
.Map(x => x.Index, 4)
Expand Down Expand Up @@ -97,4 +97,4 @@ public void Write_csv_file(int repeat, bool parallel, bool ordered)
items.Should().BeEquivalentTo(expectedItems);
}
}
}
}
Loading