Skip to content

Commit 2b74ee9

Browse files
committed
Optimize data reader hot path performance
1 parent bb80b41 commit 2b74ee9

11 files changed

Lines changed: 82 additions & 61 deletions

DuckDB.NET.Data/DataChunk/Reader/BooleanVectorDataReader.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ internal unsafe BooleanVectorDataReader(void* dataPointer, ulong* validityMaskPo
66
{
77
}
88

9-
protected override T GetValidValue<T>(ulong offset, Type targetType)
9+
protected override T GetValidValue<T>(ulong offset)
1010
{
1111
if (DuckDBType != DuckDBType.Boolean)
1212
{
13-
return base.GetValidValue<T>(offset, targetType);
13+
return base.GetValidValue<T>(offset);
1414
}
15-
15+
1616
var value = GetFieldData<bool>(offset);
1717
return (T)(object)value; //JIT will optimize the casts at least for not nullable T
1818
}

DuckDB.NET.Data/DataChunk/Reader/DateTimeVectorDataReader.cs

Lines changed: 22 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,32 @@
22

33
internal sealed class DateTimeVectorDataReader : VectorDataReaderBase
44
{
5-
private static readonly Type DateTimeType = typeof(DateTime);
6-
private static readonly Type DateTimeOffsetType = typeof(DateTimeOffset);
7-
private static readonly Type DateOnlyType = typeof(DateOnly);
8-
private static readonly Type TimeOnlyType = typeof(TimeOnly);
9-
105
internal unsafe DateTimeVectorDataReader(void* dataPointer, ulong* validityMaskPointer, DuckDBType columnType, string columnName) : base(dataPointer, validityMaskPointer, columnType, columnName)
116
{
127
}
138

14-
protected override T GetValidValue<T>(ulong offset, Type targetType)
9+
protected override T GetValidValue<T>(ulong offset)
1510
{
1611
if (DuckDBType == DuckDBType.Date)
1712
{
1813
var (dateOnly, isFinite) = GetDateOnly(offset);
1914

2015
if (!isFinite)
2116
{
22-
if (targetType == DateTimeType || targetType == DateOnlyType)
17+
if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateOnly))
2318
{
2419
ThrowInfinityDateException();
2520
}
2621

2722
return (T)(object)dateOnly;
2823
}
2924

30-
if (targetType == DateTimeType)
25+
if (typeof(T) == typeof(DateTime))
3126
{
3227
return (T)(object)(DateTime)dateOnly;
3328
}
3429

35-
if (targetType == DateOnlyType)
30+
if (typeof(T) == typeof(DateOnly))
3631
{
3732
return (T)(object)(DateOnly)dateOnly;
3833
}
@@ -44,12 +39,12 @@ protected override T GetValidValue<T>(ulong offset, Type targetType)
4439
{
4540
var timeOnly = GetTimeOnly(offset);
4641

47-
if (targetType == DateTimeType)
42+
if (typeof(T) == typeof(DateTime))
4843
{
4944
return (T)(object)(DateTime)timeOnly;
5045
}
5146

52-
if (targetType == TimeOnlyType)
47+
if (typeof(T) == typeof(TimeOnly))
5348
{
5449
return (T)(object)(TimeOnly)timeOnly;
5550
}
@@ -61,7 +56,7 @@ protected override T GetValidValue<T>(ulong offset, Type targetType)
6156
{
6257
var timeTz = GetTimeTz(offset);
6358

64-
if (targetType == DateTimeOffsetType)
59+
if (typeof(T) == typeof(DateTimeOffset))
6560
{
6661
var dateTimeOffset = new DateTimeOffset(timeTz.Time.ToDateTime(), TimeSpan.FromSeconds(timeTz.Offset));
6762
return (T)(object)dateTimeOffset;
@@ -74,18 +69,18 @@ protected override T GetValidValue<T>(ulong offset, Type targetType)
7469
{
7570
DuckDBType.Timestamp or DuckDBType.TimestampS or
7671
DuckDBType.TimestampTz or DuckDBType.TimestampMs or
77-
DuckDBType.TimestampNs => ReadTimestamp<T>(offset, targetType),
78-
_ => base.GetValidValue<T>(offset, targetType)
72+
DuckDBType.TimestampNs => ReadTimestamp<T>(offset),
73+
_ => base.GetValidValue<T>(offset)
7974
};
8075
}
8176

82-
private T ReadTimestamp<T>(ulong offset, Type targetType)
77+
private T ReadTimestamp<T>(ulong offset)
8378
{
8479
var timestampStruct = GetFieldData<DuckDBTimestampStruct>(offset);
8580

8681
if (!timestampStruct.IsFinite(DuckDBType))
8782
{
88-
if (targetType == DateTimeType || targetType == DateTimeOffsetType)
83+
if (typeof(T) == typeof(DateTime) || typeof(T) == typeof(DateTimeOffset))
8984
{
9085
ThrowInfinityTimestampException();
9186
}
@@ -95,12 +90,12 @@ private T ReadTimestamp<T>(ulong offset, Type targetType)
9590

9691
var (timestamp, additionalTicks) = timestampStruct.ToDuckDBTimestamp(DuckDBType);
9792

98-
if (targetType == DateTimeType)
93+
if (typeof(T) == typeof(DateTime))
9994
{
10095
return (T)(object)timestamp.ToDateTime().AddTicks(additionalTicks);
10196
}
10297

103-
if (targetType == DateTimeOffsetType)
98+
if (typeof(T) == typeof(DateTimeOffset))
10499
{
105100
var dateTime = timestamp.ToDateTime().AddTicks(additionalTicks);
106101
return (T)(object)new DateTimeOffset(dateTime, TimeSpan.Zero);
@@ -148,20 +143,20 @@ private object GetDate(ulong offset, Type targetType)
148143

149144
if (!isFinite)
150145
{
151-
if (targetType == DateTimeType || targetType == DateOnlyType)
146+
if (targetType == typeof(DateTime) || targetType == typeof(DateOnly))
152147
{
153148
ThrowInfinityDateException();
154149
}
155150

156151
return dateOnly;
157152
}
158153

159-
if (targetType == DateTimeType)
154+
if (targetType == typeof(DateTime))
160155
{
161156
return (DateTime)dateOnly;
162157
}
163158

164-
if (targetType == DateOnlyType)
159+
if (targetType == typeof(DateOnly))
165160
{
166161
return (DateOnly)dateOnly;
167162
}
@@ -172,12 +167,12 @@ private object GetDate(ulong offset, Type targetType)
172167
private object GetTime(ulong offset, Type targetType)
173168
{
174169
var timeOnly = GetTimeOnly(offset);
175-
if (targetType == DateTimeType)
170+
if (targetType == typeof(DateTime))
176171
{
177172
return (DateTime)timeOnly;
178173
}
179174

180-
if (targetType == TimeOnlyType)
175+
if (targetType == typeof(TimeOnly))
181176
{
182177
return (TimeOnly)timeOnly;
183178
}
@@ -191,7 +186,7 @@ private object GetDateTime(ulong offset, Type targetType)
191186

192187
if (!timestampStruct.IsFinite(DuckDBType))
193188
{
194-
if (targetType == DateTimeType || targetType == DateTimeOffsetType)
189+
if (targetType == typeof(DateTime) || targetType == typeof(DateTimeOffset))
195190
{
196191
ThrowInfinityTimestampException();
197192
}
@@ -201,14 +196,14 @@ private object GetDateTime(ulong offset, Type targetType)
201196

202197
var (timestamp, additionalTicks) = timestampStruct.ToDuckDBTimestamp(DuckDBType);
203198

204-
if (targetType == DateTimeType)
199+
if (targetType == typeof(DateTime))
205200
{
206201
var dateTime = timestamp.ToDateTime().AddTicks(additionalTicks);
207202

208203
return dateTime;
209204
}
210205

211-
if (targetType == DateTimeOffsetType)
206+
if (targetType == typeof(DateTimeOffset))
212207
{
213208
var dateTime = timestamp.ToDateTime().AddTicks(additionalTicks);
214209
return new DateTimeOffset(dateTime, TimeSpan.Zero);
@@ -221,7 +216,7 @@ private object GetDateTimeOffset(ulong offset, Type targetType)
221216
{
222217
var timeTz = GetTimeTz(offset);
223218

224-
if (targetType == DateTimeOffsetType)
219+
if (targetType == typeof(DateTimeOffset))
225220
{
226221
return new DateTimeOffset(timeTz.Time.ToDateTime(), TimeSpan.FromSeconds(timeTz.Offset));
227222
}

DuckDB.NET.Data/DataChunk/Reader/DecimalVectorDataReader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,11 @@ internal unsafe DecimalVectorDataReader(IntPtr vector, void* dataPointer, ulong*
2727

2828
internal byte Precision { get; }
2929

30-
protected override T GetValidValue<T>(ulong offset, Type targetType)
30+
protected override T GetValidValue<T>(ulong offset)
3131
{
3232
if (DuckDBType != DuckDBType.Decimal)
3333
{
34-
return base.GetValidValue<T>(offset, targetType);
34+
return base.GetValidValue<T>(offset);
3535
}
3636

3737
var value = GetDecimal(offset);

DuckDB.NET.Data/DataChunk/Reader/EnumVectorDataReader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ internal unsafe EnumVectorDataReader(IntPtr vector, void* dataPointer, ulong* va
1414
enumType = NativeMethods.LogicalType.DuckDBEnumInternalType(logicalType);
1515
}
1616

17-
protected override T GetValidValue<T>(ulong offset, Type targetType)
17+
protected override T GetValidValue<T>(ulong offset)
1818
{
1919
if (DuckDBType != DuckDBType.Enum)
2020
{
21-
return base.GetValidValue<T>(offset, targetType);
21+
return base.GetValidValue<T>(offset);
2222
}
2323

2424
switch (enumType)

DuckDB.NET.Data/DataChunk/Reader/GuidVectorDataReader.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ internal unsafe GuidVectorDataReader(void* dataPointer, ulong* validityMaskPoint
66
{
77
}
88

9-
protected override T GetValidValue<T>(ulong offset, Type targetType)
9+
protected override T GetValidValue<T>(ulong offset)
1010
{
1111
if (DuckDBType != DuckDBType.Uuid)
1212
{
13-
return base.GetValidValue<T>(offset, targetType);
13+
return base.GetValidValue<T>(offset);
1414
}
1515

1616
var hugeInt = GetFieldData<DuckDBHugeInt>(offset);

DuckDB.NET.Data/DataChunk/Reader/IntervalVectorDataReader.cs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,17 @@
22

33
internal sealed class IntervalVectorDataReader : VectorDataReaderBase
44
{
5-
private static readonly Type TimeSpanType = typeof(TimeSpan);
6-
75
internal unsafe IntervalVectorDataReader(void* dataPointer, ulong* validityMaskPointer, DuckDBType columnType, string columnName) : base(dataPointer, validityMaskPointer, columnType, columnName)
86
{
97
}
108

11-
protected override T GetValidValue<T>(ulong offset, Type targetType)
9+
protected override T GetValidValue<T>(ulong offset)
1210
{
1311
if (DuckDBType == DuckDBType.Interval)
1412
{
1513
var interval = GetFieldData<DuckDBInterval>(offset);
1614

17-
if (targetType == TimeSpanType)
15+
if (typeof(T) == typeof(TimeSpan))
1816
{
1917
var timeSpan = (TimeSpan)interval;
2018
return (T)(object)timeSpan;
@@ -23,7 +21,7 @@ protected override T GetValidValue<T>(ulong offset, Type targetType)
2321
return (T)(object)interval;
2422
}
2523

26-
return base.GetValidValue<T>(offset, targetType);
24+
return base.GetValidValue<T>(offset);
2725
}
2826

2927
internal override object GetValue(ulong offset, Type targetType)
@@ -39,7 +37,7 @@ private object GetInterval(ulong offset, Type targetType)
3937
{
4038
var interval = GetFieldData<DuckDBInterval>(offset);
4139

42-
if (targetType == TimeSpanType)
40+
if (targetType == typeof(TimeSpan))
4341
{
4442
return (TimeSpan)interval;
4543
}

DuckDB.NET.Data/DataChunk/Reader/NumericVectorDataReader.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ internal unsafe NumericVectorDataReader(void* dataPointer, ulong* validityMaskPo
1111
{
1212
}
1313

14-
protected override T GetValidValue<T>(ulong offset, Type targetType)
14+
protected override T GetValidValue<T>(ulong offset)
1515
{
1616
var isFloatingNumericType = TypeExtensions.IsFloatingNumericType<T>();
1717
var isIntegralNumericType = TypeExtensions.IsIntegralNumericType<T>();
1818

1919
if (!(isIntegralNumericType || isFloatingNumericType))
2020
{
21-
return base.GetValidValue<T>(offset, targetType);
21+
return base.GetValidValue<T>(offset);
2222
}
2323

2424
//If T is integral type and column is also integral read the data and use Unsafe.As<> or Convert.ChangeType to change type
@@ -39,15 +39,15 @@ protected override T GetValidValue<T>(ulong offset, Type targetType)
3939
DuckDBType.HugeInt => GetBigInteger<T>(offset, false),
4040
DuckDBType.UnsignedHugeInt => GetBigInteger<T>(offset, true),
4141
DuckDBType.VarInt => GetBigInteger<T>(offset),
42-
_ => base.GetValidValue<T>(offset, targetType)
42+
_ => base.GetValidValue<T>(offset)
4343
};
4444
}
4545

4646
return DuckDBType switch
4747
{
4848
DuckDBType.Float => (T)(object)GetFieldData<float>(offset),
4949
DuckDBType.Double => (T)(object)GetFieldData<double>(offset),
50-
_ => base.GetValidValue<T>(offset, targetType)
50+
_ => base.GetValidValue<T>(offset)
5151
};
5252
}
5353

DuckDB.NET.Data/DataChunk/Reader/StringVectorDataReader.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.IO;
1+
using System.IO;
22
using System.Text;
33

44
namespace DuckDB.NET.Data.DataChunk.Reader;
@@ -9,14 +9,14 @@ internal unsafe StringVectorDataReader(void* dataPointer, ulong* validityMaskPoi
99
{
1010
}
1111

12-
protected override T GetValidValue<T>(ulong offset, Type targetType)
12+
protected override T GetValidValue<T>(ulong offset)
1313
{
1414
return DuckDBType switch
1515
{
1616
DuckDBType.Bit => GetBitString<T>(offset),
1717
DuckDBType.Blob => (T)(object)GetStream(offset),
1818
DuckDBType.Varchar => (T)(object)GetString(offset),
19-
_ => base.GetValidValue<T>(offset, targetType)
19+
_ => base.GetValidValue<T>(offset)
2020
};
2121
}
2222

@@ -99,4 +99,4 @@ private unsafe Stream GetStream(ulong offset)
9999

100100
return new UnmanagedMemoryStream((byte*)data->Data, data->Length, data->Length, FileAccess.Read);
101101
}
102-
}
102+
}

DuckDB.NET.Data/DataChunk/Reader/VectorDataReaderBase.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,13 @@ public unsafe bool IsValid(ulong offset)
4545
return isValid;
4646
}
4747

48+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
4849
public T GetValue<T>(ulong offset) => GetValue<T>(offset, strict: false);
4950

51+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5052
internal T GetValueStrict<T>(ulong offset) => GetValue<T>(offset, strict: true);
5153

54+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5255
internal T GetValue<T>(ulong offset, bool strict)
5356
{
5457
// When T is Nullable<TUnderlying> (e.g. int?), we can't call GetValidValue<int>() directly
@@ -62,7 +65,7 @@ internal T GetValue<T>(ulong offset, bool strict)
6265

6366
if (IsValid(offset))
6467
{
65-
return GetValidValue<T>(offset, typeof(T));
68+
return GetValidValue<T>(offset);
6669
}
6770

6871
if (strict || !NullableHandler<T>.IsReferenceType)
@@ -77,9 +80,8 @@ internal T GetValue<T>(ulong offset, bool strict)
7780
/// </summary>
7881
/// <typeparam name="T">Type of the return value</typeparam>
7982
/// <param name="offset">Position to read the data from</param>
80-
/// <param name="targetType">Type of the return value</param>
8183
/// <returns>Data at the specified offset</returns>
82-
protected virtual T GetValidValue<T>(ulong offset, Type targetType) => (T)GetValue(offset, targetType);
84+
protected virtual T GetValidValue<T>(ulong offset) => (T)GetValue(offset, typeof(T));
8385

8486
public object GetValue(ulong offset)
8587
{
@@ -215,7 +217,7 @@ static NullableHandler()
215217
// For T = int?, builds a delegate equivalent to:
216218
// (VectorDataReaderBase reader, ulong offset) =>
217219
// reader.IsValid(offset)
218-
// ? (int?)reader.GetValidValue<int>(offset, typeof(int))
220+
// ? (int?)reader.GetValidValue<int>(offset)
219221
// : default(int?)
220222
private static Func<VectorDataReaderBase, ulong, T> Compile()
221223
{
@@ -229,7 +231,7 @@ private static Func<VectorDataReaderBase, ulong, T> Compile()
229231
var methodInfo = typeof(VectorDataReaderBase).GetMethod(nameof(GetValidValue), BindingFlags.Instance | BindingFlags.NonPublic)!;
230232
var genericGetValidValue = methodInfo.MakeGenericMethod(underlyingType);
231233

232-
var getValidValue = Expression.Call(reader, genericGetValidValue, offset, Expression.Constant(underlyingType));
234+
var getValidValue = Expression.Call(reader, genericGetValidValue, offset);
233235

234236
var body = Expression.Condition(isValid, Expression.Convert(getValidValue, type), Expression.Default(type));
235237

0 commit comments

Comments
 (0)