Skip to content

Commit

Permalink
Struct explicit (#171)
Browse files Browse the repository at this point in the history
* Optimize GetOrdinal for repetitive access pattern

* fieldinfo fixed layout with overlap to reduce mem clear cost

* tweaks

* attempt to fix github build
  • Loading branch information
MarkPflug authored May 20, 2024
1 parent 61b8c6a commit 2294c73
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 63 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v1.7.2
uses: actions/setup-dotnet@v4
with:
dotnet-version: 6.0.100
dotnet-version: 6.0.422
- name: Restore dependencies
run: dotnet restore source
- name: Build
Expand Down
60 changes: 43 additions & 17 deletions source/Sylvan.Data.Excel/ExcelDataReader+FieldInfo.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
using System;
using System.Runtime.InteropServices;

namespace Sylvan.Data.Excel;

partial class ExcelDataReader
{
internal enum FieldType : int
{
Null = 0,
Numeric,
DateTime,
String,
SharedString,
Boolean,
Error,
}

[StructLayout(LayoutKind.Explicit)]
private protected struct FieldInfo
{
public static readonly FieldInfo Null = default;

public ExcelDataType type;
public bool isSS;
public string? strValue;
public int ssIdx;
public double numValue;
public DateTime dtValue;
public int xfIdx;
[FieldOffset(0)]
internal string? strValue;

[FieldOffset(8)]
internal bool boolValue;
[FieldOffset(8)]
internal int ssIdx;
[FieldOffset(8)]
internal double numValue;
[FieldOffset(8)]
internal DateTime dtValue;

[FieldOffset(16)]
internal FieldType type;

[FieldOffset(20)]
internal int xfIdx;



internal bool IsEmptyValue
{
get
{
return this.type == ExcelDataType.Null || (this.type == ExcelDataType.String && this.strValue?.Length == 0);
return this.type == FieldType.Null || (this.type == FieldType.String && this.strValue?.Length == 0);
}
}

Expand All @@ -32,31 +56,31 @@ internal ExcelErrorCode ErrorCode

internal bool BoolValue
{
get { return numValue != 0d; }
get { return boolValue; }
}

public FieldInfo(string str)
{
this = default;
this.type = ExcelDataType.String;
this.type = FieldType.String;
this.strValue = str;
}

public FieldInfo(bool b)
{
this = default;
this.type = ExcelDataType.Boolean;
this.numValue = b ? 1 : 0;
this.type = FieldType.Boolean;
this.boolValue = b;
}

public FieldInfo(ExcelErrorCode c)
{
this = default;
this.type = ExcelDataType.Error;
this.type = FieldType.Error;
this.numValue = (double)c;
}

public FieldInfo(uint val, ExcelDataType type)
public FieldInfo(uint val, FieldType type)
{
this = default;
this.numValue = val;
Expand All @@ -66,21 +90,23 @@ public FieldInfo(uint val, ExcelDataType type)
public FieldInfo(double val, ushort ifIdx)
{
this = default;
this.type = ExcelDataType.Numeric;
this.type = FieldType.Numeric;
this.numValue = val;
this.xfIdx = ifIdx;
}

#if DEBUG
public override string ToString()
{
switch (type)
{
case ExcelDataType.Numeric:
case FieldType.Numeric:
return "Double: " + numValue;
case ExcelDataType.String:
case FieldType.String:
return "String: " + strValue;
}
return "NULL";
}
#endif
}
}
61 changes: 44 additions & 17 deletions source/Sylvan.Data.Excel/ExcelDataReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,28 @@ public ExcelDataType GetExcelDataType(int ordinal)
ValidateAccess();
ValidateSheetRange(ordinal);
ref readonly var cell = ref GetFieldValue(ordinal);
return cell.type;
return MapFieldType(cell.type);
}

static ExcelDataType MapFieldType(FieldType t)
{
switch (t)
{
default: // never
case FieldType.Null:
return ExcelDataType.Null;
case FieldType.Boolean:
return ExcelDataType.Boolean;
case FieldType.DateTime:
return ExcelDataType.DateTime;
case FieldType.Numeric:
return ExcelDataType.Numeric;
case FieldType.String:
case FieldType.SharedString:
return ExcelDataType.String;
case FieldType.Error:
return ExcelDataType.Error;
}
}

/// <summary>
Expand Down Expand Up @@ -681,7 +702,7 @@ public ExcelErrorCode GetFormulaError(int ordinal)
{
ValidateAccess();
var cell = GetFieldValue(ordinal);
if (cell.type == ExcelDataType.Error)
if (cell.type == FieldType.Error)
return cell.ErrorCode;
throw new InvalidOperationException();
}
Expand Down Expand Up @@ -902,23 +923,27 @@ string GetStringRaw(int ordinal)

switch (fi.type)
{
case ExcelDataType.Error:
case FieldType.Error:
if (errorAsNull)
{
return string.Empty;
}
throw GetError(ordinal);
case ExcelDataType.Boolean:
case FieldType.Boolean:
return fi.BoolValue ? bool.TrueString : bool.FalseString;
case ExcelDataType.Numeric:
case FieldType.Numeric:
return FormatVal(fi.xfIdx, fi.numValue);
case FieldType.String:
return fi.strValue ?? "";
case FieldType.SharedString:
return GetSharedString(fi.ssIdx) ?? "";
}
return ProcString(fi);
return ProcString(in fi);
}

