README
View on GitHub ↗Sep - the World's Fastest .NET CSV Parser
![]()
Modern, minimal, fast, zero allocation, reading and writing of separated values
(csv, tsv etc.). Cross-platform, trimmable and AOT/NativeAOT compatible.
Featuring an opinionated API design and pragmatic implementation targeted at
machine learning use cases.
⭐ Please star this project if you like it. ⭐
🌃 Modern - utilizes features such as
Span<T>,
Generic Math
(ISpanParsable<T>/
ISpanFormattable),
ref struct,
ArrayPool<T>
and similar from .NET 7+ and C#
11+ for a modern
and highly efficient implementation.
🔎 Minimal - a succinct yet expressive API with few options and no hidden changes to input or output. What you read/write is what you get. E.g. by default there is no "automatic" escaping/unescaping of quotes or trimming of spaces. To enable this see SepReaderOptions and Unescaping and Trimming. See SepWriterOptions for Escaping.
🚀 Fast - blazing fast with both architecture specific and cross-platform SIMD vectorized parsing incl. 64/128/256/512-bit paths e.g. AVX2, AVX-512 (.NET 8.0+), NEON. Uses csFastFloat for fast parsing of floating points. See detailed benchmarks for cross-platform results.
🌪️ Multi-threaded - unparalleled speed with highly efficient parallel CSV parsing that is up to 35x faster than CsvHelper, see ParallelEnumerate and benchmarks.
🌀 Async support - efficient ValueTask based async/await support.
Requires C# 13.0+ and for .NET 9.0+ includes SepReader implementing
IAsyncEnumerable<>. See Async Support for details.
🗑️ Zero allocation - intelligent and efficient memory management allowing for zero allocations after warmup incl. supporting use cases of reading or writing arrays of values (e.g. features) easily without repeated allocations.
✅ Thorough tests - great code coverage and focus on edge case testing incl. randomized fuzz testing.
🌐 Cross-platform - works on any platform, any architecture supported by NET. 100% managed and written in beautiful modern C#.
✂️ Trimmable and AOT/NativeAOT compatible - no problematic reflection or dynamic code generation. Hence, fully trimmable and Ahead-of-Time compatible. With a simple console tester program executable possible in just a few MBs. 💾
🗣️ Opinionated and pragmatic - conforms to the essentials of RFC-4180, but takes an opinionated and pragmatic approach towards this especially with regards to quoting and line ends. See section RFC-4180.
Example | Naming and Terminology | API | Limitations and Constraints | Comparison Benchmarks | Example Catalogue | RFC-4180 | FAQ | Public API Reference
Example
var text = """
A;B;C;D;E;F
Sep;🚀;1;1.2;0.1;0.5
CSV;✅;2;2.2;0.2;1.5
""";
using var reader = Sep.Reader().FromText(text); // Infers separator 'Sep' (`;`) from header
using var writer = reader.Spec.Writer().ToText(); // Writer defined from reader 'Spec'
// Use .FromFile(...)/ToFile(...) for files
var idx = reader.Header.IndexOf("B");
var nms = new[] { "E", "F" };
foreach (var readRow in reader) // Read one row at a time
{
var a = readRow["A"].Span; // Column as ReadOnlySpan<char>
var b = readRow[idx].ToString(); // Column to string (might be pooled)
var c = readRow["C"].Parse<int>(); // Parse any T : ISpanParsable<T>
var d = readRow["D"].Parse<float>(); // Parse float/double fast via csFastFloat
var s = readRow[nms].Parse<double>(); // Parse multiple columns as Span<T>
// - Sep handles array allocation and reuse
foreach (ref var v in s) { v *= 10; }
using var writeRow = writer.NewRow(); // Start new row. Row written on Dispose.
writeRow["A"].Set(a); // Set by ReadOnlySpan<char>
writeRow["B"].Set(b); // Set by string
writeRow["C"].Set($"{c * 2}"); // Set via InterpolatedStringHandler, no allocs
writeRow["D"].Format(d / 2); // Format any T : ISpanFormattable
writeRow[nms].Format(s); // Format multiple columns directly
// Columns are added on first access as ordered, header written when first row written
}
var expected = """
A;B;C;D;E;F
Sep;🚀;2;0.6;1;5
CSV;✅;4;1.1;2;15
"""; // Empty line at end is for line ending,
// which is always written.
Assert.AreEqual(expected, writer.ToString());
// Above example code is for demonstration purposes only.
// Short names and repeated constants are only for demonstration.
For more examples, incl. how to write and read objects (e.g. records) with
escape/unescape support, see Example Catalogue.
Naming and Terminology
Sep uses naming and terminology that is not based on RFC-4180, but
is more tailored to usage in machine learning or similar. Additionally, Sep
takes a pragmatic approach towards names by using short names and abbreviations
where it makes sense and there should be no ambiguity given the context. That
is, using Sep for Separator and Col for Column to keep code succinct.
| Term | Description |
|---|---|
Sep |
Short for separator, also called delimiter. E.g. comma (,) is the separator for the separated values in a csv-file. |
Header |
Optional first row defining names of columns. |
Row |
A row is a collection of col(umn)s, which may span multiple lines. Also called record. |
Col |
Short for column, also called field. |
Line |
Horizontal set of characters until a line ending; \r\n, \r, \n. |
Index |
0-based that is RowIndex will be 0 for first row (or the header if present). |
Number |
1-based that is LineNumber will be 1 for the first line (as in notepad). Given a row may span multiple lines a row can have a From line number and a ToExcl line number matching the C# range indexing syntax [LineNumberFrom..LineNumberToExcl]. |
Application Programming Interface (API)
Besides being the succinct name of the library, Sep is both the main entry
point to using the library and the container for a validated separator. That is,
Sep is basically defined as:
public readonly record struct Sep(char Separator);
The separator char is validated upon construction and is guaranteed to be
within a limited range and not being a char like " (quote) or similar. This
can be seen in src/Sep/Sep.cs. The separator is constrained
also for internal optimizations, so you cannot use any char as a separator.
⚠ Note that all types are within the namespace nietras.SeparatedValues and not
Sep since it is problematic to have a type and a namespace with the same name.
To get started you can use Sep as the static entry point to building either a
reader or writer. That is, for SepReader:
using var reader = Sep.Reader().FromFile("titanic.csv");
where .Reader() is a convenience method corresponding to:
using var reader = Sep.Auto.Reader().FromFile("titanic.csv");
where Sep? Auto => null; is a static property that returns null for a
nullable Sep to signify that the separator should be inferred from the first
row, which might be a header. If the first row does not contain any of the by
default supported separators or there are no rows, the default separator will be
used.
⚠ Note Sep uses ; as the default separator, since this is what was used in an
internal proprietary library which Sep was built to replace. This is also to
avoid issues with comma , being used as a decimal separator in some locales.
Without having to resort to quoting.
If you want to specify the separator you can write:
using var reader = Sep.New(',').Reader().FromFile("titanic.csv");
or
var sep = new Sep(',');
using var reader = sep.Reader().FromFile("titanic.csv");
Similarly, for SepWriter:
using var writer = Sep.Writer().ToFile("titanic.csv");
or
using var writer = Sep.New(',').Writer().ToFile("titanic.csv");
where you have to specify a valid separator, since it cannot be inferred. To
facilitate easy flow of the separator and CultureInfo both SepReader and
SepWriter expose a Spec property of type SepSpec that simply defines those
two. This means you can write:
using var reader = Sep.Reader().FromFile("titanic.csv");
using var writer = reader.Spec.Writer().ToFile("titanic-survivors.csv");
where the writer then will use the separator inferred by the reader, for
example.
API Pattern
In general, both reading and writing follow a similar pattern:
Sep/Spec => SepReaderOptions => SepReader => Row => Col(s) => Span/ToString/Parse
Sep/Spec => SepWriterOptions => SepWriter => Row => Col(s) => Set/Format
where each continuation flows fluently from the preceding type. For example,
Reader() is an extension method to Sep or SepSpec that returns a
SepReaderOptions. Similarly, Writer() is an extension method to Sep or
SepSpec that returns a SepWriterOptions.
SepReaderOptions and
SepWriterOptions are optionally configurable.
That and the APIs for reader and writer is covered in the following sections.
For a complete example, see the example above or the ReadMeTest.cs.
⚠ Note that it is important to understand that Sep Row/Col/Cols are ref structs
(please follow the ref struct link and understand how this limits the usage of
those). This is due to these types being simple facades or indirections to the
underlying reader or writer. That means you cannot use LINQ or create an array
of all rows like reader.ToArray(). While for .NET9+ the reader is now
IEnumerable<> since ref structs can now be used in interfaces that have
where T: allows ref struct
this still does not mean it is LINQ compatible. Hence, if you need store per row
state or similar you need to parse or copy to different types instead. The same
applies to Col/Cols which point to internal state that is also reused. This
is to avoid repeated allocations for each row and get the best possible
performance, while still defining a well structured and straightforward API that
guides users to relevant functionality. See Why SepReader Was Not IEnumerable
Until .NET 9 and Is Not LINQ
Compatible
for more.
⚠ For a full overview of public types and methods see Public API Reference.
SepReader API
SepReader API has the following structure (in pseudo-C# code):
using var reader = Sep.Reader(o => o).FromFile/FromText/From...;
var header = reader.Header;
var _ = header.IndexOf/IndicesOf/NamesStartingWith...;
foreach (var row in reader)
{
var _ = row[colName/colNames].Span/ToString/Parse<T>...;
var _ = row[colIndex/colIndices].Span/ToString/Parse<T>...;
}
That is, to use SepReader follow the points below:
- Optionally define
Sepor use default automatically inferred separator. - Specify reader with optional configuration of
SepReaderOptions. For example, if a csv-file does not have a header this can be configured via:
For all options see SepReaderOptions.Sep.Reader(o => o with { HasHeader = false }) - Specify source e.g. file, text (
string),TextWriter, etc. viaFromextension methods. - Optionally access the header. For example, to get all columns starting with
GT_use:var colNames = header.NamesStartingWith("GT_"); var colIndices = header.IndicesOf(colNames); - Enumerate rows. One row at a time.
- Access a column by name or index. Or access multiple columns with names and
indices.
Sepinternally handles pooled allocation and reuse of arrays for multiple columns. - Use
Spanto access the column directly as aReadOnlySpan<char>. Or useToStringto convert to astring. Or useParse<T>whereT : ISpanParsable<T>to parse the columnchars to a specific type.
SepReaderOptions
The following options are available:
/// <summary>
/// Specifies the separator used, if `null` then automatic detection
/// is used based on first row in source.
/// </summary>
public Sep? Sep { get; init; } = null;
/// <summary>
/// Specifies initial internal `char` buffer length.
/// </summary>
/// <remarks>
/// The length will likely be rounded up to the nearest power of 2. A
/// smaller buffer may end up being used if the underlying source for <see
/// cref="System.IO.TextReader"/> is known to be smaller. Prefer to keep the
/// default length as that has been tuned for performance and cache sizes.
/// Avoid making this unnecessarily large as that will likely not improve
/// performance and may waste memory.
/// </remarks>
public int InitialBufferLength { get; init; } = SepDefaults.InitialBufferLength;
/// <summary>
/// Specifies the culture used for parsing.
/// May be `null` for default culture.
/// </summary>
public CultureInfo? CultureInfo { get; init; } = SepDefaults.CultureInfo;
/// <summary>
/// Indicates whether the first row is a header row.
/// </summary>
public bool HasHeader { get; init; } = true;
/// <summary>
/// Specifies <see cref="IEqualityComparer{T}" /> to use
/// for comparing header column names and looking up index.
/// </summary>
public IEqualityComparer<string> ColNameComparer { get; init; } = SepDefaults.ColNameComparer;
/// <summary>
/// Specifies the method factory used to convert a column span
/// of `char`s to a `string`.
/// </summary>
public SepCreateToString CreateToString { get; init; } = SepToString.Direct;
/// <summary>
/// Disables using [csFastFloat](https://github.com/CarlVerret/csFastFloat)
/// for parsing `float` and `double`.
/// </summary>
public bool DisableFastFloat { get; init; } = false;
/// <summary>
/// Disables checking if column count is the same for all rows.
/// </summary>
public bool DisableColCountCheck { get; init; } = false;
/// <summary>
/// Disables detecting and parsing quotes.
/// </summary>
public bool DisableQuotesParsing { get; init; } = false;
/// <summary>
/// Unescape quotes on column access.
/// </summary>
/// <remarks>
/// When true, if a column starts with a quote then the two outermost quotes
/// are removed and every second inner quote is removed. Note that
/// unquote/unescape happens in-place, which means the <see
/// cref="SepReader.Row.Span" /> (<b>row</b> span) will be modified and
/// contain "garbage" state after unescaped cols before next col. This is
/// for efficiency to avoid allocating secondary memory for unescaped
/// columns. That is, the column span <see cref="SepReader.Col.Span" /> will
/// be correct with unescaped text, but the row span <see
/// cref="SepReader.Row.Span" /> may have "garbage" state between columns if
/// any column had quotes removed. Header columns/names will also be
/// unescaped. Requires <see cref="DisableQuotesParsing"/> to be false.
/// </remarks>
public bool Unescape { get; init; } = false;
/// <summary>
/// Option for trimming spaces (` ` - ASCII 32) on column access.
/// </summary>
/// <remarks>
/// By default no trimming is done. See <see cref="SepTrim"/> for options.
/// Note that trimming may happen in-place e.g. if also unescaping, which
/// means the <see cref="SepReader.Row.Span" /> will be modified and contain
/// "garbage" state for trimmed/unescaped cols. This is for efficiency to
/// avoid allocating secondary memory for trimmed/unescaped columns. That
/// is, the column span <see cref="SepReader.Col.Span" /> will be correct
/// with trimmed text, but the row span <see cref="SepReader.Row.Span" />
/// may have "garbage" state between columns if any column had spaces
/// removed. Header columns/names will also be trimmed. Note that only the
/// space ` ` (ASCII 32) character is trimmed, not any whitespace character.
/// </remarks>
public SepTrim Trim { get; init; } = SepTrim.None;
/// <summary>
/// Forwarded to <see
/// cref="System.Threading.Tasks.ValueTask.ConfigureAwait(bool)"/> or
/// similar when async methods are called.
/// </summary>
public bool AsyncContinueOnCapturedContext { get; init; } = false;
Unescaping
While great care has been taken to ensure Sep unescaping of quotes is both correct and fast, there is always the question of how does one respond to invalid input.
The below table tries to summarize the behavior of Sep vs CsvHelper and Sylvan. Note that all do the same for valid input. There are differences for how invalid input is handled. For Sep the design choice has been based on not wanting to throw exceptions and to use a principle that is both reasonably fast and simple.
| Input | Valid | CsvHelper | CsvHelper¹ | Sylvan | Sep² |
|---|---|---|---|---|---|
a |
True | a |
a |
a |
a |
"" |
True | ||||
"""" |
True | " |
" |
" |
" |
"""""" |
True | "" |
"" |
"" |
"" |
"a" |
True | a |
a |
a |
a |
"a""a" |
True | a"a |
a"a |
a"a |
a"a |
"a""a""a" |
True | a"a"a |
a"a"a |
a"a"a |
a"a"a |
a""a |
False | EXCEPTION | a""a |
a""a |
a""a |
a"a"a |
False | EXCEPTION | a"a"a |
a"a"a |
a"a"a |
·""· |
False | EXCEPTION | ·""· |
·""· |
·""· |
·"a"· |
False | EXCEPTION | ·"a"· |
·"a"· |
·"a"· |
·"" |
False | EXCEPTION | ·"" |
·"" |
·"" |
·"a" |
False | EXCEPTION | ·"a" |
·"a" |
·"a" |
a"""a |
False | EXCEPTION | a"""a |
a"""a |
a"""a |
"a"a"a" |
False | EXCEPTION | aa"a" |
a"a"a |
aa"a |
""· |
False | EXCEPTION | · |
" |
· |
"a"· |
False | EXCEPTION | a· |
a" |
a· |
"a"""a |
False | EXCEPTION | aa |
EXCEPTION | a"a |
"a"""a" |
False | EXCEPTION | aa" |
a"a<NULL> |
a"a" |
""a" |
False | EXCEPTION | a" |
"a |
a" |
"a"a" |
False | EXCEPTION | aa" |
a"a |
aa" |
""a"a"" |
False | EXCEPTION | a"a"" |
"a"a" |
a"a" |
""" |
False | EXCEPTION | " |
||
""""" |
False | " |
" |
EXCEPTION | "" |
· (middle dot) is whitespace to make this visible
¹ CsvHelper with BadDataFound = null
² Sep with Unescape = true in SepReaderOptions
Trimming
Sep supports trimming by the SepTrim flags enum, which
has two options as documented there. Below the result of both trimming and
unescaping is shown in comparison to CsvHelper. Note unescaping is enabled for
all results shown. It is possible to trim without unescaping too, of course.
As can be seen Sep supports a simple principle of trimming before and after unescaping with trimming before unescaping being important for unescaping if there is a starting quote after spaces.
| Input | CsvHelper Trim | CsvHelper InsideQuotes | CsvHelper All¹ | Sep Outer | Sep AfterUnescape | Sep All² |
|---|---|---|---|---|---|---|
a |
a |
a |
a |
a |
a |
a |
·a |
a |
·a |
a |
a |
a |
a |
a· |
a |
a· |
a |
a |
a |
a |
·a· |
a |
·a· |
a |
a |
a |
a |
·a·a· |
a·a |
·a·a· |
a·a |
a·a |
a·a |
a·a |
"a" |
a |
a |
a |
a |
a |
a |
"·a" |
·a |
a |
a |
·a |
a |
a |
"a·" |
a· |
a |
a |
a· |
a |
a |
"·a·" |
·a· |
a |
a |
·a· |
a |
a |
"·a·a·" |
·a·a· |
a·a |
a·a |
·a·a· |
a·a |
a·a |
·"a"· |
a |
·"a"· |
a |
a |
"a" |
a |
·"·a"· |
·a |
·"·a"· |
a |
·a |
"·a" |
a |
·"a·"· |
a· |
·"a·"· |
a |
a· |
"a·" |
a |
·"·a·"· |
·a· |
·"·a·"· |
a |
·a· |
"·a·" |
a |
·"·a·a·"· |
·a·a· |
·"·a·a·"· |
a·a |
·a·a· |
"·a·a·" |
a·a |
· (middle dot) is whitespace to make this visible
¹ CsvHelper with TrimOptions.Trim | TrimOptions.InsideQuotes
² Sep with SepTrim.All = SepTrim.Outer | SepTrim.AfterUnescape in
SepReaderOptions
SepReader Debuggability
Debuggability is an important part of any library and in Sep. SepReader has a
unique feature when looking at it and its rows or cols in a debug context. Given
the following example code:
var text = """
Key;Value
A;"1
2
3"
B;"Apple
Banana
Orange
Pear"
""";
using var reader = Sep.Reader().FromText(text);
foreach (var row in reader)
{
// Hover over reader, row or col when breaking here
var col = row[1];
if (Debugger.IsAttached && row.RowIndex == 2) { Debugger.Break(); }
Debug.WriteLine(col.ToString());
}
and you are hovering over reader when the break is triggered then this will
show something like:
String Length=55
That is, it will show information of the source for the reader, in this case a string of length 55.
SepReader.Row Debuggability
If you are hovering over row then this will show something like:
2:[5..9] = "B;\"Apple\r\nBanana\r\nOrange\r\nPear\""
This has the format shown below.
<ROWINDEX>:[<LINENUMBERRANGE>] = "<ROW>"
Note how this shows line number range [FromIncl..ToExcl], as in C# range
expression,
so that one can easily find the row in question in notepad or similar. This
means Sep has to track line endings inside quotes and is an example of a feature
that makes Sep a bit slower but which is a price considered worth paying.
GitHub doesn't show line numbers in code blocks so consider copying the example text to notepad or similar to see the effect.
Additionally, if you expand the row in the debugger (e.g. via the small
triangle) you will see each column of the row similar to below.
00:'Key' = "B"
01:'Value' = "\"Apple\r\nBanana\r\nOrange\r\nPear\""
SepReader.Col Debuggability
If you hover over col you should see:
"\"Apple\r\nBanana\r\nOrange\r\nPear\""
Why SepReader Was Not IEnumerable Until .NET 9 and Is Not LINQ Compatible
As mentioned earlier Sep only allows enumeration and access to one row at a time
and SepReader.Row is just a simple facade or indirection to the underlying
reader. This is why it is defined as a ref struct. In fact, the following
code:
using var reader = Sep.Reader().FromText(text);
foreach (var row in reader)
{ }
can also be rewritten as:
using var reader = Sep.Reader().FromText(text);
while (reader.MoveNext())
{
var row = reader.Current;
}
where row is just a facade for exposing row specific functionality. That is,
row is still basically the reader underneath. Hence, let's look at using
LINQ with SepReader implementing IEnumerable<SepReader.Row> and the Row
not being a ref struct. Then, you would be able to write something like below:
using var reader = Sep.Reader().FromText(text);
SepReader.Row[] rows = reader.ToArray();
Given Row is just a facade for the reader, this would be equivalent to
writing:
using var reader = Sep.Reader().FromText(text);
SepReader[] rows = reader.ToArray();
which hopefully makes it clear why this is not a good thing. The array would
effectively be the reader repeated several times. If this would have to be
supported one would have to allocate memory for each row always, which would
basically be no different than a ReadLine approach as benchmarked in
Comparison Benchmarks.
This is perhaps also the reason why no other efficient .NET CSV parser (known to author) implements an API pattern like Sep, but instead let the reader define all functionality directly and hence only lets you access the current row and cols on that. This API, however, is in this author's opinion not ideal and can be a bit confusing, which is why Sep is designed like it is. The downside is the above caveat.
The main culprit above is that for example ToArray() would store a ref struct in a heap allocated array, the actual enumeration is not a problem and
hence implementing IEnumerable<SepReader.Row> is not the problem as such. The
problem was that prior to .NET 9 it was not possible to implement this interface
with T being a ref struct, but with C# 13 allows ref struct and .NET 9
having annotated such interfaces it is now possible and you can assign
SepReader to IEnumerable, but most if not all of LINQ will still not work as
shown below.
var text = """
Key;Value
A;1.1
B;2.2
""";
using var reader = Sep.Reader().FromText(text);
IEnumerable<SepReader.Row> enumerable = reader;
// Currently, most LINQ methods do not work for ref types. See below.
//
// The type 'SepReader.Row' may not be a ref struct or a type parameter
// allowing ref structs in order to use it as parameter 'TSource' in the
// generic type or method 'Enumerable.Select<TSource,
// TResult>(IEnumerable<TSource>, Func<TSource, TResult>)'
//
// enumerable.Select(row => row["Key"].ToString()).ToArray();
Calling Select should in principle be possible if this was annotated with allows ref struct, but it isn't currently.
If you want to use LINQ or similar you have to first parse or transform the rows
into some other type and enumerate it. This is easy to do and instead of
counting lines you should focus on how such enumeration can be easily expressed
using C# iterators (aka yield return). With local functions this can be done
inside a method like:
var text = """
Key;Value
A;1.1
B;2.2
""";
var expected = new (string Key, double Value)[] {
("A", 1.1),
("B", 2.2),
};
using var reader = Sep.Reader().FromText(text);
var actual = Enumerate(reader).ToArray();
CollectionAssert.AreEqual(expected, actual);
static IEnumerable<(string Key, double Value)> Enumerate(SepReader reader)
{
foreach (var row in reader)
{
yield return (row["Key"].ToString(), row["Value"].Parse<double>());
}
}
Now if instead refactoring this to something LINQ-compatible by defining a
common Enumerate or similar method it could be:
var text = """
Key;Value
A;1.1
B;2.2
""";
var expected = new (string Key, double Value)[] {
("A", 1.1),
("B", 2.2),
};
using var reader = Sep.Reader().FromText(text);
var actual = Enumerate(reader,
row => (row["Key"].ToString(), row["Value"].Parse<double>()))
.ToArray();
CollectionAssert.AreEqual(expected, actual);
static IEnumerable<T> Enumerate<T>(SepReader reader, SepReader.RowFunc<T> select)
{
foreach (var row in reader)
{
yield return select(row);
}
}
In fact, Sep provides such a convenience extension method. And, discounting the
Enumerate method, this does have less boilerplate, but not really more
effective lines of code. The issue here is that this tends to favor factoring
code in a way that can become very inefficient quickly. Consider if one wanted
to only enumerate rows matching a predicate on Key which meant only 1% of rows
were to be enumerated e.g.:
var text = """
Key;Value
A;1.1
B;2.2
""";
var expected = new (string Key, double Value)[] {
("B", 2.2),
};
using var reader = Sep.Reader().FromText(text);
var actual = reader.Enumerate(
row => (row["Key"].ToString(), row["Value"].Parse<double>()))
.Where(kv => kv.Item1.StartsWith('B'))
.ToArray();
CollectionAssert.AreEqual(expected, actual);
This means you are still parsing the double (which is magnitudes slower than
getting just the key) for all rows. Imagine if this was an array of floating
points or similar. Not only would you then be parsing a lot of values you would
also be allocated 99x arrays that aren't used after filtering with Where.
Instead, you should focus on how to express the enumeration in a way that is both efficient and easy to read. For example, the above could be rewritten as:
var text = """
Key;Value
A;1.1
B;2.2
""";
var expected = new (string Key, double Value)[] {
("B", 2.2),
};
using var reader = Sep.Reader().FromText(text);
var actual = Enumerate(reader).ToArray();
CollectionAssert.AreEqual(expected, actual);
static IEnumerable<(string Key, double Value)> Enumerate(SepReader reader)
{
foreach (var row in reader)
{
var keyCol = row["Key"];
if (keyCol.Span.StartsWith("B"))
{
yield return (keyCol.ToString(), row["Value"].Parse<double>());
}
}
}
To accommodate this Sep provides an overload for Enumerate that is similar to:
static IEnumerable<T> Enumerate<T>(this SepReader reader, SepReader.RowTryFunc<T> trySelect)
{
foreach (var row in reader)
{
if (trySelect(row, out var value))
{
yield return value;
}
}
}
With this the above custom Enumerate can be replaced with:
var text = """
Key;Value
A;1.1
B;2.2
""";
var expected = new (string Key, double Value)[] {
("B", 2.2),
};
using var reader = Sep.Reader().FromText(text);
var actual = reader.Enumerate((SepReader.Row row, out (string Key, double Value) kv) =>
{
var keyCol = row["Key"];
if (keyCol.Span.StartsWith("B"))
{
kv = (keyCol.ToString(), row["Value"].Parse<double>());
return true;
}
kv = default;
return false;
}).ToArray();
CollectionAssert.AreEqual(expected, actual);
Note how this is pretty much the same length as the previous custom Enumerate.
Also worse due to how C# requires specifying types for out parameters which
then requires all parameter types for the lambda to be specified. Hence, in this
case the custom Enumerate does not take significantly longer to write and is a
lot more efficient than using LINQ .Where (also avoids allocating a string for
key for each row) and is easier to debug and perhaps even read. All examples
above can be seen in ReadMeTest.cs.
There is a strong case for having an enumerate API though and that is for parallelized enumeration, which will be discussed next.
ParallelEnumerate and Enumerate
As discussed in the previous section Sep provides Enumerate convenience
extension methods, that should be used carefully. Alongside these there are
ParallelEnumerate extension methods that provide very efficient multi-threaded
enumeration. See benchmarks for numbers and Public
API Reference.
ParallelEnumerate is built on top of LINQ AsParallel().AsOrdered() and will
return exactly the same as Enumerate but with enumeration parallelized. This
will use more memory during execution and as many threads as possible via the
.NET thread pool. When using ParallelEnumerate one should, therefore (as
always), be certain the provided delegate does not refer to or change any
mutable state.
ParallelEnumerate comes with a lot of overhead compared to single-threaded
foreach or Enumerate and should be used carefully based on measuring any
potential benefit. Sep goes a long way to make this very efficient by using
pooled arrays and parsing multiple rows in batches, but if the source only has a
few rows then any benefit is unlikely.
Due to ParallelEnumerate being based on batches of rows it is also important
not to "abuse" it in-place of LINQ AsParallel. The idea is to use it for
parsing rows, not for doing expensive per row operations like loading an image
or similar. In that case, you are better off using AsParallel() after
ParallelEnumerate or Enumerate similarly to:
using var reader = Sep.Reader().FromFile("very-long.csv");
var results = reader.ParallelEnumerate(ParseRow)
.AsParallel().AsOrdered()
.Select(LoadData) // Expensive load
.ToList();
As a rule of thumb if the time per row exceeds 1 millisecond consider moving the
expensive work to after ParallelEnumerate/Enumerate,
SepWriter API
SepWriter API has the following structure (in pseudo-C# code):
using var writer = Sep.Writer(o => o).ToFile/ToText/To...;
foreach (var data in EnumerateData())
{
using var row = writer.NewRow();
var _ = row[colName/colNames].Set/Format<T>...;
var _ = row[colIndex/colIndices].Set/Format<T>...;
}
That is, to use SepWriter follow the points below:
- Optionally define
Sepor use default automatically inferred separator. - Specify writer with optional configuration of
SepWriterOptions. For all options see SepWriterOptions. - Specify destination e.g. file, text (
stringviaStringWriter),TextWriter, etc. viaToextension methods. SepWriterdoes allow you to define the header up front viaSepWriter.Header.Add(...). However, header is usually defined by the order in which column names are accessed/created when defining the row.- Define new rows with
NewRow. ⚠ Be sure to dispose any new rows before starting the next! For convenience Sep provides an overload forNewRowthat takes aSepReader.Rowand copies the columns from that row to the new row:using var reader = Sep.Reader().FromText(text); using var writer = reader.Spec.Writer().ToText(); foreach (var readRow in reader) { using var writeRow = writer.NewRow(readRow); } - Create a column by selecting by name or index. Or multiple columns via
indices and names.
Sepinternally handles pooled allocation and reuse of arrays for multiple columns. - Use
Setto set the column value either as aReadOnlySpan<char>,stringor via an interpolated string. Or useFormat<T>whereT : IFormattableto formatTto the column value. - Row is written when
Disposeis called on the row.Note this is to allow a row to be defined flexibly with both column removal, moves and renames in the future. This is not yet supported.
SepWriterOptions
The following options are available:
/// <summary>
/// Specifies the separator used.
/// </summary>
public Sep Sep { get; init; }
/// <summary>
/// Specifies the culture used for formatting.
/// May be `null` for default culture.
/// </summary>
public CultureInfo? CultureInfo { get; init; }
/// <summary>
/// Specifies whether to write a header row
/// before data rows. Requires all columns
/// to have a name. Otherwise, columns can be
/// added by indexing alone.
/// </summary>
public bool WriteHeader { get; init; } = true;
/// <summary>
/// Disables checking if column count is the
/// same for all rows.
/// </summary>
/// <remarks>
/// When true, the <see cref="ColNotSetOption"/>
/// will define how columns that are not set
/// are handled. For example, whether to skip
/// or write an empty column if a column has
/// not been set for a given row.
/// <para>
/// If any columns are skipped, then columns of
/// a row may, therefore, be out of sync with
/// column names if <see cref="WriteHeader"/>
/// is true.
/// </para>
/// As such, any number of columns can be
/// written as long as done sequentially.
/// </remarks>
public bool DisableColCountCheck { get; init; } = false;
/// <summary>
/// Specifies how to handle columns that are
/// not set.
/// </summary>
public SepColNotSetOption ColNotSetOption { get; init; } = SepColNotSetOption.Throw;
/// <summary>
/// Specifies whether to escape column names
/// and values when writing.
/// </summary>
/// <remarks>
/// When true, if a column contains a separator
/// (e.g. `;`), carriage return (`\r`), line
/// feed (`\n`) or quote (`"`) then the column
/// is prefixed and suffixed with quotes `"`
/// and any quote in the column is escaped by
/// adding an extra quote so it becomes `""`.
/// Note that escape applies to column names
/// too, but only the written name.
/// </remarks>
public bool Escape { get; init; } = false;
/// <summary>
/// Forwarded to <see
/// cref="System.Threading.Tasks.ValueTask.ConfigureAwait(bool)"/> or
/// similar when async methods are called.
/// </summary>
public bool AsyncContinueOnCapturedContext { get; init; } = false;
Escaping
Escaping is not enabled by default in Sep, but when it is it gives the same results as other popular CSV libraries as shown below. Although, CsvHelper appears to be escaping spaces as well, which is not necessary.
| Input | CsvHelper | Sylvan | Sep¹ |
|---|---|---|---|
| `` | |||
· |
"·" |
· |
· |
a |
a |
a |
a |
; |
";" |
";" |
";" |
, |
, |
, |
, |
" |
"""" |
"""" |
"""" |
\r |
"\r" |
"\r" |
"\r" |
\n |
"\n" |
"\n" |
"\n" |
a"aa"aaa |
"a""aa""aaa" |
"a""aa""aaa" |
"a""aa""aaa" |
a;aa;aaa |
"a;aa;aaa" |
"a;aa;aaa" |
"a;aa;aaa" |
Separator/delimiter is set to semi-colon ; (default for Sep)
· (middle dot) is whitespace to make this visible
\r, \n are carriage return and line feed special characters to make these visible
¹ Sep with Escape = true in SepWriterOptions
Async Support
Sep supports efficient ValueTask based asynchronous reading and writing.
However, given both SepReader.Row and SepWriter.Row are ref structs, as
they point to internal state and should only be used one at a time,
async/await usage is only supported on C# 13.0+ as this has support for "ref
and unsafe in iterators and async methods" as covered in What's new in C#
13. Please
consult details in that for limitations and constraints due to this.
Similarly, SepReader only implements IAsyncEnumerable<SepReader.Row> (and
IEnumerable<SepReader.Row>) for .NET 9.0+/C# 13.0+ since then the interfaces
have been annotated with allows ref struct for T.
Async support is provided on the existing SepReader and SepWriter types
similar to how TextReader and TextWriter support both sync and async usage.
This means you as a developer are responsible for calling async methods and
using await when necessary. See below for a simple example and consult tests
on GitHub for more examples.
var text = """
A;B;C;D;E;F
Sep;🚀;1;1.2;0.1;0.5
CSV;✅;2;2.2;0.2;1.5
"""; // Empty line at end is for line ending
using var reader = await Sep.Reader().FromTextAsync(text);
await using var writer = reader.Spec.Writer().ToText();
await foreach (var readRow in reader)
{
await using var writeRow = writer.NewRow(readRow);
}
Assert.AreEqual(text, writer.ToString());
Note how for SepReader the FromTextAsync is suffixed with Async to
indicate async creation, this is due to the reader having to read the first row
of the source at creation to determine both separator and, if file has a header,
column names of the header. The From*Async call then has to be awaited.
After that rows can be enumerated asynchronously simply by putting await
before foreach. If one forgets to do that the rows will be enumerated
synchronously.
For SepWriter the usage is kind of reversed. To* methods have no Async
variants, since creation is synchronous. That is, StreamWriter is created by a
simple constructor call. Nothing is written until a header or row is defined and
Dispose/DisposeAsync is called on the row.
For reader nothing needs to be asynchronously disposed, so using does not
require await. However, for SepWriter dispose may have to write/flush data
to underlying TextWriter and hence it should be using DisposeAsync, so you
must use await using.
To support cancellation many methods have overloads that accept a
CancellationToken like the From*Async methods for creating a SepReader or
for example NewRow for SepWriter. Consult Public API
Reference for full set of available methods.
Additionally, both SepReaderOptions and
SepWriterOptions feature the bool AsyncContinueOnCapturedContext option that is forwarded to internal
ConfigureAwait calls, see the ConfigureAwait
FAQ for details on
that.
Limitations and Constraints
Sep is designed to be minimal and fast. As such, it has some limitations and constraints:
- Comments
#are not directly supported. You can skip a row by:
This does not allow skipping lines before a header row starting withforeach (var row in reader) { // Skip row if starts with # if (!row.Span.StartsWith("#")) { // ... } }#though. In Example Catalogue a full example is given detailing how to skip lines before header.
Comparison Benchmarks
To investigate the performance of Sep it is compared to:
- CsvHelper - the most commonly
used CSV library with a staggering
downloads on NuGet. Fully featured and battle tested.
- Sylvan - is well-known and has previously been shown to be the fastest CSV libraries for parsing (Sep changes that 😉).
ReadLine/WriteLine- basic naive implementations that read line by line and split on separator. While writing columns, separators and line endings directly. Does not handle quotes or similar correctly.
All benchmarks are run from/to memory either with:
StringReaderorStreamReader + MemoryStreamStringWriterorStreamWriter + MemoryStream
This to avoid confounding factors from reading from or writing to disk.
When using StringReader/StringWriter each char counts as 2 bytes, when
measuring throughput e.g. MB/s. When using StreamReader/StreamWriter
content is UTF-8 encoded and each char typically counts as 1 byte, as content
usually limited to 1 byte per char in UTF-8. Note that in .NET for TextReader
and TextWriter data is converted to/from char, but for reading such
conversion can often be just as fast as Memmove.
By default only StringReader/StringWriter results are shown, if a result is
based on StreamReader/StreamWriter it will be called out. Usually, results
for StreamReader/StreamWriter are in line with StringReader/StringWriter
but with half the throughput due to 1 byte vs 2 bytes. For brevity they are not
shown here.
For all benchmark results, Sep has been defined as the Baseline in
BenchmarkDotNet. This means Ratio will be 1.00
for Sep. For the others Ratio will then show how many times faster Sep is
than that. Or how many times more bytes are allocated in Alloc Ratio.
Disclaimer: Any comparison made is based on a number of preconditions and assumptions. Sep is a new library written from the ground up to use the latest and greatest features in .NET. CsvHelper has a long history and has to take into account backwards compatibility and still supporting older runtimes, so may not be able to easily utilize more recent features. Same goes for Sylvan. Additionally, Sep has a different feature set compared to the two. Performance is a feature, but not the only feature. Keep that in mind when evaluating results.
Runtime and Platforms
The following runtime is used for benchmarking:
NET 9.0.X
NOTE: Garbage Collection
DATAS
mode is disabled since this severely impacts (e.g. 1.7x
slower) performance for some
benchmarks due to the bursty accumulated allocations. That is,
GarbageCollectionAdaptationMode is set to 0.
The following platforms are used for benchmarking:
AMD EPYC 7763(Virtual) X64 Platform InformationOS=Ubuntu 22.04.5 LTS (Jammy Jellyfish) AMD EPYC 7763, 1 CPU, 4 logical and 2 physical coresAMD Ryzen 7 PRO 7840U(Laptop on battery) X64 Platform InformationOS=Windows 11 (10.0.22631.4460/23H2/2023Update/SunValley3) AMD Ryzen 7 PRO 7840U w/ Radeon 780M Graphics, 1 CPU, 16 logical and 8 physical coresAMD 5950X(Desktop) X64 Platform Information (no longer available)OS=Windows 10 (10.0.19044.2846/21H2/November2021Update) AMD Ryzen 9 5950X, 1 CPU, 32 logical and 16 physical coresAMD 9950X(Desktop) X64 Platform InformationOS=Windows 10 (10.0.19044.3086/21H2/November2021Update) AMD Ryzen 9 9950X, 1 CPU, 32 logical and 16 physical coresApple M1(Virtual) ARM64 Platform InformationOS=macOS Sonoma 14.7.1 (23H222) [Darwin 23.6.0] Apple M1 (Virtual), 1 CPU, 3 logical and 3 physical cores
Reader Comparison Benchmarks
The following reader scenarios are benchmarked:
- NCsvPerf from The fastest CSV parser in .NET
- Floats as for example in machine learning.
Details for each can be found in the following. However, for each of these 3 different scopes are benchmarked to better ascertain the low-level performance of each library and approach and what parts of the parsing consume the most time:
- Row - for this scope only the row is enumerated. That is, for Sep all
that is done is:
this should capture parsing both row and columns but without accessing these. Note that some libraries (like Sylvan) will defer work for columns to when these are accessed.foreach (var row in reader) { } - Cols - for this scope all rows and all columns are enumerated. If
possible columns are accessed as spans, if not as strings, which then might
mean a string has to be allocated. That is, for Sep this is:
foreach (var row in reader) { for (var i = 0; i < row.ColCount; i++) { var span = row[i].Span; } } - XYZ - finally the full scope is performed which is specific to each of the scenarios.
Additionally, as Sep supports multi-threaded parsing via ParallelEnumerate
benchmarks results with _MT in the method name are multi-threaded. These show
Sep provides unparalleled performance compared to any other CSV parser.
The overhead of Sep async support is also benchmarked and can be seen with
_Async in the method name. Note that this is the absolute best case for async
given there is no real IO involved and hence no actual asynchronous work or
continuations (thus no Task allocations) since benchmarks run from memory
only. This is fine as the main purpose of the benchmark is to gauge the overhead
of the async code path.
NCsvPerf PackageAssets Reader Comparison Benchmarks
NCsvPerf from The fastest CSV parser in .NET is a benchmark which in Joel Verhagen's own words was defined with:
My goal was to find the fastest low-level CSV parser. Essentially, all I wanted was a library that gave me a string[] for each line where each field in the line was an element in the array.
What is great about this work is it tests a whole of 35 different libraries and approaches to this. Providing a great overview of those and their performance on this specific scenario. Given Sylvan is the fastest of those it is used as the one to beat here, while CsvHelper is used to compare to the most commonly used library.
The source used for this benchmark PackageAssetsBench.cs is a PackageAssets.csv with NuGet package information in 25 columns with rows like:
75fcf875-017d-4579-bfd9-791d3e6767f0,2020-11-28T01:50:41.2449947+00:00,Akinzekeel.BlazorGrid,0.9.1-preview,2020-11-27T22:42:54.3100000+00:00,AvailableAssets,RuntimeAssemblies,,,net5.0,,,,,,lib/net5.0/BlazorGrid.dll,BlazorGrid.dll,.dll,lib,net5.0,.NETCoreApp,5.0.0.0,,,0.0.0.0
75fcf875-017d-4579-bfd9-791d3e6767f0,2020-11-28T01:50:41.2449947+00:00,Akinzekeel.BlazorGrid,0.9.1-preview,2020-11-27T22:42:54.3100000+00:00,AvailableAssets,CompileLibAssemblies,,,net5.0,,,,,,lib/net5.0/BlazorGrid.dll,BlazorGrid.dll,.dll,lib,net5.0,.NETCoreApp,5.0.0.0,,,0.0.0.0
75fcf875-017d-4579-bfd9-791d3e6767f0,2020-11-28T01:50:41.2449947+00:00,Akinzekeel.BlazorGrid,0.9.1-preview,2020-11-27T22:42:54.3100000+00:00,AvailableAssets,ResourceAssemblies,,,net5.0,,,,,,lib/net5.0/de/BlazorGrid.resources.dll,BlazorGrid.resources.dll,.dll,lib,net5.0,.NETCoreApp,5.0.0.0,,,0.0.0.0
75fcf875-017d-4579-bfd9-791d3e6767f0,2020-11-28T01:50:41.2449947+00:00,Akinzekeel.BlazorGrid,0.9.1-preview,2020-11-27T22:42:54.3100000+00:00,AvailableAssets,MSBuildFiles,,,any,,,,,,build/Microsoft.AspNetCore.StaticWebAssets.props,Microsoft.AspNetCore.StaticWebAssets.props,.props,build,any,Any,0.0.0.0,,,0.0.0.0
75fcf875-017d-4579-bfd9-791d3e6767f0,2020-11-28T01:50:41.2449947+00:00,Akinzekeel.BlazorGrid,0.9.1-preview,2020-11-27T22:42:54.3100000+00:00,AvailableAssets,MSBuildFiles,,,any,,,,,,build/Akinzekeel.BlazorGrid.props,Akinzekeel.BlazorGrid.props,.props,build,any,Any,0.0.0.0,,,0.0.0.0
For Scope = Asset the columns are parsed into a
PackageAsset class, which
consists of 25 properties of which 22 are strings. Each asset is accumulated
into a List<PackageAsset>. Each column is accessed as a string regardless.
This means this benchmark is dominated by turning columns into strings for the
decently fast parsers. Hence, the fastest libraries in this test employ string
pooling. That is, basically a custom dictionary from ReadOnlySpan<char> to
string, which avoids allocating a new string for repeated values. And as can
be seen in the csv-file there are a lot of repeated values. Both Sylvan and
CsvHelper do this in the benchmark. So does Sep and as with Sep this is an
optional configuration that has to be explicitly enable. For Sep this means the
reader is created with something like:
using var reader = Sep.Reader(o => o with
{
HasHeader = false,
CreateToString = SepToString.PoolPerCol(maximumStringLength: 128),
})
.From(CreateReader());
What is unique for Sep is that it allows defining a pool per column e.g. via
SepToString.PoolPerCol(...). This is based on the fact
that often each column has its own set of values or strings that may be repeated
without any overlap to other columns. This also allows one to define per column
specific handling of ToString behavior. Whether to pool or not. Or even to use
a statically defined pool.
Sep supports unescaping via an option, see SepReaderOptions
and Unescaping. Therefore, Sep has two methods being tested. The
default Sep without unescaping and Sep_Unescape where unescaping is enabled.
Note that only if there are quotes will there be any unescaping, but to support
unescape one has to track extra state during parsing which means there is a
slight cost no matter what, most notably for the Cols scope. Sep is still the
fastest of all (by far in many cases).
PackageAssets Benchmark Results
The results below show Sep is the fastest .NET CSV Parser (for this
benchmark on these platforms and machines 😀). While for pure parsing allocating
only a fraction of the memory due to extensive use of pooling and the
ArrayPool<T>.
This is in many aspects due to Sep having extremely optimized string pooling and
optimized hashing of ReadOnlySpan<char>, and thus not really due to the
csv-parsing itself, since that is not a big part of the time consumed. At least
not for a decently fast csv-parser.
With ParallelEnumerate (MT) Sep is >2x faster than Sylvan and up to 9x
faster than CsvHelper.
At the lowest level of enumerating rows only, that is csv parsing only, Sep hits a staggering 21 GB/s on 9950X. Single-threaded.
AMD.EPYC.7763 - PackageAssets Benchmark Results (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 3.593 ms | 1.00 | 29 | 8094.2 | 71.9 | 1.02 KB | 1.00 |
| Sep_Async | Row | 50000 | 3.886 ms | 1.08 | 29 | 7484.8 | 77.7 | 1.02 KB | 1.00 |
| Sep_Unescape | Row | 50000 | 3.586 ms | 1.00 | 29 | 8110.7 | 71.7 | 1.02 KB | 1.00 |
| Sylvan___ | Row | 50000 | 4.341 ms | 1.21 | 29 | 6700.0 | 86.8 | 8.46 KB | 8.33 |
| ReadLine_ | Row | 50000 | 22.018 ms | 6.13 | 29 | 1321.0 | 440.4 | 88608.23 KB | 87,245.02 |
| CsvHelper | Row | 50000 | 63.984 ms | 17.81 | 29 | 454.6 | 1279.7 | 19.95 KB | 19.64 |
| Sep______ | Cols | 50000 | 4.957 ms | 1.00 | 29 | 5868.2 | 99.1 | 1.02 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 5.868 ms | 1.18 | 29 | 4956.5 | 117.4 | 1.02 KB | 1.00 |
| Sylvan___ | Cols | 50000 | 8.174 ms | 1.65 | 29 | 3558.5 | 163.5 | 8.46 KB | 8.32 |
| ReadLine_ | Cols | 50000 | 23.316 ms | 4.70 | 29 | 1247.5 | 466.3 | 88608.23 KB | 87,161.22 |
| CsvHelper | Cols | 50000 | 103.321 ms | 20.85 | 29 | 281.5 | 2066.4 | 445.67 KB | 438.39 |
| Sep______ | Asset | 50000 | 39.423 ms | 1.00 | 29 | 737.8 | 788.5 | 13802.24 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 28.737 ms | 0.73 | 29 | 1012.1 | 574.7 | 13861.74 KB | 1.00 |
| Sylvan___ | Asset | 50000 | 48.458 ms | 1.23 | 29 | 600.2 | 969.2 | 13961.98 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 123.884 ms | 3.14 | 29 | 234.8 | 2477.7 | 102133.97 KB | 7.40 |
| CsvHelper | Asset | 50000 | 123.793 ms | 3.14 | 29 | 235.0 | 2475.9 | 13971.04 KB | 1.01 |
| Sep______ | Asset | 1000000 | 825.400 ms | 1.00 | 581 | 705.0 | 825.4 | 266668.32 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 506.193 ms | 0.61 | 581 | 1149.5 | 506.2 | 275203.41 KB | 1.03 |
| Sylvan___ | Asset | 1000000 | 998.747 ms | 1.21 | 581 | 582.6 | 998.7 | 266823.98 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 2,634.271 ms | 3.19 | 581 | 220.9 | 2634.3 | 2038835.21 KB | 7.65 |
| CsvHelper | Asset | 1000000 | 2,494.118 ms | 3.02 | 581 | 233.3 | 2494.1 | 266839.37 KB | 1.00 |
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - PackageAssets Benchmark Results (Sep 0.12.0.0, Sylvan 1.4.3.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 3.501 ms | 1.00 | 29 | 8334.3 | 70.0 | 1.01 KB | 1.00 |
| Sep_Async | Row | 50000 | 3.748 ms | 1.07 | 29 | 7785.1 | 75.0 | 1.01 KB | 1.00 |
| Sep_Unescape | Row | 50000 | 3.489 ms | 1.00 | 29 | 8364.7 | 69.8 | 1.01 KB | 1.00 |
| Sylvan___ | Row | 50000 | 4.436 ms | 1.27 | 29 | 6578.6 | 88.7 | 7.65 KB | 7.59 |
| ReadLine_ | Row | 50000 | 18.126 ms | 5.18 | 29 | 1609.9 | 362.5 | 88608.23 KB | 87,921.34 |
| CsvHelper | Row | 50000 | 65.367 ms | 18.69 | 29 | 446.4 | 1307.3 | 19.95 KB | 19.79 |
| Sep______ | Cols | 50000 | 4.800 ms | 1.00 | 29 | 6079.7 | 96.0 | 1.01 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 5.952 ms | 1.24 | 29 | 4902.5 | 119.0 | 1.01 KB | 1.00 |
| Sylvan___ | Cols | 50000 | 8.572 ms | 1.79 | 29 | 3404.3 | 171.4 | 7.65 KB | 7.59 |
| ReadLine_ | Cols | 50000 | 19.182 ms | 4.00 | 29 | 1521.3 | 383.6 | 88608.23 KB | 87,921.34 |
| CsvHelper | Cols | 50000 | 108.414 ms | 22.59 | 29 | 269.2 | 2168.3 | 445.6 KB | 442.15 |
| Sep______ | Asset | 50000 | 52.283 ms | 1.00 | 29 | 558.1 | 1045.7 | 13802.65 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 34.441 ms | 0.66 | 29 | 847.3 | 688.8 | 13913.24 KB | 1.01 |
| Sylvan___ | Asset | 50000 | 53.960 ms | 1.04 | 29 | 540.8 | 1079.2 | 13962.29 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 210.735 ms | 4.04 | 29 | 138.5 | 4214.7 | 102133.96 KB | 7.40 |
| CsvHelper | Asset | 50000 | 131.644 ms | 2.53 | 29 | 221.7 | 2632.9 | 13970.83 KB | 1.01 |
| Sep______ | Asset | 1000000 | 978.280 ms | 1.00 | 583 | 596.7 | 978.3 | 266668.88 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 515.844 ms | 0.53 | 583 | 1131.7 | 515.8 | 267776.55 KB | 1.00 |
| Sylvan___ | Asset | 1000000 | 1,130.594 ms | 1.16 | 583 | 516.4 | 1130.6 | 266825.13 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 3,155.567 ms | 3.23 | 583 | 185.0 | 3155.6 | 2038835.23 KB | 7.65 |
| CsvHelper | Asset | 1000000 | 2,669.767 ms | 2.73 | 583 | 218.7 | 2669.8 | 266844.99 KB | 1.00 |
AMD.Ryzen.9.5950X - PackageAssets Benchmark Results (Sep 0.9.0.0, Sylvan 1.3.9.0, CsvHelper 33.0.1.24)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 2.230 ms | 1.00 | 29 | 13088.4 | 44.6 | 1.09 KB | 1.00 |
| Sep_Async | Row | 50000 | 2.379 ms | 1.07 | 29 | 12264.0 | 47.6 | 1.02 KB | 0.93 |
| Sep_Unescape | Row | 50000 | 2.305 ms | 1.03 | 29 | 12657.6 | 46.1 | 1.02 KB | 0.93 |
| Sylvan___ | Row | 50000 | 2.993 ms | 1.33 | 29 | 9750.2 | 59.9 | 7.65 KB | 7.52 |
| ReadLine_ | Row | 50000 | 12.106 ms | 5.36 | 29 | 2410.5 | 242.1 | 88608.25 KB | 87,077.59 |
| CsvHelper | Row | 50000 | 43.313 ms | 19.19 | 29 | 673.7 | 866.3 | 20.04 KB | 19.69 |
| Sep______ | Cols | 50000 | 3.211 ms | 1.00 | 29 | 9089.3 | 64.2 | 1.02 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 3.845 ms | 1.20 | 29 | 7589.1 | 76.9 | 1.02 KB | 1.00 |
| Sylvan___ | Cols | 50000 | 5.065 ms | 1.58 | 29 | 5760.9 | 101.3 | 7.66 KB | 7.52 |
| ReadLine_ | Cols | 50000 | 12.850 ms | 4.00 | 29 | 2270.9 | 257.0 | 88608.25 KB | 86,910.78 |
| CsvHelper | Cols | 50000 | 68.999 ms | 21.49 | 29 | 422.9 | 1380.0 | 445.85 KB | 437.31 |
| Sep______ | Asset | 50000 | 33.615 ms | 1.00 | 29 | 868.1 | 672.3 | 13802.47 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 20.231 ms | 0.60 | 29 | 1442.4 | 404.6 | 13992.1 KB | 1.01 |
| Sylvan___ | Asset | 50000 | 34.762 ms | 1.03 | 29 | 839.5 | 695.2 | 13962.2 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 97.204 ms | 2.89 | 29 | 300.2 | 1944.1 | 102133.9 KB | 7.40 |
| CsvHelper | Asset | 50000 | 83.550 ms | 2.49 | 29 | 349.3 | 1671.0 | 13970.66 KB | 1.01 |
| Sep______ | Asset | 1000000 | 629.552 ms | 1.00 | 583 | 927.3 | 629.6 | 266669.13 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 261.089 ms | 0.41 | 583 | 2236.0 | 261.1 | 267793.45 KB | 1.00 |
| Sylvan___ | Asset | 1000000 | 761.171 ms | 1.21 | 583 | 767.0 | 761.2 | 266825.09 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 1,636.526 ms | 2.60 | 583 | 356.7 | 1636.5 | 2038835.59 KB | 7.65 |
| CsvHelper | Asset | 1000000 | 1,754.461 ms | 2.79 | 583 | 332.7 | 1754.5 | 266833.16 KB | 1.00 |
AMD.Ryzen.9.9950X - PackageAssets Benchmark Results (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 1.371 ms | 1.00 | 29 | 21288.2 | 27.4 | 1.02 KB | 1.00 |
| Sep_Async | Row | 50000 | 1.511 ms | 1.10 | 29 | 19318.0 | 30.2 | 1.02 KB | 1.00 |
| Sep_Unescape | Row | 50000 | 1.399 ms | 1.02 | 29 | 20858.2 | 28.0 | 1.02 KB | 1.00 |
| Sylvan___ | Row | 50000 | 1.911 ms | 1.39 | 29 | 15271.3 | 38.2 | 8.45 KB | 8.32 |
| ReadLine_ | Row | 50000 | 7.884 ms | 5.75 | 29 | 3701.4 | 157.7 | 88608.23 KB | 87,245.02 |
| CsvHelper | Row | 50000 | 23.786 ms | 17.35 | 29 | 1226.8 | 475.7 | 19.95 KB | 19.64 |
| Sep______ | Cols | 50000 | 2.232 ms | 1.00 | 29 | 13076.0 | 44.6 | 1.02 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 2.369 ms | 1.06 | 29 | 12315.8 | 47.4 | 1.02 KB | 1.00 |
| Sylvan___ | Cols | 50000 | 3.283 ms | 1.47 | 29 | 8889.4 | 65.7 | 8.46 KB | 8.32 |
| ReadLine_ | Cols | 50000 | 8.279 ms | 3.71 | 29 | 3524.9 | 165.6 | 88608.23 KB | 87,245.02 |
| CsvHelper | Cols | 50000 | 44.021 ms | 19.73 | 29 | 662.9 | 880.4 | 445.61 KB | 438.75 |
| Sep______ | Asset | 50000 | 22.918 ms | 1.00 | 29 | 1273.3 | 458.4 | 13802.3 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 15.072 ms | 0.66 | 29 | 1936.2 | 301.4 | 13997.81 KB | 1.01 |
| Sylvan___ | Asset | 50000 | 25.305 ms | 1.10 | 29 | 1153.2 | 506.1 | 13962.14 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 80.250 ms | 3.50 | 29 | 363.6 | 1605.0 | 102133.88 KB | 7.40 |
| CsvHelper | Asset | 50000 | 54.268 ms | 2.37 | 29 | 537.7 | 1085.4 | 13970.19 KB | 1.01 |
| Sep______ | Asset | 1000000 | 441.901 ms | 1.00 | 583 | 1321.1 | 441.9 | 266667.44 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 191.048 ms | 0.43 | 583 | 3055.7 | 191.0 | 267864.88 KB | 1.00 |
| Sylvan___ | Asset | 1000000 | 529.177 ms | 1.20 | 583 | 1103.2 | 529.2 | 266824.41 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 1,312.114 ms | 2.97 | 583 | 444.9 | 1312.1 | 2038834.94 KB | 7.65 |
| CsvHelper | Asset | 1000000 | 1,160.448 ms | 2.63 | 583 | 503.1 | 1160.4 | 266840.68 KB | 1.00 |
AMD.Ryzen.9.9950X3D2 - PackageAssets Benchmark Results (Sep 0.17.1.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 1.300 ms | 1.00 | 29 | 22451.9 | 26.0 | 1.02 KB | 1.00 |
| Sep_Async | Row | 50000 | 1.450 ms | 1.12 | 29 | 20131.5 | 29.0 | 1.02 KB | 1.00 |
| Sep_Unescape | Row | 50000 | 1.313 ms | 1.01 | 29 | 22220.0 | 26.3 | 1.02 KB | 1.00 |
| Sylvan___ | Row | 50000 | 1.858 ms | 1.43 | 29 | 15708.8 | 37.2 | 8.45 KB | 8.32 |
| ReadLine_ | Row | 50000 | 7.627 ms | 5.87 | 29 | 3826.1 | 152.5 | 88608.23 KB | 87,245.02 |
| CsvHelper | Row | 50000 | 24.656 ms | 18.97 | 29 | 1183.5 | 493.1 | 19.95 KB | 19.64 |
| Sep______ | Cols | 50000 | 1.851 ms | 1.00 | 29 | 15766.8 | 37.0 | 1.02 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 2.252 ms | 1.22 | 29 | 12958.3 | 45.0 | 1.02 KB | 1.00 |
| Sylvan___ | Cols | 50000 | 3.199 ms | 1.73 | 29 | 9122.8 | 64.0 | 8.46 KB | 8.32 |
| ReadLine_ | Cols | 50000 | 8.249 ms | 4.46 | 29 | 3537.6 | 165.0 | 88608.23 KB | 87,245.02 |
| CsvHelper | Cols | 50000 | 44.812 ms | 24.21 | 29 | 651.2 | 896.2 | 445.6 KB | 438.75 |
| Sep______ | Asset | 50000 | 20.932 ms | 1.00 | 29 | 1394.1 | 418.6 | 13801.89 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 9.588 ms | 0.46 | 29 | 3043.5 | 191.8 | 13976.54 KB | 1.01 |
| Sylvan___ | Asset | 50000 | 25.103 ms | 1.20 | 29 | 1162.5 | 502.1 | 13961.71 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 57.269 ms | 2.74 | 29 | 509.5 | 1145.4 | 102133.38 KB | 7.40 |
| CsvHelper | Asset | 50000 | 53.148 ms | 2.55 | 29 | 549.1 | 1063.0 | 13969.95 KB | 1.01 |
| Sep______ | Asset | 1000000 | 406.995 ms | 1.00 | 583 | 1434.4 | 407.0 | 266664.02 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 183.364 ms | 0.45 | 583 | 3183.7 | 183.4 | 268786.5 KB | 1.01 |
| Sylvan___ | Asset | 1000000 | 476.032 ms | 1.17 | 583 | 1226.4 | 476.0 | 266822.84 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 1,408.037 ms | 3.47 | 583 | 414.6 | 1408.0 | 2038835.55 KB | 7.65 |
| CsvHelper | Asset | 1000000 | 1,146.483 ms | 2.82 | 583 | 509.2 | 1146.5 | 266831.14 KB | 1.00 |
Apple.M1.(Virtual) - PackageAssets Benchmark Results (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 2.971 ms | 1.00 | 29 | 9790.9 | 59.4 | 960 B | 1.00 |
| Sep_Async | Row | 50000 | 3.025 ms | 1.02 | 29 | 9614.8 | 60.5 | 960 B | 1.00 |
| Sep_Unescape | Row | 50000 | 2.981 ms | 1.00 | 29 | 9755.5 | 59.6 | 960 B | 1.00 |
| Sylvan___ | Row | 50000 | 18.006 ms | 6.06 | 29 | 1615.3 | 360.1 | 7475 B | 7.79 |
| ReadLine_ | Row | 50000 | 16.749 ms | 5.64 | 29 | 1736.6 | 335.0 | 90734824 B | 94,515.44 |
| CsvHelper | Row | 50000 | 43.180 ms | 14.54 | 29 | 673.6 | 863.6 | 20496 B | 21.35 |
| Sep______ | Cols | 50000 | 4.085 ms | 1.00 | 29 | 7120.0 | 81.7 | 960 B | 1.00 |
| Sep_Unescape | Cols | 50000 | 4.628 ms | 1.13 | 29 | 6284.5 | 92.6 | 960 B | 1.00 |
| Sylvan___ | Cols | 50000 | 21.259 ms | 5.21 | 29 | 1368.2 | 425.2 | 7516 B | 7.83 |
| ReadLine_ | Cols | 50000 | 16.801 ms | 4.11 | 29 | 1731.2 | 336.0 | 90734824 B | 94,515.44 |
| CsvHelper | Cols | 50000 | 65.949 ms | 16.15 | 29 | 441.0 | 1319.0 | 456368 B | 475.38 |
| Sep______ | Asset | 50000 | 30.072 ms | 1.00 | 29 | 967.2 | 601.4 | 14133394 B | 1.00 |
| Sep_MT___ | Asset | 50000 | 21.703 ms | 0.72 | 29 | 1340.2 | 434.1 | 14271705 B | 1.01 |
| Sylvan___ | Asset | 50000 | 49.570 ms | 1.65 | 29 | 586.8 | 991.4 | 14295922 B | 1.01 |
| ReadLine_ | Asset | 50000 | 79.663 ms | 2.66 | 29 | 365.1 | 1593.3 | 104585174 B | 7.40 |
| CsvHelper | Asset | 50000 | 73.557 ms | 2.45 | 29 | 395.4 | 1471.1 | 14305304 B | 1.01 |
| Sep______ | Asset | 1000000 | 590.295 ms | 1.00 | 581 | 985.7 | 590.3 | 273067136 B | 1.00 |
| Sep_MT___ | Asset | 1000000 | 429.687 ms | 0.73 | 581 | 1354.2 | 429.7 | 284090744 B | 1.04 |
| Sylvan___ | Asset | 1000000 | 997.005 ms | 1.69 | 581 | 583.6 | 997.0 | 273230816 B | 1.00 |
| ReadLine_ | Asset | 1000000 | 1,645.187 ms | 2.79 | 581 | 353.7 | 1645.2 | 2087766816 B | 7.65 |
| CsvHelper | Asset | 1000000 | 1,534.770 ms | 2.60 | 581 | 379.1 | 1534.8 | 273236224 B | 1.00 |
Cobalt.100 - PackageAssets Benchmark Results (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 5.195 ms | 1.00 | 29 | 5616.8 | 103.9 | 960 B | 1.00 |
| Sep_Async | Row | 50000 | 5.715 ms | 1.10 | 29 | 5106.0 | 114.3 | 960 B | 1.00 |
| Sep_Unescape | Row | 50000 | 5.432 ms | 1.05 | 29 | 5372.4 | 108.6 | 960 B | 1.00 |
| Sylvan___ | Row | 50000 | 20.667 ms | 3.99 | 29 | 1412.0 | 413.3 | 7476 B | 7.79 |
| ReadLine_ | Row | 50000 | 20.739 ms | 4.00 | 29 | 1407.1 | 414.8 | 90734824 B | 94,515.44 |
| CsvHelper | Row | 50000 | 54.592 ms | 10.53 | 29 | 534.5 | 1091.8 | 20424 B | 21.27 |
| Sep______ | Cols | 50000 | 6.963 ms | 1.00 | 29 | 4190.9 | 139.3 | 960 B | 1.00 |
| Sep_Unescape | Cols | 50000 | 7.811 ms | 1.12 | 29 | 3735.7 | 156.2 | 960 B | 1.00 |
| Sylvan___ | Cols | 50000 | 24.814 ms | 3.56 | 29 | 1176.0 | 496.3 | 7480 B | 7.79 |
| ReadLine_ | Cols | 50000 | 20.856 ms | 3.00 | 29 | 1399.2 | 417.1 | 90734827 B | 94,515.44 |
| CsvHelper | Cols | 50000 | 88.627 ms | 12.73 | 29 | 329.3 | 1772.5 | 456368 B | 475.38 |
| Sep______ | Asset | 50000 | 31.201 ms | 1.00 | 29 | 935.3 | 624.0 | 14133000 B | 1.00 |
| Sep_MT___ | Asset | 50000 | 14.267 ms | 0.46 | 29 | 2045.3 | 285.3 | 14193488 B | 1.00 |
| Sylvan___ | Asset | 50000 | 56.442 ms | 1.81 | 29 | 517.0 | 1128.8 | 14295523 B | 1.01 |
| ReadLine_ | Asset | 50000 | 117.311 ms | 3.76 | 29 | 248.8 | 2346.2 | 104584230 B | 7.40 |
| CsvHelper | Asset | 50000 | 97.873 ms | 3.14 | 29 | 298.2 | 1957.5 | 14308394 B | 1.01 |
| Sep______ | Asset | 1000000 | 775.808 ms | 1.00 | 583 | 752.5 | 775.8 | 273063600 B | 1.00 |
| Sep_MT___ | Asset | 1000000 | 367.674 ms | 0.47 | 583 | 1587.8 | 367.7 | 281452296 B | 1.03 |
| Sylvan___ | Asset | 1000000 | 1,237.548 ms | 1.60 | 583 | 471.7 | 1237.5 | 273225288 B | 1.00 |
| ReadLine_ | Asset | 1000000 | 2,741.695 ms | 3.53 | 583 | 212.9 | 2741.7 | 2087765224 B | 7.65 |
| CsvHelper | Asset | 1000000 | 2,130.007 ms | 2.75 | 583 | 274.1 | 2130.0 | 273247280 B | 1.00 |
PackageAssets Benchmark Results (SERVER GC)
The package assets benchmark (Scope Asset) has a very high base load in the
form of the accumulated instances of PackageAsset and since Sep is so fast the
GC becomes a significant bottleneck for the benchmark, especially for
multi-threaded parsing. Switching to SERVER
GC
can, therefore, provide significant speedup as can be seen below.
With ParallelEnumerate and server GC Sep is >4x faster than Sylvan and up to
18x faster than CsvHelper. Breaking 8 GB/s parsing speed on package assets on
9950X.
AMD.EPYC.7763 - PackageAssets Benchmark Results (SERVER GC) (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 30.67 ms | 1.00 | 29 | 948.5 | 613.3 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 17.07 ms | 0.56 | 29 | 1704.0 | 341.4 | 13.53 MB | 1.00 |
| Sylvan___ | Asset | 50000 | 41.63 ms | 1.36 | 29 | 698.7 | 832.5 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 58.75 ms | 1.92 | 29 | 495.0 | 1175.1 | 99.74 MB | 7.40 |
| CsvHelper | Asset | 50000 | 114.31 ms | 3.73 | 29 | 254.5 | 2286.1 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 640.63 ms | 1.00 | 581 | 908.3 | 640.6 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 348.07 ms | 0.54 | 581 | 1671.7 | 348.1 | 269.03 MB | 1.03 |
| Sylvan___ | Asset | 1000000 | 811.52 ms | 1.27 | 581 | 717.0 | 811.5 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 1,205.96 ms | 1.88 | 581 | 482.5 | 1206.0 | 1991.04 MB | 7.65 |
| CsvHelper | Asset | 1000000 | 2,315.88 ms | 3.62 | 581 | 251.3 | 2315.9 | 260.58 MB | 1.00 |
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - PackageAssets Benchmark Results (SERVER GC) (Sep 0.12.0.0, Sylvan 1.4.3.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 35.630 ms | 1.00 | 29 | 819.0 | 712.6 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 8.892 ms | 0.25 | 29 | 3281.6 | 177.8 | 13.57 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 38.901 ms | 1.09 | 29 | 750.1 | 778.0 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 43.945 ms | 1.23 | 29 | 664.0 | 878.9 | 99.74 MB | 7.40 |
| CsvHelper | Asset | 50000 | 116.596 ms | 3.27 | 29 | 250.3 | 2331.9 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 654.638 ms | 1.00 | 583 | 891.8 | 654.6 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 244.949 ms | 0.37 | 583 | 2383.3 | 244.9 | 262.32 MB | 1.01 |
| Sylvan___ | Asset | 1000000 | 825.727 ms | 1.26 | 583 | 707.0 | 825.7 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 968.707 ms | 1.48 | 583 | 602.6 | 968.7 | 1991.04 MB | 7.65 |
| CsvHelper | Asset | 1000000 | 2,364.922 ms | 3.62 | 583 | 246.9 | 2364.9 | 260.58 MB | 1.00 |
AMD.Ryzen.9.5950X - PackageAssets Benchmark Results (SERVER GC) (Sep 0.9.0.0, Sylvan 1.3.9.0, CsvHelper 33.0.1.24)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 20.951 ms | 1.00 | 29 | 1392.9 | 419.0 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 6.614 ms | 0.32 | 29 | 4411.8 | 132.3 | 13.64 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 27.761 ms | 1.33 | 29 | 1051.2 | 555.2 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 33.516 ms | 1.60 | 29 | 870.7 | 670.3 | 99.74 MB | 7.40 |
| CsvHelper | Asset | 50000 | 77.007 ms | 3.68 | 29 | 378.9 | 1540.1 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 432.887 ms | 1.00 | 583 | 1348.6 | 432.9 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 119.430 ms | 0.28 | 583 | 4888.1 | 119.4 | 261.39 MB | 1.00 |
| Sylvan___ | Asset | 1000000 | 559.550 ms | 1.29 | 583 | 1043.3 | 559.6 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 573.637 ms | 1.33 | 583 | 1017.7 | 573.6 | 1991.05 MB | 7.65 |
| CsvHelper | Asset | 1000000 | 1,537.602 ms | 3.55 | 583 | 379.7 | 1537.6 | 260.58 MB | 1.00 |
AMD.Ryzen.9.9950X - PackageAssets Benchmark Results (SERVER GC) (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 14.258 ms | 1.00 | 29 | 2046.6 | 285.2 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 3.261 ms | 0.23 | 29 | 8947.4 | 65.2 | 13.65 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 18.170 ms | 1.27 | 29 | 1606.0 | 363.4 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 19.166 ms | 1.34 | 29 | 1522.6 | 383.3 | 99.74 MB | 7.40 |
| CsvHelper | Asset | 50000 | 49.240 ms | 3.45 | 29 | 592.6 | 984.8 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 287.939 ms | 1.00 | 583 | 2027.5 | 287.9 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 58.468 ms | 0.20 | 583 | 9984.7 | 58.5 | 261.67 MB | 1.00 |
| Sylvan___ | Asset | 1000000 | 370.560 ms | 1.29 | 583 | 1575.4 | 370.6 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 365.042 ms | 1.27 | 583 | 1599.2 | 365.0 | 1991.04 MB | 7.65 |
| CsvHelper | Asset | 1000000 | 1,001.476 ms | 3.48 | 583 | 582.9 | 1001.5 | 260.58 MB | 1.00 |
AMD.Ryzen.9.9950X3D2 - PackageAssets Benchmark Results (SERVER GC) (Sep 0.17.1.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 14.645 ms | 1.00 | 29 | 1992.6 | 292.9 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 3.216 ms | 0.22 | 29 | 9074.6 | 64.3 | 13.65 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 18.847 ms | 1.29 | 29 | 1548.3 | 376.9 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 18.680 ms | 1.28 | 29 | 1562.2 | 373.6 | 99.74 MB | 7.40 |
| CsvHelper | Asset | 50000 | 50.322 ms | 3.44 | 29 | 579.9 | 1006.4 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 294.100 ms | 1.00 | 583 | 1985.0 | 294.1 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 52.370 ms | 0.18 | 583 | 11147.3 | 52.4 | 261.64 MB | 1.00 |
| Sylvan___ | Asset | 1000000 | 379.257 ms | 1.29 | 583 | 1539.3 | 379.3 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 463.493 ms | 1.58 | 583 | 1259.5 | 463.5 | 1991.04 MB | 7.65 |
| CsvHelper | Asset | 1000000 | 1,026.722 ms | 3.49 | 583 | 568.6 | 1026.7 | 260.58 MB | 1.00 |
Apple.M1.(Virtual) - PackageAssets Benchmark Results (SERVER GC) (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 21.545 ms | 1.00 | 29 | 1350.0 | 430.9 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 9.889 ms | 0.46 | 29 | 2941.4 | 197.8 | 13.59 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 43.137 ms | 2.00 | 29 | 674.3 | 862.7 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 28.729 ms | 1.33 | 29 | 1012.4 | 574.6 | 99.74 MB | 7.40 |
| CsvHelper | Asset | 50000 | 75.601 ms | 3.51 | 29 | 384.7 | 1512.0 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 457.098 ms | 1.00 | 581 | 1273.0 | 457.1 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 224.967 ms | 0.49 | 581 | 2586.5 | 225.0 | 268.14 MB | 1.03 |
| Sylvan___ | Asset | 1000000 | 894.656 ms | 1.96 | 581 | 650.4 | 894.7 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 792.175 ms | 1.73 | 581 | 734.5 | 792.2 | 1991.04 MB | 7.65 |
| CsvHelper | Asset | 1000000 | 1,462.667 ms | 3.20 | 581 | 397.8 | 1462.7 | 260.58 MB | 1.00 |
Cobalt.100 - PackageAssets Benchmark Results (SERVER GC) (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 32.30 ms | 1.00 | 29 | 903.3 | 646.1 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 11.63 ms | 0.36 | 29 | 2509.7 | 232.5 | 13.54 MB | 1.00 |
| Sylvan___ | Asset | 50000 | 54.58 ms | 1.69 | 29 | 534.6 | 1091.6 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 48.26 ms | 1.49 | 29 | 604.6 | 965.3 | 99.74 MB | 7.40 |
| CsvHelper | Asset | 50000 | 98.61 ms | 3.05 | 29 | 295.9 | 1972.2 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 645.55 ms | 1.00 | 583 | 904.3 | 645.6 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 199.49 ms | 0.31 | 583 | 2926.4 | 199.5 | 266.18 MB | 1.02 |
| Sylvan___ | Asset | 1000000 | 1,090.59 ms | 1.69 | 583 | 535.3 | 1090.6 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 1,001.06 ms | 1.55 | 583 | 583.2 | 1001.1 | 1991.04 MB | 7.65 |
| CsvHelper | Asset | 1000000 | 1,986.34 ms | 3.08 | 583 | 293.9 | 1986.3 | 260.58 MB | 1.00 |
PackageAssets with Quotes Benchmark Results
NCsvPerf does not examine performance in the face of quotes in the csv. This
is relevant since some libraries like Sylvan will revert to a slower (not SIMD
vectorized) parsing code path if it encounters quotes. Sep was designed to
always use SIMD vectorization no matter what.
Since there are two extra chars to handle per column, it does have a
significant impact on performance, no matter what though. This is expected when
looking at the numbers. For each row of 25 columns, there are 24 separators
(here ,) and one set of line endings (here \r\n). That's 26 characters.
Adding quotes around each of the 25 columns will add 50 characters or almost
triple the total to 76.
AMD.EPYC.7763 - PackageAssets with Quotes Benchmark Results (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 11.42 ms | 1.00 | 33 | 2913.9 | 228.4 | 1.02 KB | 1.00 |
| Sep_Async | Row | 50000 | 11.72 ms | 1.03 | 33 | 2840.1 | 234.4 | 1.02 KB | 1.00 |
| Sep_Unescape | Row | 50000 | 10.82 ms | 0.95 | 33 | 3076.9 | 216.3 | 1.02 KB | 1.00 |
| Sylvan___ | Row | 50000 | 24.56 ms | 2.15 | 33 | 1355.4 | 491.1 | 8.47 KB | 8.34 |
| ReadLine_ | Row | 50000 | 26.64 ms | 2.33 | 33 | 1249.5 | 532.7 | 108778.73 KB | 107,105.21 |
| CsvHelper | Row | 50000 | 77.46 ms | 6.78 | 33 | 429.7 | 1549.2 | 19.95 KB | 19.64 |
| Sep______ | Cols | 50000 | 12.66 ms | 1.00 | 33 | 2629.7 | 253.1 | 1.02 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 13.32 ms | 1.05 | 33 | 2498.0 | 266.5 | 1.02 KB | 1.00 |
| Sylvan___ | Cols | 50000 | 28.17 ms | 2.23 | 33 | 1181.4 | 563.4 | 8.47 KB | 8.34 |
| ReadLine_ | Cols | 50000 | 27.55 ms | 2.18 | 33 | 1208.2 | 550.9 | 108778.73 KB | 107,105.21 |
| CsvHelper | Cols | 50000 | 106.05 ms | 8.38 | 33 | 313.8 | 2121.1 | 445.68 KB | 438.82 |
| Sep______ | Asset | 50000 | 45.66 ms | 1.00 | 33 | 728.9 | 913.2 | 13802.21 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 31.85 ms | 0.70 | 33 | 1044.8 | 637.1 | 13865.49 KB | 1.00 |
| Sylvan___ | Asset | 50000 | 68.62 ms | 1.50 | 33 | 485.0 | 1372.4 | 13961.94 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 153.43 ms | 3.36 | 33 | 216.9 | 3068.5 | 122305.14 KB | 8.86 |
| CsvHelper | Asset | 50000 | 127.77 ms | 2.80 | 33 | 260.5 | 2555.4 | 13971.34 KB | 1.01 |
| Sep______ | Asset | 1000000 | 968.61 ms | 1.00 | 665 | 687.4 | 968.6 | 266679.21 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 566.21 ms | 0.58 | 665 | 1175.9 | 566.2 | 271900.91 KB | 1.02 |
| Sylvan___ | Asset | 1000000 | 1,412.90 ms | 1.46 | 665 | 471.2 | 1412.9 | 266824.41 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 3,224.56 ms | 3.33 | 665 | 206.5 | 3224.6 | 2442318.43 KB | 9.16 |
| CsvHelper | Asset | 1000000 | 2,561.55 ms | 2.64 | 665 | 259.9 | 2561.6 | 266835.7 KB | 1.00 |
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - PackageAssets with Quotes Benchmark Results (Sep 0.12.0.0, Sylvan 1.4.3.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 11.228 ms | 1.00 | 33 | 2972.8 | 224.6 | 1.01 KB | 1.00 |
| Sep_Async | Row | 50000 | 9.916 ms | 0.88 | 33 | 3365.9 | 198.3 | 1.01 KB | 1.00 |
| Sep_Unescape | Row | 50000 | 9.953 ms | 0.89 | 33 | 3353.4 | 199.1 | 1.01 KB | 1.00 |
| Sylvan___ | Row | 50000 | 23.829 ms | 2.12 | 33 | 1400.7 | 476.6 | 7.66 KB | 7.60 |
| ReadLine_ | Row | 50000 | 21.909 ms | 1.95 | 33 | 1523.4 | 438.2 | 108778.73 KB | 107,935.48 |
| CsvHelper | Row | 50000 | 71.571 ms | 6.38 | 33 | 466.4 | 1431.4 | 19.95 KB | 19.79 |
| Sep______ | Cols | 50000 | 12.798 ms | 1.00 | 33 | 2608.0 | 256.0 | 1.01 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 13.689 ms | 1.07 | 33 | 2438.2 | 273.8 | 1.01 KB | 1.00 |
| Sylvan___ | Cols | 50000 | 27.287 ms | 2.13 | 33 | 1223.2 | 545.7 | 7.67 KB | 7.61 |
| ReadLine_ | Cols | 50000 | 23.201 ms | 1.81 | 33 | 1438.6 | 464.0 | 108778.73 KB | 107,935.48 |
| CsvHelper | Cols | 50000 | 104.298 ms | 8.15 | 33 | 320.0 | 2086.0 | 445.6 KB | 442.15 |
| Sep______ | Asset | 50000 | 55.957 ms | 1.00 | 33 | 596.5 | 1119.1 | 13802.68 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 44.668 ms | 0.80 | 33 | 747.2 | 893.4 | 13934.89 KB | 1.01 |
| Sylvan___ | Asset | 50000 | 68.353 ms | 1.22 | 33 | 488.3 | 1367.1 | 13961.88 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 260.030 ms | 4.65 | 33 | 128.4 | 5200.6 | 122304.52 KB | 8.86 |
| CsvHelper | Asset | 50000 | 127.992 ms | 2.29 | 33 | 260.8 | 2559.8 | 13971.32 KB | 1.01 |
| Sep______ | Asset | 1000000 | 1,145.307 ms | 1.00 | 667 | 583.0 | 1145.3 | 266673.49 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 696.277 ms | 0.61 | 667 | 959.0 | 696.3 | 267979.84 KB | 1.00 |
| Sylvan___ | Asset | 1000000 | 1,499.974 ms | 1.31 | 667 | 445.1 | 1500.0 | 266827.47 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 4,060.701 ms | 3.55 | 667 | 164.4 | 4060.7 | 2442318 KB | 9.16 |
| CsvHelper | Asset | 1000000 | 2,601.519 ms | 2.27 | 667 | 256.7 | 2601.5 | 266839.95 KB | 1.00 |
AMD.Ryzen.9.5950X - PackageAssets with Quotes Benchmark Results (Sep 0.9.0.0, Sylvan 1.3.9.0, CsvHelper 33.0.1.24)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 7.046 ms | 1.00 | 33 | 4737.2 | 140.9 | 1.04 KB | 1.00 |
| Sep_Async | Row | 50000 | 8.137 ms | 1.15 | 33 | 4101.8 | 162.7 | 1.04 KB | 1.00 |
| Sep_Unescape | Row | 50000 | 7.473 ms | 1.06 | 33 | 4466.7 | 149.5 | 1.04 KB | 1.00 |
| Sylvan___ | Row | 50000 | 17.571 ms | 2.38 | 33 | 1899.5 | 351.4 | 7.69 KB | 7.41 |
| ReadLine_ | Row | 50000 | 14.336 ms | 1.94 | 33 | 2328.2 | 286.7 | 108778.75 KB | 104,689.33 |
| CsvHelper | Row | 50000 | 52.672 ms | 7.12 | 33 | 633.7 | 1053.4 | 20.05 KB | 19.29 |
| Sep______ | Cols | 50000 | 8.126 ms | 1.00 | 33 | 4107.5 | 162.5 | 1.04 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 9.748 ms | 1.20 | 33 | 3424.0 | 195.0 | 1.05 KB | 1.01 |
| Sylvan___ | Cols | 50000 | 20.503 ms | 2.52 | 33 | 1628.0 | 410.1 | 7.7 KB | 7.39 |
| ReadLine_ | Cols | 50000 | 16.513 ms | 2.03 | 33 | 2021.3 | 330.3 | 108778.76 KB | 104,394.99 |
| CsvHelper | Cols | 50000 | 74.224 ms | 9.13 | 33 | 449.7 | 1484.5 | 445.85 KB | 427.88 |
| Sep______ | Asset | 50000 | 39.523 ms | 1.00 | 33 | 844.5 | 790.5 | 13802.63 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 23.386 ms | 0.59 | 33 | 1427.2 | 467.7 | 13981.76 KB | 1.01 |
| Sylvan___ | Asset | 50000 | 50.803 ms | 1.29 | 33 | 657.0 | 1016.1 | 13962.08 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 114.306 ms | 2.89 | 33 | 292.0 | 2286.1 | 122304.45 KB | 8.86 |
| CsvHelper | Asset | 50000 | 88.786 ms | 2.25 | 33 | 375.9 | 1775.7 | 13970.43 KB | 1.01 |
| Sep______ | Asset | 1000000 | 752.681 ms | 1.00 | 667 | 887.1 | 752.7 | 266669 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 377.733 ms | 0.50 | 667 | 1767.7 | 377.7 | 267992.5 KB | 1.00 |
| Sylvan___ | Asset | 1000000 | 1,091.345 ms | 1.45 | 667 | 611.8 | 1091.3 | 266825.09 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 2,615.390 ms | 3.47 | 667 | 255.3 | 2615.4 | 2442319.06 KB | 9.16 |
| CsvHelper | Asset | 1000000 | 1,756.409 ms | 2.33 | 667 | 380.2 | 1756.4 | 266839.53 KB | 1.00 |
AMD.Ryzen.9.9950X - PackageAssets with Quotes Benchmark Results (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 4.186 ms | 1.00 | 33 | 7973.2 | 83.7 | 1.02 KB | 1.00 |
| Sep_Async | Row | 50000 | 4.303 ms | 1.03 | 33 | 7756.4 | 86.1 | 1.02 KB | 1.00 |
| Sep_Unescape | Row | 50000 | 4.170 ms | 1.00 | 33 | 8004.6 | 83.4 | 1.02 KB | 1.00 |
| Sylvan___ | Row | 50000 | 10.639 ms | 2.54 | 33 | 3137.2 | 212.8 | 8.46 KB | 8.33 |
| ReadLine_ | Row | 50000 | 9.464 ms | 2.26 | 33 | 3526.7 | 189.3 | 108778.73 KB | 107,105.21 |
| CsvHelper | Row | 50000 | 27.532 ms | 6.58 | 33 | 1212.3 | 550.6 | 19.95 KB | 19.64 |
| Sep______ | Cols | 50000 | 5.125 ms | 1.00 | 33 | 6512.0 | 102.5 | 1.02 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 5.701 ms | 1.11 | 33 | 5854.7 | 114.0 | 1.02 KB | 1.00 |
| Sylvan___ | Cols | 50000 | 11.930 ms | 2.33 | 33 | 2797.9 | 238.6 | 8.46 KB | 8.33 |
| ReadLine_ | Cols | 50000 | 10.103 ms | 1.97 | 33 | 3303.7 | 202.1 | 108778.73 KB | 107,105.21 |
| CsvHelper | Cols | 50000 | 40.502 ms | 7.90 | 33 | 824.1 | 810.0 | 445.61 KB | 438.75 |
| Sep______ | Asset | 50000 | 28.119 ms | 1.00 | 33 | 1187.0 | 562.4 | 13802.38 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 16.974 ms | 0.60 | 33 | 1966.4 | 339.5 | 13995.39 KB | 1.01 |
| Sylvan___ | Asset | 50000 | 33.211 ms | 1.18 | 33 | 1005.0 | 664.2 | 13961.94 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 94.445 ms | 3.36 | 33 | 353.4 | 1888.9 | 122304.82 KB | 8.86 |
| CsvHelper | Asset | 50000 | 51.841 ms | 1.84 | 33 | 643.8 | 1036.8 | 13970.67 KB | 1.01 |
| Sep______ | Asset | 1000000 | 520.110 ms | 1.00 | 667 | 1283.8 | 520.1 | 266667.34 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 228.902 ms | 0.44 | 667 | 2917.0 | 228.9 | 269331.98 KB | 1.01 |
| Sylvan___ | Asset | 1000000 | 715.715 ms | 1.38 | 667 | 932.9 | 715.7 | 266826.29 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 2,134.339 ms | 4.10 | 667 | 312.8 | 2134.3 | 2442318.89 KB | 9.16 |
| CsvHelper | Asset | 1000000 | 1,114.437 ms | 2.14 | 667 | 599.1 | 1114.4 | 266832.37 KB | 1.00 |
AMD.Ryzen.9.9950X3D2 - PackageAssets with Quotes Benchmark Results (Sep 0.17.1.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 4.252 ms | 1.00 | 33 | 7850.6 | 85.0 | 1.02 KB | 1.00 |
| Sep_Async | Row | 50000 | 4.180 ms | 0.98 | 33 | 7984.6 | 83.6 | 1.02 KB | 1.00 |
| Sep_Unescape | Row | 50000 | 3.989 ms | 0.94 | 33 | 8367.3 | 79.8 | 1.02 KB | 1.00 |
| Sylvan___ | Row | 50000 | 10.795 ms | 2.54 | 33 | 3091.8 | 215.9 | 8.46 KB | 8.33 |
| ReadLine_ | Row | 50000 | 9.123 ms | 2.15 | 33 | 3658.5 | 182.5 | 108778.73 KB | 107,105.21 |
| CsvHelper | Row | 50000 | 28.260 ms | 6.65 | 33 | 1181.1 | 565.2 | 19.95 KB | 19.64 |
| Sep______ | Cols | 50000 | 5.014 ms | 1.00 | 33 | 6656.3 | 100.3 | 1.02 KB | 1.00 |
| Sep_Unescape | Cols | 50000 | 5.367 ms | 1.07 | 33 | 6219.5 | 107.3 | 1.02 KB | 1.00 |
| Sylvan___ | Cols | 50000 | 12.688 ms | 2.53 | 33 | 2630.6 | 253.8 | 8.46 KB | 8.33 |
| ReadLine_ | Cols | 50000 | 9.400 ms | 1.87 | 33 | 3550.9 | 188.0 | 108778.73 KB | 107,105.21 |
| CsvHelper | Cols | 50000 | 41.837 ms | 8.34 | 33 | 797.8 | 836.7 | 445.6 KB | 438.75 |
| Sep______ | Asset | 50000 | 25.479 ms | 1.00 | 33 | 1310.0 | 509.6 | 13801.9 KB | 1.00 |
| Sep_MT___ | Asset | 50000 | 12.111 ms | 0.48 | 33 | 2755.9 | 242.2 | 13975.38 KB | 1.01 |
| Sylvan___ | Asset | 50000 | 31.375 ms | 1.23 | 33 | 1063.8 | 627.5 | 13961.65 KB | 1.01 |
| ReadLine_ | Asset | 50000 | 68.645 ms | 2.70 | 33 | 486.2 | 1372.9 | 122304.07 KB | 8.86 |
| CsvHelper | Asset | 50000 | 50.970 ms | 2.00 | 33 | 654.8 | 1019.4 | 13969.95 KB | 1.01 |
| Sep______ | Asset | 1000000 | 470.303 ms | 1.00 | 667 | 1419.7 | 470.3 | 266663.86 KB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 226.690 ms | 0.48 | 667 | 2945.5 | 226.7 | 268001.88 KB | 1.01 |
| Sylvan___ | Asset | 1000000 | 667.054 ms | 1.42 | 667 | 1001.0 | 667.1 | 266822.84 KB | 1.00 |
| ReadLine_ | Asset | 1000000 | 1,738.841 ms | 3.70 | 667 | 384.0 | 1738.8 | 2442318.52 KB | 9.16 |
| CsvHelper | Asset | 1000000 | 1,084.758 ms | 2.31 | 667 | 615.5 | 1084.8 | 266831.14 KB | 1.00 |
Apple.M1.(Virtual) - PackageAssets with Quotes Benchmark Results (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 7.063 ms | 1.00 | 33 | 4712.5 | 141.3 | 960 B | 1.00 |
| Sep_Async | Row | 50000 | 7.049 ms | 1.00 | 33 | 4721.6 | 141.0 | 960 B | 1.00 |
| Sep_Unescape | Row | 50000 | 7.173 ms | 1.02 | 33 | 4640.0 | 143.5 | 960 B | 1.00 |
| Sylvan___ | Row | 50000 | 20.460 ms | 2.90 | 33 | 1626.7 | 409.2 | 7516 B | 7.83 |
| ReadLine_ | Row | 50000 | 19.699 ms | 2.79 | 33 | 1689.5 | 394.0 | 111389416 B | 116,030.64 |
| CsvHelper | Row | 50000 | 46.922 ms | 6.64 | 33 | 709.3 | 938.4 | 20496 B | 21.35 |
| Sep______ | Cols | 50000 | 8.268 ms | 1.00 | 33 | 4025.4 | 165.4 | 960 B | 1.00 |
| Sep_Unescape | Cols | 50000 | 9.084 ms | 1.10 | 33 | 3663.8 | 181.7 | 961 B | 1.00 |
| Sylvan___ | Cols | 50000 | 23.584 ms | 2.85 | 33 | 1411.2 | 471.7 | 7516 B | 7.83 |
| ReadLine_ | Cols | 50000 | 19.731 ms | 2.39 | 33 | 1686.8 | 394.6 | 111389416 B | 116,030.64 |
| CsvHelper | Cols | 50000 | 72.376 ms | 8.75 | 33 | 459.8 | 1447.5 | 456296 B | 475.31 |
| Sep______ | Asset | 50000 | 35.076 ms | 1.00 | 33 | 948.8 | 701.5 | 14133372 B | 1.00 |
| Sep_MT___ | Asset | 50000 | 21.356 ms | 0.61 | 33 | 1558.4 | 427.1 | 14273964 B | 1.01 |
| Sylvan___ | Asset | 50000 | 51.378 ms | 1.47 | 33 | 647.8 | 1027.6 | 14295882 B | 1.01 |
| ReadLine_ | Asset | 50000 | 117.825 ms | 3.37 | 33 | 282.5 | 2356.5 | 125240038 B | 8.86 |
| CsvHelper | Asset | 50000 | 79.105 ms | 2.26 | 33 | 420.7 | 1582.1 | 14305304 B | 1.01 |
| Sep______ | Asset | 1000000 | 712.793 ms | 1.00 | 665 | 934.1 | 712.8 | 273067136 B | 1.00 |
| Sep_MT___ | Asset | 1000000 | 480.678 ms | 0.67 | 665 | 1385.1 | 480.7 | 283217688 B | 1.04 |
| Sylvan___ | Asset | 1000000 | 1,120.908 ms | 1.57 | 665 | 594.0 | 1120.9 | 273226744 B | 1.00 |
| ReadLine_ | Asset | 1000000 | 2,250.766 ms | 3.16 | 665 | 295.8 | 2250.8 | 2500933480 B | 9.16 |
| CsvHelper | Asset | 1000000 | 1,737.967 ms | 2.44 | 665 | 383.1 | 1738.0 | 273241080 B | 1.00 |
Cobalt.100 - PackageAssets with Quotes Benchmark Results (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Row | 50000 | 10.92 ms | 1.00 | 33 | 3057.5 | 218.3 | 960 B | 1.00 |
| Sep_Async | Row | 50000 | 11.08 ms | 1.02 | 33 | 3012.0 | 221.6 | 962 B | 1.00 |
| Sep_Unescape | Row | 50000 | 10.98 ms | 1.01 | 33 | 3040.5 | 219.6 | 962 B | 1.00 |
| Sylvan___ | Row | 50000 | 23.03 ms | 2.11 | 33 | 1449.2 | 460.6 | 7478 B | 7.79 |
| ReadLine_ | Row | 50000 | 24.21 ms | 2.22 | 33 | 1378.4 | 484.3 | 111389416 B | 116,030.64 |
| CsvHelper | Row | 50000 | 62.88 ms | 5.76 | 33 | 530.8 | 1257.7 | 20424 B | 21.27 |
| Sep______ | Cols | 50000 | 12.47 ms | 1.00 | 33 | 2677.2 | 249.3 | 962 B | 1.00 |
| Sep_Unescape | Cols | 50000 | 13.73 ms | 1.10 | 33 | 2430.4 | 274.7 | 962 B | 1.00 |
| Sylvan___ | Cols | 50000 | 27.41 ms | 2.20 | 33 | 1217.5 | 548.3 | 7481 B | 7.78 |
| ReadLine_ | Cols | 50000 | 25.01 ms | 2.01 | 33 | 1334.7 | 500.2 | 111389416 B | 115,789.41 |
| CsvHelper | Cols | 50000 | 95.34 ms | 7.65 | 33 | 350.1 | 1906.8 | 456368 B | 474.40 |
| Sep______ | Asset | 50000 | 36.81 ms | 1.00 | 33 | 906.7 | 736.3 | 14133000 B | 1.00 |
| Sep_MT___ | Asset | 50000 | 14.89 ms | 0.40 | 33 | 2241.8 | 297.8 | 14190284 B | 1.00 |
| Sylvan___ | Asset | 50000 | 58.64 ms | 1.59 | 33 | 569.2 | 1172.9 | 14295523 B | 1.01 |
| ReadLine_ | Asset | 50000 | 126.40 ms | 3.43 | 33 | 264.1 | 2528.0 | 125238788 B | 8.86 |
| CsvHelper | Asset | 50000 | 106.21 ms | 2.89 | 33 | 314.3 | 2124.2 | 14305310 B | 1.01 |
| Sep______ | Asset | 1000000 | 912.09 ms | 1.00 | 667 | 732.1 | 912.1 | 273063648 B | 1.00 |
| Sep_MT___ | Asset | 1000000 | 389.94 ms | 0.43 | 667 | 1712.3 | 389.9 | 282220464 B | 1.03 |
| Sylvan___ | Asset | 1000000 | 1,276.04 ms | 1.40 | 667 | 523.3 | 1276.0 | 273225288 B | 1.00 |
| ReadLine_ | Asset | 1000000 | 3,123.47 ms | 3.42 | 667 | 213.8 | 3123.5 | 2500931904 B | 9.16 |
| CsvHelper | Asset | 1000000 | 2,292.40 ms | 2.51 | 667 | 291.3 | 2292.4 | 273234848 B | 1.00 |
PackageAssets with Quotes Benchmark Results (SERVER GC)
Here again are benchmark results with server garbage collection, which provides significant speedup over workstation garbage collection.
AMD.EPYC.7763 - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 36.95 ms | 1.00 | 33 | 900.8 | 738.9 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 22.04 ms | 0.60 | 33 | 1510.2 | 440.8 | 13.53 MB | 1.00 |
| Sylvan___ | Asset | 50000 | 60.86 ms | 1.65 | 33 | 546.8 | 1217.2 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 64.87 ms | 1.76 | 33 | 513.1 | 1297.4 | 119.44 MB | 8.86 |
| CsvHelper | Asset | 50000 | 118.69 ms | 3.21 | 33 | 280.4 | 2373.7 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 763.29 ms | 1.00 | 665 | 872.3 | 763.3 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 429.16 ms | 0.56 | 665 | 1551.4 | 429.2 | 261.76 MB | 1.01 |
| Sylvan___ | Asset | 1000000 | 1,223.33 ms | 1.60 | 665 | 544.3 | 1223.3 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 1,393.90 ms | 1.83 | 665 | 477.7 | 1393.9 | 2385.07 MB | 9.16 |
| CsvHelper | Asset | 1000000 | 2,365.35 ms | 3.10 | 665 | 281.5 | 2365.4 | 260.58 MB | 1.00 |
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.12.0.0, Sylvan 1.4.3.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 39.09 ms | 1.00 | 33 | 854.0 | 781.7 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 25.90 ms | 0.66 | 33 | 1288.9 | 517.9 | 13.58 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 59.92 ms | 1.53 | 33 | 557.0 | 1198.4 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 60.27 ms | 1.54 | 33 | 553.8 | 1205.4 | 119.44 MB | 8.86 |
| CsvHelper | Asset | 50000 | 111.54 ms | 2.85 | 33 | 299.2 | 2230.8 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 810.33 ms | 1.00 | 667 | 824.0 | 810.3 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 433.80 ms | 0.54 | 667 | 1539.2 | 433.8 | 261.43 MB | 1.00 |
| Sylvan___ | Asset | 1000000 | 1,216.78 ms | 1.50 | 667 | 548.8 | 1216.8 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 1,079.99 ms | 1.33 | 667 | 618.3 | 1080.0 | 2385.07 MB | 9.16 |
| CsvHelper | Asset | 1000000 | 2,275.20 ms | 2.81 | 667 | 293.5 | 2275.2 | 260.58 MB | 1.00 |
AMD.Ryzen.9.5950X - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.9.0.0, Sylvan 1.3.9.0, CsvHelper 33.0.1.24)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 26.42 ms | 1.00 | 33 | 1263.1 | 528.5 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 11.53 ms | 0.44 | 33 | 2894.1 | 230.7 | 13.64 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 43.05 ms | 1.63 | 33 | 775.3 | 861.1 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 37.30 ms | 1.41 | 33 | 894.8 | 746.0 | 119.44 MB | 8.86 |
| CsvHelper | Asset | 50000 | 78.91 ms | 2.99 | 33 | 423.0 | 1578.1 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 538.48 ms | 1.00 | 667 | 1240.0 | 538.5 | 260.43 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 213.29 ms | 0.40 | 667 | 3130.5 | 213.3 | 261.37 MB | 1.00 |
| Sylvan___ | Asset | 1000000 | 879.04 ms | 1.63 | 667 | 759.6 | 879.0 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 642.57 ms | 1.19 | 667 | 1039.1 | 642.6 | 2385.07 MB | 9.16 |
| CsvHelper | Asset | 1000000 | 1,598.79 ms | 2.97 | 667 | 417.6 | 1598.8 | 260.58 MB | 1.00 |
AMD.Ryzen.9.9950X - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 17.929 ms | 1.00 | 33 | 1861.7 | 358.6 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 5.872 ms | 0.33 | 33 | 5684.4 | 117.4 | 13.64 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 26.385 ms | 1.47 | 33 | 1265.0 | 527.7 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 21.798 ms | 1.22 | 33 | 1531.2 | 436.0 | 119.44 MB | 8.86 |
| CsvHelper | Asset | 50000 | 47.041 ms | 2.62 | 33 | 709.5 | 940.8 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 358.149 ms | 1.00 | 667 | 1864.3 | 358.1 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 105.507 ms | 0.29 | 667 | 6328.6 | 105.5 | 261.97 MB | 1.01 |
| Sylvan___ | Asset | 1000000 | 542.348 ms | 1.51 | 667 | 1231.1 | 542.3 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 399.659 ms | 1.12 | 667 | 1670.7 | 399.7 | 2385.07 MB | 9.16 |
| CsvHelper | Asset | 1000000 | 942.707 ms | 2.63 | 667 | 708.3 | 942.7 | 260.58 MB | 1.00 |
AMD.Ryzen.9.9950X3D2 - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.17.1.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 18.010 ms | 1.00 | 33 | 1853.3 | 360.2 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 6.742 ms | 0.37 | 33 | 4950.8 | 134.8 | 13.64 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 28.010 ms | 1.56 | 33 | 1191.6 | 560.2 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 24.633 ms | 1.37 | 33 | 1355.0 | 492.7 | 119.44 MB | 8.86 |
| CsvHelper | Asset | 50000 | 48.301 ms | 2.68 | 33 | 691.0 | 966.0 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 365.281 ms | 1.00 | 667 | 1827.9 | 365.3 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 107.768 ms | 0.30 | 667 | 6195.8 | 107.8 | 261.79 MB | 1.01 |
| Sylvan___ | Asset | 1000000 | 571.951 ms | 1.57 | 667 | 1167.4 | 572.0 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 512.824 ms | 1.40 | 667 | 1302.0 | 512.8 | 2385.07 MB | 9.16 |
| CsvHelper | Asset | 1000000 | 970.257 ms | 2.66 | 667 | 688.2 | 970.3 | 260.58 MB | 1.00 |
Apple.M1.(Virtual) - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 25.99 ms | 1.00 | 33 | 1280.6 | 519.8 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 11.90 ms | 0.46 | 33 | 2797.0 | 238.0 | 13.6 MB | 1.01 |
| Sylvan___ | Asset | 50000 | 45.83 ms | 1.77 | 33 | 726.2 | 916.6 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 35.97 ms | 1.39 | 33 | 925.3 | 719.4 | 119.44 MB | 8.86 |
| CsvHelper | Asset | 50000 | 76.69 ms | 2.95 | 33 | 434.0 | 1533.8 | 13.64 MB | 1.01 |
| Sep______ | Asset | 1000000 | 542.50 ms | 1.00 | 665 | 1227.3 | 542.5 | 260.41 MB | 1.00 |
| Sep_MT___ | Asset | 1000000 | 239.81 ms | 0.44 | 665 | 2776.4 | 239.8 | 266.31 MB | 1.02 |
| Sylvan___ | Asset | 1000000 | 961.86 ms | 1.77 | 665 | 692.2 | 961.9 | 260.57 MB | 1.00 |
| ReadLine_ | Asset | 1000000 | 929.89 ms | 1.71 | 665 | 716.0 | 929.9 | 2385.07 MB | 9.16 |
| CsvHelper | Asset | 1000000 | 1,584.75 ms | 2.92 | 665 | 420.1 | 1584.8 | 260.58 MB | 1.00 |
Cobalt.100 - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.13.0.0, Sylvan 1.4.4.0, CsvHelper 33.1.0.26)
| Method | Scope | Rows | Mean | Ratio | MB | MB/s | ns/row | Allocated | Alloc Ratio |
|---|---|---|---|---|---|---|---|---|---|
| Sep______ | Asset | 50000 | 39.23 ms | 1.00 | 33 | 850.7 | 784.7 | 13.48 MB | 1.00 |
| Sep_MT___ | Asset | 50000 | 14.62 ms | 0.37 | 33 | 2283.8 | 292.3 | 13.53 MB | 1.00 |
| Sylvan___ | Asset | 50000 | 56.71 ms | 1.45 | 33 | 588.5 | 1134.3 | 13.63 MB | 1.01 |
| ReadLine_ | Asset | 50000 | 55.29 ms | 1.41 | 33 | 603.7 | 1105.8 | 119.44 MB | 8.86 |
| CsvHelper |
README truncated. Read the full version on GitHub.