diff --git a/performance/CsvHelper.Benchmarks/BenchmarkMain.cs b/performance/CsvHelper.Benchmarks/BenchmarkMain.cs index 849ffcc1b..5476b4440 100644 --- a/performance/CsvHelper.Benchmarks/BenchmarkMain.cs +++ b/performance/CsvHelper.Benchmarks/BenchmarkMain.cs @@ -6,6 +6,6 @@ internal class BenchmarkMain { static void Main(string[] args) { - _ = BenchmarkRunner.Run(); + _ = BenchmarkSwitcher.FromAssembly(System.Reflection.Assembly.GetExecutingAssembly()).Run(args); } } diff --git a/performance/CsvHelper.Benchmarks/BenchmarkWriteCsv.cs b/performance/CsvHelper.Benchmarks/BenchmarkWriteCsv.cs new file mode 100644 index 000000000..055353393 --- /dev/null +++ b/performance/CsvHelper.Benchmarks/BenchmarkWriteCsv.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using BenchmarkDotNet.Attributes; + +namespace CsvHelper.Benchmarks; + +public class BenchmarkWriteCsv +{ + private const int entryCount = 10000; + private readonly List records = new(entryCount); + + public class Simple + { + public int Id1 { get; set; } + public int Id2 { get; set; } + public string Name1 { get; set; } + public string Name2 { get; set; } + } + + [GlobalSetup] + public void GlobalSetup() + { + var random = new Random(42); + var chars = new char[10]; + string getRandomString() + { + for (int i = 0; i < 10; ++i) + chars[i] = (char)random.Next('a', 'z' + 1); + return new string(chars); + } + + for (int i = 0; i < entryCount; ++i) + { + records.Add(new Simple + { + Id1 = random.Next(), + Id2 = random.Next(), + Name1 = getRandomString(), + Name2 = getRandomString(), + }); + } + } + + [Benchmark] + public void WriteRecords() + { + using var stream = new MemoryStream(); + using var streamWriter = new StreamWriter(stream); + using var writer = new CsvHelper.CsvWriter(streamWriter, CultureInfo.InvariantCulture); + writer.WriteRecords(records); + streamWriter.Flush(); + } +} diff --git a/performance/CsvHelper.Benchmarks/CsvHelper.Benchmarks.csproj b/performance/CsvHelper.Benchmarks/CsvHelper.Benchmarks.csproj index 248fbd13f..7804d7294 100644 --- a/performance/CsvHelper.Benchmarks/CsvHelper.Benchmarks.csproj +++ b/performance/CsvHelper.Benchmarks/CsvHelper.Benchmarks.csproj @@ -6,7 +6,8 @@ - + + diff --git a/src/CsvHelper/Expressions/ObjectRecordWriter.cs b/src/CsvHelper/Expressions/ObjectRecordWriter.cs index e00119024..47d9f4f93 100644 --- a/src/CsvHelper/Expressions/ObjectRecordWriter.cs +++ b/src/CsvHelper/Expressions/ObjectRecordWriter.cs @@ -46,7 +46,7 @@ protected override Action CreateWriteDelegate(Type type) throw new WriterException(Writer.Context, $"No properties are mapped for type '{type.FullName}'."); } - var delegates = new List>(); + var expressions = new List(members.Count); foreach (var memberMap in members) { @@ -59,7 +59,7 @@ protected override Action CreateWriteDelegate(Type type) var args = Expression.New(constructor, recordParameterConverted); Expression exp = Expression.Invoke(memberMap.Data.WritingConvertExpression, args); exp = Expression.Call(Expression.Constant(Writer), nameof(Writer.WriteField), null, exp); - delegates.Add(Expression.Lambda>(exp, recordParameter).Compile()); + expressions.Add(exp); continue; } @@ -110,12 +110,16 @@ protected override Action CreateWriteDelegate(Type type) } var writeFieldMethodCall = Expression.Call(Expression.Constant(Writer), nameof(Writer.WriteConvertedField), null, fieldExpression, Expression.Constant(memberMap.Data.Type)); - - delegates.Add(Expression.Lambda>(writeFieldMethodCall, recordParameter).Compile()); + expressions.Add(writeFieldMethodCall); } - var action = CombineDelegates(delegates) ?? new Action((T parameter) => { }); + if (expressions.Count == 0) + { + return new Action((T parameter) => { }); + } - return action; + // Combine all field writes into a single block + var block = Expression.Block(expressions); + return Expression.Lambda>(block, recordParameter).Compile(); } }