Skip to content

Commit

Permalink
- caching of writer action for collections
Browse files Browse the repository at this point in the history
- removed duplicate type resolving
- optimized Type HashCodes
- reduced memory allocation during writes
  • Loading branch information
snaumenko-st committed Apr 19, 2024
1 parent 9da7834 commit 183d47b
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 45 deletions.
63 changes: 54 additions & 9 deletions src/CsvHelper/CsvWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ public virtual void WriteRecord<T>(T record)
{
try
{
recordManager.Value.Write(record);
recordManager.Value.GetWriterAction<T>(GetTypeInfoForRecord(record))(record);
}
catch (TargetInvocationException ex)
{
Expand Down Expand Up @@ -362,6 +362,9 @@ public virtual void WriteRecords(IEnumerable records)
NextRecord();
}

Action<object> writerAction = null;
RecordTypeInfo writerActionType = default;

foreach (var record in records)
{
if (record == null)
Expand All @@ -371,7 +374,13 @@ public virtual void WriteRecords(IEnumerable records)
continue;
}

WriteRecord(record);
if (writerAction == null || writerActionType.RecordType != record.GetType())
{
writerActionType = GetTypeInfoForRecord(record);
writerAction = recordManager.Value.GetWriterAction<object>(writerActionType);
}

writerAction(record);
NextRecord();
}
}
Expand All @@ -393,9 +402,18 @@ public virtual void WriteRecords<T>(IEnumerable<T> records)
NextRecord();
}

Action<T> writerAction = null;
RecordTypeInfo writerActionType = default;

foreach (var record in records)
{
WriteRecord(record);
if (writerAction == null || (record != null && writerActionType.RecordType != record.GetType()))
{
writerActionType = GetTypeInfoForRecord(record);
writerAction = recordManager.Value.GetWriterAction<T>(writerActionType);
}

writerAction(record);
NextRecord();
}
}
Expand All @@ -420,11 +438,20 @@ public virtual async Task WriteRecordsAsync(IEnumerable records, CancellationTok
await NextRecordAsync().ConfigureAwait(false);
}

Action<object> writerAction = null;
RecordTypeInfo writerActionType = default;

foreach (var record in records)
{
cancellationToken.ThrowIfCancellationRequested();

WriteRecord(record);
if (writerAction == null || (record != null && writerActionType.RecordType != record.GetType()))
{
writerActionType = GetTypeInfoForRecord(record);
writerAction = recordManager.Value.GetWriterAction<object>(writerActionType);
}

writerAction(record);
await NextRecordAsync().ConfigureAwait(false);
}
}
Expand All @@ -449,11 +476,20 @@ public virtual async Task WriteRecordsAsync<T>(IEnumerable<T> records, Cancellat
await NextRecordAsync().ConfigureAwait(false);
}

Action<T> writerAction = null;
RecordTypeInfo writerActionType = default;

foreach (var record in records)
{
cancellationToken.ThrowIfCancellationRequested();

WriteRecord(record);
if (writerAction == null || (record != null && writerActionType.RecordType != record.GetType()))
{
writerActionType = GetTypeInfoForRecord(record);
writerAction = recordManager.Value.GetWriterAction<T>(writerActionType);
}

writerAction(record);
await NextRecordAsync().ConfigureAwait(false);
}
}
Expand All @@ -478,11 +514,20 @@ public virtual async Task WriteRecordsAsync<T>(IAsyncEnumerable<T> records, Canc
await NextRecordAsync().ConfigureAwait(false);
}

Action<T> writerAction = null;
RecordTypeInfo writerActionType = default;

await foreach (var record in records.ConfigureAwait(false))
{
cancellationToken.ThrowIfCancellationRequested();

WriteRecord(record);
if (writerAction == null || (record != null && writerActionType.RecordType != record.GetType()))
{
writerActionType = GetTypeInfoForRecord(record);
writerAction = recordManager.Value.GetWriterAction<T>(writerActionType);
}

writerAction(record);
await NextRecordAsync().ConfigureAwait(false);
}
}
Expand Down Expand Up @@ -576,15 +621,15 @@ public virtual bool CanWrite(MemberMap memberMap)
/// <typeparam name="T">The type of the record.</typeparam>
/// <param name="record">The record to determine the type of.</param>
/// <returns>The System.Type for the record.</returns>
public virtual Type GetTypeForRecord<T>(T record)
public virtual RecordTypeInfo GetTypeInfoForRecord<T>(T record)
{
var type = typeof(T);
if (type == typeof(object))
{
type = record.GetType();
return new RecordTypeInfo(record.GetType(), true);
}

return type;
return new RecordTypeInfo(type, false);
}

