diff --git a/CsvHelper.FastDynamic.Performance/Internal/CsvReaderExtension.cs b/CsvHelper.FastDynamic.Performance/Internal/CsvReaderExtension.cs index 3bb3d15..a5dad08 100644 --- a/CsvHelper.FastDynamic.Performance/Internal/CsvReaderExtension.cs +++ b/CsvHelper.FastDynamic.Performance/Internal/CsvReaderExtension.cs @@ -5,7 +5,7 @@ namespace CsvHelper.FastDynamic.Performance.Internal { internal static class CsvReaderExtension { - internal static IList> GetDictionaryRecords(this CsvReader csvReader) + internal static IReadOnlyList> GetDictionaryRecords(this CsvReader csvReader) { // Read Header csvReader.Read(); diff --git a/CsvHelper.FastDynamic.Performance/Program.cs b/CsvHelper.FastDynamic.Performance/Program.cs index c256c77..fc8b64d 100644 --- a/CsvHelper.FastDynamic.Performance/Program.cs +++ b/CsvHelper.FastDynamic.Performance/Program.cs @@ -7,6 +7,7 @@ class Program static void Main(string[] args) { BenchmarkRunner.Run(); + BenchmarkRunner.Run(); } } } diff --git a/CsvHelper.FastDynamic.Performance/ReaderBenchmark.cs b/CsvHelper.FastDynamic.Performance/ReaderBenchmark.cs index aee3403..1d11a47 100644 --- a/CsvHelper.FastDynamic.Performance/ReaderBenchmark.cs +++ b/CsvHelper.FastDynamic.Performance/ReaderBenchmark.cs @@ -34,7 +34,7 @@ public IReadOnlyList> GetDictionaryRecords() { using var csvReader = new CsvReader(new StringReader(_sampleCsvData), CultureInfo.InvariantCulture); - return csvReader.GetDictionaryRecords().ToArray(); + return csvReader.GetDictionaryRecords(); } [Benchmark] diff --git a/CsvHelper.FastDynamic.Performance/WriterBenchmark.cs b/CsvHelper.FastDynamic.Performance/WriterBenchmark.cs new file mode 100644 index 0000000..204bdc1 --- /dev/null +++ b/CsvHelper.FastDynamic.Performance/WriterBenchmark.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using System.Globalization; +using System.IO; + +using BenchmarkDotNet.Attributes; + +namespace CsvHelper.FastDynamic.Performance +{ + [MemoryDiagnoser] + public class WriterBenchmark + { + private const string SampleCsvFile = @".\sampledata\SFO_Airport_Monthly_Utility_Consumption_for_Natural_Gas__Water__and_Electricity.csv"; + + public WriterBenchmark() + { + using var csvReader = new CsvReader(new StreamReader(SampleCsvFile), CultureInfo.InvariantCulture); + + _sampleCsvData = csvReader.GetDynamicRecords(); + } + + private readonly IReadOnlyList _sampleCsvData; + + [Benchmark] + public void WriteRecords() + { + using var csvWriter = new CsvWriter(new StringWriter(), CultureInfo.InvariantCulture); + + csvWriter.WriteRecords(_sampleCsvData); + } + + [Benchmark] + public void WriteDynamicRecords() + { + using var csvWriter = new CsvWriter(new StringWriter(), CultureInfo.InvariantCulture); + + csvWriter.WriteDynamicRecords(_sampleCsvData); + } + } +} diff --git a/CsvHelper.FastDynamic/CsvReaderExtensions.cs b/CsvHelper.FastDynamic/CsvReaderExtensions.cs index 9772f6c..e811155 100644 --- a/CsvHelper.FastDynamic/CsvReaderExtensions.cs +++ b/CsvHelper.FastDynamic/CsvReaderExtensions.cs @@ -36,10 +36,7 @@ public static IEnumerable EnumerateDynamicRecords(this CsvReader csvRea { var values = new object[context.HeaderRecord.Length]; - for (int i = 0; i < values.Length; i++) - { - values[i] = csvReader.GetField(i); - } + Array.Copy(context.Record, values, values.Length); record = new CsvRecord(csvHeader, values); } @@ -99,10 +96,7 @@ public static async IAsyncEnumerable EnumerateDynamicRecordsAsync(this { var values = new object[context.HeaderRecord.Length]; - for (int i = 0; i < values.Length; i++) - { - values[i] = csvReader.GetField(i); - } + Array.Copy(context.Record, values, values.Length); record = new CsvRecord(csvHeader, values); } diff --git a/CsvHelper.FastDynamic/CsvRecordMetaObject.cs b/CsvHelper.FastDynamic/CsvRecordMetaObject.cs index 92ad85e..6c6ef88 100644 --- a/CsvHelper.FastDynamic/CsvRecordMetaObject.cs +++ b/CsvHelper.FastDynamic/CsvRecordMetaObject.cs @@ -9,6 +9,8 @@ namespace CsvHelper.FastDynamic // Thanks, from https://github.com/StackExchange/Dapper/blob/master/Dapper/SqlMapper.DapperRowMetaObject.cs internal sealed class CsvRecordMetaObject : DynamicMetaObject { + private static readonly string[] EmptyArray = Array.Empty(); + private static readonly MethodInfo GetValueMethod = typeof(IDictionary).GetProperty("Item").GetGetMethod(); private static readonly MethodInfo SetValueMethod = typeof(CsvRecord).GetMethod(nameof(CsvRecord.SetValue), new[] { typeof(string), typeof(object) }); @@ -60,7 +62,7 @@ public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicM public override IEnumerable GetDynamicMemberNames() { - return HasValue && Value is IDictionary lookup ? lookup.Keys : Array.Empty(); + return HasValue && Value is IDictionary lookup ? lookup.Keys : EmptyArray; } } } diff --git a/README.md b/README.md index 5dac733..9155534 100644 --- a/README.md +++ b/README.md @@ -63,19 +63,37 @@ class Program ## Performance +### Reader + ``` -BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.264 (2004/?/20H1) +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.329 (2004/?/20H1) Intel Core i9-10940X CPU 3.30GHz, 1 CPU, 28 logical and 14 physical cores -.NET Core SDK=3.1.300 - [Host] : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT - DefaultJob : .NET Core 3.1.4 (CoreCLR 4.700.20.20201, CoreFX 4.700.20.22101), X64 RyuJIT +.NET Core SDK=3.1.301 + [Host] : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT + DefaultJob : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT | Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | |--------------------- |-----------:|---------:|---------:|--------:|--------:|------:|----------:| -| GetRecords | 1,586.6 us | 12.29 us | 11.50 us | 83.9844 | 41.0156 | - | 828.13 KB | -| GetDictionaryRecords | 734.3 us | 7.96 us | 7.45 us | 73.2422 | 35.1563 | - | 728.99 KB | -| GetDynamicRecords | 599.2 us | 5.04 us | 4.47 us | 61.5234 | 1.9531 | - | 607.82 KB | +| GetRecords | 1,687.1 us | 17.11 us | 15.17 us | 83.9844 | 41.0156 | - | 829.18 KB | +| GetDictionaryRecords | 776.7 us | 14.59 us | 16.21 us | 73.2422 | 34.1797 | - | 725.63 KB | +| GetDynamicRecords | 617.3 us | 11.23 us | 9.96 us | 61.5234 | 2.9297 | - | 608.87 KB | +``` + +### Writer + +``` +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.329 (2004/?/20H1) +Intel Core i9-10940X CPU 3.30GHz, 1 CPU, 28 logical and 14 physical cores +.NET Core SDK=3.1.301 + [Host] : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT + DefaultJob : .NET Core 3.1.5 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.27001), X64 RyuJIT + + +| Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated | +|-------------------- |-----------:|---------:|---------:|---------:|--------:|------:|-----------:| +| WriteRecords | 2,355.6 us | 46.86 us | 64.14 us | 148.4375 | 27.3438 | - | 1476.46 KB | +| WriteDynamicRecords | 885.7 us | 17.50 us | 16.37 us | 17.5781 | 2.9297 | - | 174.49 KB | ``` ## Thanks