string ProcString(in FieldInfo fi)
string ProcString(ref readonly FieldInfo fi)
{
return (fi.isSS ? GetSharedString(fi.ssIdx) : fi.strValue) ?? string.Empty;
return (fi.type == FieldType.SharedString ? GetSharedString(fi.ssIdx) : fi.strValue) ?? string.Empty;
}

private protected abstract string GetSharedString(int idx);
Expand Down Expand Up @@ -955,11 +980,12 @@ public sealed override double GetDouble(int ordinal)
ref readonly var cell = ref GetFieldValue(ordinal);
switch (cell.type)
{
case ExcelDataType.String:
return double.Parse(ProcString(cell), culture);
case ExcelDataType.Numeric:
case FieldType.String:
case FieldType.SharedString:
return double.Parse(ProcString(in cell), culture);
case FieldType.Numeric:
return cell.numValue;
case ExcelDataType.Error:
case FieldType.Error:
throw Error(ordinal);
}

Expand All @@ -979,18 +1005,19 @@ public sealed override bool GetBoolean(int ordinal)
ref readonly var fi = ref this.GetFieldValue(ordinal);
switch (fi.type)
{
case ExcelDataType.Boolean:
case FieldType.Boolean:
return fi.BoolValue;
case ExcelDataType.Numeric:
case FieldType.Numeric:
return this.GetDouble(ordinal) != 0;
case ExcelDataType.String:
case FieldType.String:
case FieldType.SharedString:

var col = (uint)ordinal < this.columnSchema.Length ? this.columnSchema[ordinal] : null;

var trueString = col?.TrueString ?? this.trueString;
var falseString = col?.FalseString ?? this.falseString;

var strVal = ProcString(fi);
var strVal = ProcString(in fi);
var c = StringComparer.OrdinalIgnoreCase;

if (trueString != null && c.Equals(strVal, trueString))
Expand All @@ -1017,7 +1044,7 @@ public sealed override bool GetBoolean(int ordinal)
if (trueString == null && falseString != null) return true;

throw new InvalidCastException();
case ExcelDataType.Error:
case FieldType.Error:
var code = fi.ErrorCode;
throw new ExcelFormulaException(ordinal, RowNumber, code);
}
Expand Down
22 changes: 10 additions & 12 deletions source/Sylvan.Data.Excel/Xlsb/XlsbWorkbookReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ int ParseRowValues()
int count = 0;
int notNull = 0;

ExcelDataType type = 0;
FieldType type = FieldType.Null;

while (reader.RecordType != RecordType.Row)
{
Expand Down Expand Up @@ -385,7 +385,7 @@ static void EnsureCols(ref FieldInfo[] values, int c)
EnsureCols(ref values, col);
ref var fi = ref values[col];

fi.type = ExcelDataType.Numeric;
fi.type = FieldType.Numeric;
fi.numValue = d;
fi.xfIdx = sf;
notNull++;
Expand All @@ -399,7 +399,7 @@ static void EnsureCols(ref FieldInfo[] values, int c)
double d = reader.GetDouble(8);
EnsureCols(ref values, col);
ref var fi = ref values[col];
fi.type = ExcelDataType.Numeric;
fi.type = FieldType.Numeric;
fi.numValue = d;
fi.xfIdx = sf;
count = col + 1;
Expand All @@ -425,42 +425,40 @@ static void EnsureCols(ref FieldInfo[] values, int c)
switch (reader.RecordType)
{
case RecordType.CellBlank:
type = ExcelDataType.Null;
type = FieldType.Null;
break;
case RecordType.CellBool:
case RecordType.CellFmlaBool:
type = ExcelDataType.Boolean;
type = FieldType.Boolean;
fi = new FieldInfo(reader.GetByte(8) != 0);
notNull++;
break;
case RecordType.CellError:
case RecordType.CellFmlaError:
type = ExcelDataType.Error;
type = FieldType.Error;
fi = new FieldInfo((ExcelErrorCode)reader.GetByte(8));
notNull++;
break;
case RecordType.CellIsst:
type = ExcelDataType.String;
var sstIdx = reader.GetInt32(8);
fi.isSS = true;
type = FieldType.SharedString;
var sstIdx = reader.GetInt32(8);
fi.ssIdx = sstIdx;
notNull++;
break;
case RecordType.CellSt:
case RecordType.CellFmlaString:
type = ExcelDataType.String;
type = FieldType.String;
fi.strValue = reader.GetString(8);
if (fi.strValue.Length > 0)
notNull++;
break;
case RecordType.CellFmlaNum:
type = ExcelDataType.Numeric;
type = FieldType.Numeric;
fi.numValue = reader.GetDouble(8);
notNull++;
break;
}


fi.type = type;
fi.xfIdx = sf;
count = col + 1;
Expand Down
Loading

0 comments on commit 2294c73

Please sign in to comment.