/// <summary>
Expand Down
6 changes: 3 additions & 3 deletions src/CsvHelper/Expressions/DynamicRecordWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ public DynamicRecordWriter(CsvWriter writer) : base(writer) { }
/// that will write the given record using the current writer row.
/// </summary>
/// <typeparam name="T">The record type.</typeparam>
/// <param name="record">The record.</param>
protected override Action<T> CreateWriteDelegate<T>(T record)
/// <param name="type">The type for the record.</param>
protected override Action<T> CreateWriteDelegate<T>(Type type)
{
// http://stackoverflow.com/a/14011692/68499

Expand Down Expand Up @@ -72,4 +72,4 @@ private object GetValue(string name, IDynamicMetaObjectProvider target)
return callSite.Target(callSite, target);
}
}
}
}
6 changes: 3 additions & 3 deletions src/CsvHelper/Expressions/ExpandoObjectRecordWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public ExpandoObjectRecordWriter(CsvWriter writer) : base(writer) { }
/// that will write the given record using the current writer row.
/// </summary>
/// <typeparam name="T">The record type.</typeparam>
/// <param name="record">The record.</param>
protected override Action<T> CreateWriteDelegate<T>(T record)
/// <param name="type">The type for the record.</param>
protected override Action<T> CreateWriteDelegate<T>(Type type)
{
Action<T> action = r =>
{
Expand All @@ -46,4 +46,4 @@ protected override Action<T> CreateWriteDelegate<T>(T record)
return action;
}
}
}
}
6 changes: 2 additions & 4 deletions src/CsvHelper/Expressions/ObjectRecordWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,9 @@ public ObjectRecordWriter(CsvWriter writer) : base(writer) { }
/// that will write the given record using the current writer row.
/// </summary>
/// <typeparam name="T">The record type.</typeparam>
/// <param name="record">The record.</param>
protected override Action<T> CreateWriteDelegate<T>(T record)
/// <param name="type">The type for the record.</param>
protected override Action<T> CreateWriteDelegate<T>(Type type)
{
var type = Writer.GetTypeForRecord(record);

if (Writer.Context.Maps[type] == null)
{
Writer.Context.Maps.Add(Writer.Context.AutoMap(type));
Expand Down
6 changes: 2 additions & 4 deletions src/CsvHelper/Expressions/PrimitiveRecordWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,9 @@ public PrimitiveRecordWriter(CsvWriter writer) : base(writer) { }
/// that will write the given record using the current writer row.
/// </summary>
/// <typeparam name="T">The record type.</typeparam>
/// <param name="record">The record.</param>
protected override Action<T> CreateWriteDelegate<T>(T record)
/// <param name="type">The type for the record.</param>
protected override Action<T> CreateWriteDelegate<T>(Type type)
{
var type = Writer.GetTypeForRecord(record);

var recordParameter = Expression.Parameter(typeof(T), "record");

Expression fieldExpression = Expression.Convert(recordParameter, typeof(object));
Expand Down
13 changes: 7 additions & 6 deletions src/CsvHelper/Expressions/RecordManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,15 @@ public void Hydrate<T>(T record)
}

/// <summary>
/// Writes the given record to the current writer row.
/// Gets Writer Action to write multiple rows faster
/// </summary>
/// <typeparam name="T">The type of the record.</typeparam>
/// <param name="record">The record.</param>
public void Write<T>(T record)
/// <param name="typeInfo"></param>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
internal Action<T> GetWriterAction<T>(RecordTypeInfo typeInfo)
{
var recordWriter = recordWriterFactory.MakeRecordWriter(record);
recordWriter.Write(record);
var recordWriter = recordWriterFactory.MakeRecordWriter(typeInfo);
return recordWriter.GetWriteDelegate<T>(typeInfo);
}
}
}
35 changes: 33 additions & 2 deletions src/CsvHelper/Expressions/RecordWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void Write<T>(T record)
/// </summary>
/// <typeparam name="T">The record type.</typeparam>
/// <param name="record">The record.</param>
protected Action<T> GetWriteDelegate<T>(T record)
protected internal Action<T> GetWriteDelegate<T>(T record)
{
var type = typeof(T);
var typeKeyName = type.AssemblyQualifiedName;
Expand All @@ -86,13 +86,44 @@ protected Action<T> GetWriteDelegate<T>(T record)
return (Action<T>)action;
}

/// <summary>
/// Gets the delegate to write the given record.
/// If the delegate doesn't exist, one will be created and cached.
/// </summary>
/// <typeparam name="T">The record type.</typeparam>
/// <param name="typeInfo">The type for the record.</param>
protected internal Action<T> GetWriteDelegate<T>(RecordTypeInfo typeInfo)
{
var typeKey = typeInfo.RecordType.GetHashCode();

if (typeInfo.IsItemType)
typeKey = HashCode.Combine(17180427, typeKey);

if (!typeActions.TryGetValue(typeKey, out Delegate action))
{
typeActions[typeKey] = action = CreateWriteDelegate<T>(typeInfo.RecordType);
}

return (Action<T>)action;
}

/// <summary>
/// Creates a <see cref="Delegate"/> of type <see cref="Action{T}"/>
/// that will write the given record using the current writer row.
/// </summary>
/// <typeparam name="T">The record type.</typeparam>
/// <param name="record">The record.</param>
protected abstract Action<T> CreateWriteDelegate<T>(T record);
protected virtual Action<T> CreateWriteDelegate<T>(T record)
{
return CreateWriteDelegate<T>(Writer.GetTypeInfoForRecord(record).RecordType);
}

/// <summary>
/// Creates a <see cref="Delegate"/> of type <see cref="Action{T}"/>
/// that will write the given record using the current writer row.
/// </summary>
/// <param name="typeForRecord">The type for the record.</param>
protected abstract Action<T> CreateWriteDelegate<T>(Type typeForRecord);

/// <summary>
/// Combines the delegates into a single multicast delegate.
Expand Down
24 changes: 10 additions & 14 deletions src/CsvHelper/Expressions/RecordWriterFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
// This file is a part of CsvHelper and is dual licensed under MS-PL and Apache 2.0.
// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0.
// https://github.com/JoshClose/CsvHelper

using System.Dynamic;
using System.Reflection;

namespace CsvHelper.Expressions
{
Expand Down Expand Up @@ -34,27 +34,23 @@ public RecordWriterFactory(CsvWriter writer)
/// <summary>
/// Creates a new record writer for the given record.
/// </summary>
/// <typeparam name="T">The type of the record.</typeparam>
/// <param name="record">The record.</param>
public virtual RecordWriter MakeRecordWriter<T>(T record)
/// <param name="typeInfo">The type of the record.</param>
public virtual RecordWriter MakeRecordWriter(RecordTypeInfo typeInfo)
{
var type = writer.GetTypeForRecord(record);
var type = typeInfo.RecordType;

if (record is ExpandoObject expandoObject)
if (type.IsPrimitive)
{
return expandoObjectRecordWriter;
return primitiveRecordWriter;
}

if (record is IDynamicMetaObjectProvider dynamicObject)
if (typeof(ExpandoObject).IsAssignableFrom(type))
{
return dynamicRecordWriter;
return expandoObjectRecordWriter;
}

if (type.GetTypeInfo().IsPrimitive)
if (typeof(IDynamicMetaObjectProvider).IsAssignableFrom(type))
{
return primitiveRecordWriter;
return dynamicRecordWriter;
}

return objectRecordWriter;
}
}
Expand Down
31 changes: 31 additions & 0 deletions src/CsvHelper/RecordTypeInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;

namespace CsvHelper
{
/// <summary>
/// Container for type info about written records
/// </summary>
public struct RecordTypeInfo
{
/// <summary>
/// .ctor
/// </summary>
/// <param name="recordType"></param>
/// <param name="isItemType"></param>
public RecordTypeInfo(Type recordType, bool isItemType)
{
RecordType = recordType;
IsItemType = isItemType;
}

/// <summary>
/// Final type of the record
/// </summary>
public Type RecordType { get; }

/// <summary>
/// Is this type from record item
/// </summary>
public bool IsItemType { get; }
}
}

0 comments on commit 183d47b

Please sign in to comment.