Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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.

License

NotificationsYou must be signed in to change notification settings

nietras/Sep

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

.NETC#Build StatusSuper-LintercodecovCodeQLNugetReleasedownloadsSizeLicenseBlogGitHub Repo stars

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 targetted atmachine learning use cases.

⭐ Please star this project if you like it. ⭐

🌃 Modern - utilizes features such asSpan<T>,Generic Math(ISpanParsable<T>/ISpanFormattable),ref struct,ArrayPool<T>and similar from.NET 7+ and C#11+ for a modernand highly efficient implementation.

🔎 Minimal - a succinct yet expressive API with few options and no hiddenchanges to input or output. What you read/write is what you get. E.g. by defaultthere is no "automatic" escaping/unescaping of quotes or trimming of spaces. Toenable this seeSepReaderOptions andUnescaping andTrimming. SeeSepWriterOptions forEscaping.

🚀 Fast - blazing fast with both architecture specific and cross-platformSIMD vectorized parsing incl. 64/128/256/512-bit paths e.g. AVX2, AVX-512 (.NET8.0+), NEON. UsescsFastFloat forfast parsing of floating points. Seedetailedbenchmarks for cross-platform results.

🌪️ Multi-threaded - unparalleled speed with highly efficient parallel CSVparsing that isup to 35x faster thanCsvHelper, seeParallelEnumerate andbenchmarks.

🌀 Async support - efficientValueTask basedasync/await support.Requires C# 13.0+ and for .NET 9.0+ includesSepReader implementingIAsyncEnumerable<>. SeeAsync Support for details.

🗑️ Zero allocation - intelligent and efficient memory management allowingfor zero allocations after warmup incl. supporting use cases of reading orwriting arrays of values (e.g. features) easily without repeated allocations.

✅ Thorough tests - great code coverage and focus on edge case testing incl.randomizedfuzz testing.

🌐 Cross-platform - works on any platform, any architecture supported byNET. 100% managed and written in beautiful modern C#.

✂️ Trimmable and AOT/NativeAOT compatible - no problematic reflection ordynamic code generation. Hence, fullytrimmableandAhead-of-Timecompatible. With a simple console tester program executable possible in just afew MBs. 💾

🗣️ Opinionated and pragmatic - conforms to the essentials ofRFC-4180, but takes an opinionated andpragmatic approach towards this especially with regards to quoting and lineends. See sectionRFC-4180.

Example |Naming and Terminology |API |Limitations and Constraints |Comparison Benchmarks |Example Catalogue |RFC-4180 |FAQ |Public API Reference

Example

vartext="""           A;B;C;D;E;F           Sep;🚀;1;1.2;0.1;0.5           CSV;✅;2;2.2;0.2;1.5           """;usingvarreader=Sep.Reader().FromText(text);// Infers separator 'Sep' from headerusingvarwriter=reader.Spec.Writer().ToText();// Writer defined from reader 'Spec'// Use .FromFile(...)/ToFile(...) for filesvaridx=reader.Header.IndexOf("B");varnms=new[]{"E","F"};foreach(varreadRowinreader)// Read one row at a time{vara=readRow["A"].Span;// Column as ReadOnlySpan<char>varb=readRow[idx].ToString();// Column to string (might be pooled)varc=readRow["C"].Parse<int>();// Parse any T : ISpanParsable<T>vard=readRow["D"].Parse<float>();// Parse float/double fast via csFastFloatvars=readRow[nms].Parse<double>();// Parse multiple columns as Span<T>// - Sep handles array allocation and reuseforeach(refvarvins){v*=10;}usingvarwriteRow=writer.NewRow();// Start new row. Row written on Dispose.writeRow["A"].Set(a);// Set by ReadOnlySpan<char>writeRow["B"].Set(b);// Set by stringwriteRow["C"].Set($"{c*2}");// Set via InterpolatedStringHandler, no allocswriteRow["D"].Format(d/2);// Format any T : ISpanFormattablewriteRow[nms].Format(s);// Format multiple columns directly// Columns are added on first access as ordered, header written when first row written}varexpected="""               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.

Naming and Terminology

Sep uses naming and terminology that is not based onRFC-4180, butis more tailored to usage in machine learning or similar. Additionally, Septakes a pragmatic approach towards names by using short names and abbreviationswhere it makes sense and there should be no ambiguity given the context. Thatis, usingSep forSeparator andCol forColumn to keep code succinct.

TermDescription
SepShort for separator, also calleddelimiter. E.g. comma (,) is the separator for the separated values in acsv-file.
HeaderOptional first row defining names of columns.
RowA row is a collection of col(umn)s, which may span multiple lines. Also calledrecord.
ColShort for column, also calledfield.
LineHorizontal set of characters until a line ending;\r\n,\r,\n.
Index0-based that isRowIndex will be 0 for first row (or the header if present).
Number1-based that isLineNumber will be 1 for the first line (as innotepad). Given a row may span multiple lines a row can have aFrom line number and aToExcl 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 entrypoint to using the library and the container for a validated separator. That is,Sep is basically defined as:

publicreadonlyrecordstructSep(charSeparator);

The separatorchar is validated upon construction and is guaranteed to bewithin a limited range and not being achar like" (quote) or similar. Thiscan be seen insrc/Sep/Sep.cs. The separator is constrainedalso for internal optimizations, so you cannot use anychar as a separator.

⚠ Note that all types are within the namespacenietras.SeparatedValues and notSep since it is problematic to have a type and a namespace with the same name.

To get started you can useSep as the static entry point to building either areader or writer. That is, forSepReader:

usingvarreader=Sep.Reader().FromFile("titanic.csv");

where.Reader() is a convenience method corresponding to:

usingvarreader=Sep.Auto.Reader().FromFile("titanic.csv");

whereSep? Auto => null; is a static property that returnsnull for anullableSep to signify that the separator should be inferred from the firstrow, which might be a header. If the first row does not contain any of the bydefault supported separators or there are no rows, the default separator will beused.

⚠ Note Sep uses; as the default separator, since this is what was used in aninternal proprietary library which Sep was built to replace. This is also toavoid 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:

usingvarreader=Sep.New(',').Reader().FromFile("titanic.csv");

or

varsep=newSep(',');usingvarreader=sep.Reader().FromFile("titanic.csv");

Similarly, forSepWriter:

usingvarwriter=Sep.Writer().ToFile("titanic.csv");

or

usingvarwriter=Sep.New(',').Writer().ToFile("titanic.csv");

where you have to specify a valid separator, since it cannot be inferred. Tofascillitate easy flow of the separator andCultureInfo bothSepReader andSepWriter expose aSpec property of typeSepSpec that simply defines thosetwo. This means you can write:

usingvarreader=Sep.Reader().FromFile("titanic.csv");usingvarwriter=reader.Spec.Writer().ToFile("titanic-survivors.csv");

where thewriter then will use the separator inferred by the reader, forexample.

API Pattern

In general, both reading and writing follow a similar pattern:

Sep/Spec => SepReaderOptions => SepReader => Row => Col(s) => Span/ToString/ParseSep/Spec => SepWriterOptions => SepWriter => Row => Col(s) => Set/Format

where each continuation flows fluently from the preceding type. For example,Reader() is an extension method toSep orSepSpec that returns aSepReaderOptions. Similarly,Writer() is an extension method toSep orSepSpec that returns aSepWriterOptions.

SepReaderOptions andSepWriterOptions are optionally configurable.That and the APIs for reader and writer is covered in the following sections.

For a complete example, see theexample above or theReadMeTest.cs.

⚠ Note that it is important to understand that SepRow/Col/Cols areref structs(please follow theref struct link and understand how this limits the usage ofthose). This is due to these types being simplefacades or indirections to theunderlying reader or writer. That means you cannot use LINQ or create an arrayof all rows likereader.ToArray(). While for .NET9+ the reader is nowIEnumerable<> sinceref structs can now be used in interfaces that havewhere T: allows ref structthis still does not mean it is LINQ compatible. Hence, if you need store per rowstate or similar you need to parse or copy to different types instead. The sameapplies toCol/Cols which point to internal state that is also reused. Thisis to avoid repeated allocations for each row and get the best possibleperformance, while still defining a well structured and straightforward API thatguides users to relevant functionality. SeeWhy SepReader Was Not IEnumerableUntil .NET 9 and Is Not LINQCompatiblefor more.

⚠ For a full overview of public types and methods seePublic APIReference.

SepReader API

SepReader API has the following structure (in pseudo-C# code):

usingvarreader=Sep.Reader(o=>o).FromFile/FromText/From...;varheader=reader.Header;var_=header.IndexOf/IndicesOf/NamesStartingWith...;foreach(varrowinreader){var_=row[colName/colNames].Span/ToString/Parse<T>...;var_=row[colIndex/colIndices].Span/ToString/Parse<T>...;}

That is, to useSepReader follow the points below:

  1. Optionally defineSep or use default automatically inferred separator.
  2. Specify reader with optional configuration ofSepReaderOptions. Forexample, if a csv-file does not have a header this can be configured via:
    Sep.Reader(o=>owith{HasHeader=false})
    For all options seeSepReaderOptions.
  3. Specify source e.g. file, text (string),TextWriter, etc. viaFromextension methods.
  4. Optionally access the header. For example, to get all columns starting withGT_ use:
    varcolNames=header.NamesStarting("GT_");varcolIndices=header.IndicesOf(colNames);
  5. Enumerate rows. One row at a time.
  6. Access a column by name or index. Or access multiple columns with names andindices.Sep internally handles pooled allocation and reuse of arrays formultiple columns.
  7. UseSpan to access the column directly as aReadOnlySpan<char>. Or useToString to 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>publicSep?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>publicintInitialBufferLength{get;init;}=SepDefaults.InitialBufferLength;/// <summary>/// Specifies the culture used for parsing./// May be `null` for default culture./// </summary>publicCultureInfo?CultureInfo{get;init;}=SepDefaults.CultureInfo;/// <summary>/// Indicates whether the first row is a header row./// </summary>publicboolHasHeader{get;init;}=true;/// <summary>/// Specifies <see cref="IEqualityComparer{T}" /> to use/// for comparing header column names and looking up index./// </summary>publicIEqualityComparer<string>ColNameComparer{get;init;}=SepDefaults.ColNameComparer;/// <summary>/// Specifies the method factory used to convert a column span/// of `char`s to a `string`./// </summary>publicSepCreateToStringCreateToString{get;init;}=SepToString.Direct;/// <summary>/// Disables using [csFastFloat](https://github.com/CarlVerret/csFastFloat)/// for parsing `float` and `double`./// </summary>publicboolDisableFastFloat{get;init;}=false;/// <summary>/// Disables checking if column count is the same for all rows./// </summary>publicboolDisableColCountCheck{get;init;}=false;/// <summary>/// Disables detecting and parsing quotes./// </summary>publicboolDisableQuotesParsing{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" /> 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. Header/// columns/names will also be unescaped./// Requires <see cref="DisableQuotesParsing"/> to be false./// </remarks>publicboolUnescape{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. Header/// columns/names will also be trimmed. Note that only the space ` ` (ASCII/// 32) character is trimmed, not any whitespace character./// </remarks>publicSepTrimTrim{get;init;}=SepTrim.None;/// <summary>/// Forwarded to <see/// cref="System.Threading.Tasks.ValueTask.ConfigureAwait(bool)"/> or/// similar when async methods are called./// </summary>publicboolAsyncContinueOnCapturedContext{get;init;}=false;

Unescaping

While great care has been taken to ensure Sep unescaping of quotes is bothcorrect and fast, there is always the question of how does one respond toinvalid 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 invalidinput is handled. For Sep the design choice has been based on not wanting tothrow exceptions and to use a principle that is both reasonably fast and simple.

InputValidCsvHelperCsvHelper¹SylvanSep²
aTrueaaaa
""True
""""True""""
""""""True""""""""
"a"Trueaaaa
"a""a"Truea"aa"aa"aa"a
"a""a""a"Truea"a"aa"a"aa"a"aa"a"a
a""aFalseEXCEPTIONa""aa""aa""a
a"a"aFalseEXCEPTIONa"a"aa"a"aa"a"a
·""·FalseEXCEPTION·""··""··""·
·"a"·FalseEXCEPTION·"a"··"a"··"a"·
·""FalseEXCEPTION·""·""·""
·"a"FalseEXCEPTION·"a"·"a"·"a"
a"""aFalseEXCEPTIONa"""aa"""aa"""a
"a"a"a"FalseEXCEPTIONaa"a"a"a"aaa"a
""·FalseEXCEPTION·"·
"a"·FalseEXCEPTIONa"
"a"""aFalseEXCEPTIONaaEXCEPTIONa"a
"a"""a"FalseEXCEPTIONaa"a"a<NULL>a"a"
""a"FalseEXCEPTIONa""aa"
"a"a"FalseEXCEPTIONaa"a"aaa"
""a"a""FalseEXCEPTIONa"a"""a"a"a"a"
"""FalseEXCEPTION"
"""""False""EXCEPTION""

· (middle dot) is whitespace to make this visible

¹ CsvHelper withBadDataFound = null

² Sep withUnescape = true inSepReaderOptions

Trimming

Sep supports trimming by theSepTrim flags enum, whichhas two options as documented there. Below the result of both trimming andunescaping is shown in comparison to CsvHelper. Note unescaping is enabled forall results shown. It is possible to trim without unescaping too, of course.

As can be seen Sep supports a simple principle of trimmingbefore andafterunescaping with trimming before unescaping being important for unescaping ifthere is a starting quote after spaces.

InputCsvHelper TrimCsvHelper InsideQuotesCsvHelper All¹Sep OuterSep AfterUnescapeSep All²
aaaaaaa
·aa·aaaaa
aaaaa
·a·a·a·aaaa
·a·a·a·a·a·a·a·aa·aa·aa·a
"a"aaaaaa
"·a"·aaa·aaa
"a·"aaaa
"·a·"·a·aa·a·aa
"·a·a·"·a·a·a·aa·a·a·a·a·aa·a
·"a"·a·"a"·aa"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 withTrimOptions.Trim | TrimOptions.InsideQuotes

² Sep withSepTrim.All = SepTrim.Outer | SepTrim.AfterUnescape inSepReaderOptions

SepReader Debuggability

Debuggability is an important part of any library and while this is still a workin progress for Sep,SepReader does have a unique feature when looking at itand it's row or cols in a debug context. Given the below example code:

vartext="""           Key;Value           A;"1           2           3"           B;"Apple           Banana           Orange           Pear"           """;usingvarreader=Sep.Reader().FromText(text);foreach(varrowinreader){// Hover over reader, row or col when breaking herevarcol=row[1];if(Debugger.IsAttached&&row.RowIndex==2){Debugger.Break();}Debug.WriteLine(col.ToString());}

and you are hovering overreader when the break is triggered then this willshow something like:

String Length=55

That is, it will show information of the source for the reader, in this case astring of length 55.

SepReader.Row Debuggability

If you are hovering overrow 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#rangeexpression,so that one can easily find the row in question innotepad or similar. Thismeans Sep has to track line endings inside quotes and is an example of a featurethat 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 theexample text to notepad or similar to see the effect.

Additionally, if you expand therow in the debugger (e.g. via the smalltriangle) 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 overcol 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 timeandSepReader.Row is just a simplefacade or indirection to the underlyingreader. This is why it is defined as aref struct. In fact, the followingcode:

usingvarreader=Sep.Reader().FromText(text);foreach(varrowinreader){}

can also be rewritten as:

usingvarreader=Sep.Reader().FromText(text);while(reader.MoveNext()){varrow=reader.Current;}

whererow is just afacade for exposing row specific functionality. That is,row is still basically thereader underneath. Hence, let's look at usingLINQ withSepReader implementingIEnumerable<SepReader.Row> and theRownot being aref struct. Then, you would be able to write something like below:

usingvarreader=Sep.Reader().FromText(text);SepReader.Row[]rows=reader.ToArray();

GivenRow is just a facade for the reader, this would be equivalent towriting:

usingvarreader=Sep.Reader().FromText(text);SepReader[]rows=reader.ToArray();

which hopefully makes it clear why this is not a good thing. The array wouldeffectively be the reader repeated several times. If this would have to besupported one would have to allocate memory for each row always, which wouldbasically be no different than aReadLine approach as benchmarked inComparison Benchmarks.

This is perhaps also the reason why no other efficient .NET CSV parser (known toauthor) implements an API pattern like Sep, but instead let the reader defineall functionality directly and hence only let's you access the current row andcols on that. This API, however, is in this authors opinion not ideal and can bea bit confusing, which is why Sep is designed like it is. The downside is theabove caveat.

The main culprit above is that for exampleToArray() would store aref struct in a heap allocated array, the actual enumeration is not a problem andhence implementingIEnumerable<SepReader.Row> is not the problem as such. Theproblem was that prior to .NET 9 it was not possible to implement this interfacewithT being aref struct, but with C# 13allows ref struct and .NET 9having annotated such interfaces it is now possible and you can assignSepReader toIEnumerable, but most if not all of LINQ will still not work asshown below.

vartext="""           Key;Value           A;1.1           B;2.2           """;usingvarreader=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();

CallingSelect should in principle be possible if this was annotated withallows ref struct, but it isn't currently.

If you want to use LINQ or similar you have to first parse or transform the rowsinto some other type and enumerate it. This is easy to do and instead ofcounting lines you should focus on how such enumeration can be easily expressedusing C# iterators (akayield return). With local functions this can be doneinside a method like:

vartext="""           Key;Value           A;1.1           B;2.2           """;varexpected=new(stringKey,doubleValue)[]{("A",1.1),("B",2.2),};usingvarreader=Sep.Reader().FromText(text);varactual=Enumerate(reader).ToArray();CollectionAssert.AreEqual(expected,actual);staticIEnumerable<(stringKey,doubleValue)>Enumerate(SepReaderreader){foreach(varrowinreader){yieldreturn(row["Key"].ToString(),row["Value"].Parse<double>());}}

Now if instead refactoring this to something LINQ-compatible by defining acommonEnumerate or similar method it could be:

vartext="""           Key;Value           A;1.1           B;2.2           """;varexpected=new(stringKey,doubleValue)[]{("A",1.1),("B",2.2),};usingvarreader=Sep.Reader().FromText(text);varactual=Enumerate(reader,    row=>(row["Key"].ToString(),row["Value"].Parse<double>())).ToArray();CollectionAssert.AreEqual(expected,actual);staticIEnumerable<T>Enumerate<T>(SepReaderreader,SepReader.RowFunc<T>select){foreach(varrowinreader){yieldreturnselect(row);}}

In fact, Sep provides such a convenience extension method. And, discounting theEnumerate method, this does have less boilerplate, but not really moreeffective lines of code. The issue here is that this tends to favor factoringcode in a way that can become very inefficient quickly. Consider if one wantedto only enumerate rows matching a predicate onKey which meant only 1% of rowswere to be enumerated e.g.:

vartext="""           Key;Value           A;1.1           B;2.2           """;varexpected=new(stringKey,doubleValue)[]{("B",2.2),};usingvarreader=Sep.Reader().FromText(text);varactual=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 thangetting just the key) for all rows. Imagine if this was an array of floatingpoints or similar. Not only would you then be parsing a lot of values you wouldalso be allocated 99x arrays that aren't used after filtering withWhere.

Instead, you should focus on how to express the enumeration in a way that isboth efficient and easy to read. For example, the above could be rewritten as:

vartext="""           Key;Value           A;1.1           B;2.2           """;varexpected=new(stringKey,doubleValue)[]{("B",2.2),};usingvarreader=Sep.Reader().FromText(text);varactual=Enumerate(reader).ToArray();CollectionAssert.AreEqual(expected,actual);staticIEnumerable<(stringKey,doubleValue)>Enumerate(SepReaderreader){foreach(varrowinreader){varkeyCol=row["Key"];if(keyCol.Span.StartsWith("B")){yieldreturn(keyCol.ToString(),row["Value"].Parse<double>());}}}

To accomodate this Sep provides an overload forEnumerate that is similar to:

staticIEnumerable<T>Enumerate<T>(thisSepReaderreader,SepReader.RowTryFunc<T>trySelect){foreach(varrowinreader){if(trySelect(row,outvarvalue)){yieldreturnvalue;}}}

With this the above customEnumerate can be replaced with:

vartext="""           Key;Value           A;1.1           B;2.2           """;varexpected=new(stringKey,doubleValue)[]{("B",2.2),};usingvarreader=Sep.Reader().FromText(text);varactual=reader.Enumerate((SepReader.Rowrow,out(stringKey,doubleValue)kv)=>{varkeyCol=row["Key"];if(keyCol.Span.StartsWith("B")){kv=(keyCol.ToString(),row["Value"].Parse<double>());returntrue;}kv=default;returnfalse;}).ToArray();CollectionAssert.AreEqual(expected,actual);

Note how this is pretty much the same length as the previous customEnumerate.Also worse due to how C# requires specifying types forout parameters whichthen requires all parameter types for the lambda to be specified. Hence, in thiscase the customEnumerate does not take significantly longer to write and is alot more efficient than using LINQ.Where (also avoids allocating a string forkey for each row) and is easier to debug and perhaps even read. All examplesabove can be seen inReadMeTest.cs.

There is a strong case for having an enumerate API though and that is forparallelized enumeration, which will be discussed next.

ParallelEnumerate and Enumerate

As discussed in the previous section Sep providesEnumerate convenienceextension methods, that should be used carefully. Alongside these there areParallelEnumerate extension methods that provide very efficient multi-threadedenumeration. Seebenchmarks for numbers andPublicAPI Reference.

ParallelEnumerate is build on top of LINQAsParallel().AsOrdered() and willreturn exactly the same asEnumerate but with enumeration parallelized. Thiswill use more memory during execution and as many threads as possible via the.NET thread pool. When usingParallelEnumerate one should, therefore (asalways), be certain the provided delegate does not refer to or change anymutable state.

ParallelEnumerate comes with a lot of overhead compared to single-threadedforeach orEnumerate and should be used carefully based on measuring anypotential benefit. Sep goes a long way to make this very efficient by usingpooled arrays and parsing multiple rows in batches, but if the source only has afew rows then any benefit is unlikely.

Due toParallelEnumerate being based on batches of rows it is also importantnot to "abuse" it in-place of LINQAsParallel. The idea is to use it forparsing rows, not for doing expensive per row operations like loading an imageor similar. In that case, you are better off usingAsParallel() afterParallelEnumerate orEnumerate similarly to:

usingvarreader=Sep.Reader().FromFile("very-long.csv");varresults=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 theexpensive work to afterParallelEnumerate/Enumerate,

SepWriter API

SepWriter API has the following structure (in pseudo-C# code):

usingvarwriter=Sep.Writer(o=>o).ToFile/ToText/To...;foreach(vardatainEnumerateData()){usingvarrow=writer.NewRow();var_=row[colName/colNames].Set/Format<T>...;var_=row[colIndex/colIndices].Set/Format<T>...;}

That is, to useSepWriter follow the points below:

  1. Optionally defineSep or use default automatically inferred separator.
  2. Specify writer with optional configuration ofSepWriterOptions.For all options seeSepWriterOptions.
  3. Specify destination e.g. file, text (string viaStringWriter),TextWriter, etc. viaTo extension methods.
  4. MISSING:SepWriter currently does not allow you to define the header upfront. Instead, header is defined by the order in which column names areaccessed/created when defining the row.
  5. Define new rows withNewRow. ⚠ Be sure to dispose any new rows beforestarting the next! For convenience Sep provides an overload forNewRow thattakes aSepReader.Row and copies the columns from that row to the new row:
    usingvarreader=Sep.Reader().FromText(text);usingvarwriter=reader.Spec.Writer().ToText();foreach(varreadRowinreader){usingvarwriteRow=writer.NewRow(readRow);}
  6. Create a column by selecting by name or index. Or multiple columns viaindices and names.Sep internally handles pooled allocation and reuse ofarrays for multiple columns.
  7. UseSet to set the column value either as aReadOnlySpan<char>,stringor via an interpolated string. Or useFormat<T> whereT : IFormattableto formatT to the column value.
  8. Row is written whenDispose is called on the row.

    Note this is to allow a row to be defined flexibly with both columnremoval, moves and renames in the future. This is not yet supported.

SepWriterOptions

The following options are available:

/// <summary>/// Specifies the separator used./// </summary>publicSepSep{get;init;}/// <summary>/// Specifies the culture used for parsing./// May be `null` for default culture./// </summary>publicCultureInfo?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>publicboolWriteHeader{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>publicboolDisableColCountCheck{get;init;}=false;/// <summary>/// Specifies how to handle columns that are/// not set./// </summary>publicSepColNotSetOptionColNotSetOption{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>publicboolEscape{get;init;}=false;/// <summary>/// Forwarded to <see/// cref="System.Threading.Tasks.ValueTask.ConfigureAwait(bool)"/> or/// similar when async methods are called./// </summary>publicboolAsyncContinueOnCapturedContext{get;init;}=false;

Escaping

Escaping is not enabled by default in Sep, but when it is it gives the sameresults as other popular CSV librares as shown below. Although, CsvHelperappears to be escaping spaces as well, which is not necessary.

InputCsvHelperSylvanSep¹
``
·"·"··
aaaa
;";"";"";"
,,,,
"""""""""""""
\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 withEscape = true inSepWriterOptions

Async Support

Sep supports efficientValueTask based asynchronous reading and writing.

However, given bothSepReader.Row andSepWriter.Row areref structs, asthey 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"refand unsafe in iterators and async methods" as covered inWhat's new in C#13. Pleaseconsult details in that for limitations and constraints due to this.

Similarly,SepReader only implementsIAsyncEnumerable<SepReader.Row> (andIEnumerable<SepReader.Row>) for .NET 9.0+/C# 13.0+ since then the interfaceshave been annotated withallows ref struct forT.

Async support is provided on the existingSepReader andSepWriter typessimilar to howTextReader andTextWriter support both sync and async usage.This means you as a developer are responsible for calling async methods andusingawait when necessary. See below for a simple example and consult testson GitHub for more examples.

vartext="""           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 endingusingvarreader=awaitSep.Reader().FromTextAsync(text);awaitusingvarwriter=reader.Spec.Writer().ToText();awaitforeach(varreadRowinreader){awaitusingvarwriteRow=writer.NewRow(readRow);}Assert.AreEqual(text,writer.ToString());

Note how forSepReader theFromTextAsync is suffixed withAsync toindicate async creation, this is due to the reader having to read the first rowof the source at creation to determine both separator and, if file has a header,column names of the header. TheFrom*Async call then has to beawaited.After that rows can be enumerated asynchronously simply by puttingawaitbeforeforeach. If one forgets to do that the rows will be enumeratedsynchronously.

ForSepWriter the usage is kind of reversed.To* methods have noAsyncvariants, since creation is synchronous. That is,StreamWriter is created by asimple constructor call. Nothing is written until a header or row is defined andDispose/DisposeAsync is called on the row.

For reader nothing needs to be asynchronously disposed, sousing does notrequireawait. However, forSepWriter dispose may have to write/flush datato underlyingTextWriter and hence it should be usingDisposeAsync, so youmust useawait using.

To support cancellation many methods have overloads that accept aCancellationToken like theFrom*Async methods for creating aSepReader orfor exampleNewRow forSepWriter. ConsultPublic APIReference for full set of available methods.

Additionally, bothSepReaderOptions andSepWriterOptions feature thebool AsyncContinueOnCapturedContext option that is forwarded to internalConfigureAwait calls, see theConfigureAwaitFAQ for details onthat.

Limitations and Constraints

Sep is designed to be minimal and fast. As such, it has some limitations andconstraints:

  • Comments# are not directly supported. You can skip a row by:
    foreach(varrowinreader){// Skip row if starts with #if(!row.Span.StartsWith("#")){// ...}}
    This does not allow skipping lines before a header row starting with#though. InExample Catalogue a full example is givendetailing how to skip lines before header.

Comparison Benchmarks

To investigate the performance of Sep it is compared to:

  • CsvHelper -the most commonlyused CSV library with a staggeringdownloads downloads on NuGet. Fullyfeatured and battle tested.
  • Sylvan - is well-known and haspreviously been shown to bethe fastest CSV libraries forparsing(Sep changes that 😉).
  • ReadLine/WriteLine - basic naive implementations that read line by lineand split on separator. While writing columns, separators and line endingsdirectly. Does not handle quotes or similar correctly.

All benchmarks are run from/to memory either with:

  • StringReader orStreamReader + MemoryStream
  • StringWriter orStreamWriter + MemoryStream

This to avoid confounding factors from reading from or writing to disk.

When usingStringReader/StringWriter eachchar counts as 2 bytes, whenmeasuring throughput e.g.MB/s. When usingStreamReader/StreamWritercontent is UTF-8 encoded and eachchar typically counts as 1 byte, as contentusually limited to 1 byte per char in UTF-8. Note that in .NET forTextReaderandTextWriter data is converted to/fromchar, but for reading suchconversion can often be just as fast asMemmove.

By default onlyStringReader/StringWriter results are shown, if a result isbased onStreamReader/StreamWriter it will be called out. Usually, resultsforStreamReader/StreamWriter are in line withStringReader/StringWriterbut with half the throughput due to 1 byte vs 2 bytes. For brevity they are notshown here.

For all benchmark results, Sep has been defined as theBaseline inBenchmarkDotNet. This meansRatio will be 1.00for Sep. For the othersRatio will then show how manytimes faster Sep isthan that. Or how manytimes more bytes are allocated inAlloc Ratio.

Disclaimer: Any comparison made is based on a number of preconditions andassumptions. Sep is a new library written from the ground up to use the latestand greatest features in .NET. CsvHelper has a long history and has to takeinto account backwards compatibility and still supporting older runtimes, somay not be able to easily utilize more recent features. Same goes for Sylvan.Additionally, Sep has a different feature set compared to the two. Performanceis a feature, but not the only feature. Keep that in mind when evaluatingresults.

Runtime and Platforms

The following runtime is used for benchmarking:

  • NET 9.0.X

NOTE:Garbage CollectionDATASmode is disabled since this severely impacts (e.g.1.7xslower) performance for somebenchmarks due to the bursty accumulated allocations. That is,GarbageCollectionAdaptationMode is set to0.

The following platforms are used for benchmarking:

  • AMD EPYC 7763 (Virtual) X64 Platform Information
    OS=Ubuntu 22.04.5 LTS (Jammy Jellyfish)AMD EPYC 7763, 1 CPU, 4 logical and 2 physical cores
  • AMD Ryzen 7 PRO 7840U (Laptop on battery) X64 Platform Information
    OS=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 cores
  • AMD 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 cores
  • AMD 9950X (Desktop) X64 Platform Information
    OS=Windows 10 (10.0.19044.3086/21H2/November2021Update)AMD Ryzen 9 9950X, 1 CPU, 32 logical and 16 physical cores
  • Apple M1 (Virtual) ARM64 Platform Information
    OS=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:

Details for each can be found in the following. However, for each of these 3different scopes are benchmarked to better assertain the low-level performanceof each library and approach and what parts of the parsing consume the mosttime:

  • Row - for this scope only the row is enumerated. That is, for Sep allthat is done is:
    foreach(varrowinreader){}
    this should capture parsing both row and columns but without accessing these.Note that some libraries (like Sylvan) will defer work for columns to whenthese are accessed.
  • Cols - for this scope all rows and all columns are enumerated. Ifpossible columns are accessed as spans, if not as strings, which then mightmean a string has to be allocated. That is, for Sep this is:
    foreach(varrowinreader){for(vari=0;i<row.ColCount;i++){varspan=row[i].Span;}}
  • XYZ - finally the full scope is performed which is specific to each ofthe scenarios.

Additionally, as Sep supports multi-threaded parsing viaParallelEnumeratebenchmarks results with_MT in the method name are multi-threaded. These showSep 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 asyncgiven there is no real IO involved and hence no actual asynchronous work orcontinuations (thus noTask allocations) since benchmarks run from memoryonly. This is fine as the main purpose of the benchmark is to gauge the overheadof the async code path.

NCsvPerf PackageAssets Reader Comparison Benchmarks

NCsvPerf fromThe fastest CSVparser in.NET is abenchmark which inJoel Verhagen ownwords was defined with:

My goal was to find the fastest low-level CSV parser. Essentially, all Iwanted was a library that gave me a string[] for each line where each field inthe line was an element in the array.

What is great about this work is it tests a whole of 35 different libraries andapproaches to this. Providing a great overview of those and their performance onthis specific scenario. Given Sylvan is the fastest of those it is used as theone to beat here, while CsvHelper is used to compare to the most commonly usedlibrary.

The source used for this benchmarkPackageAssetsBench.cs is aPackageAssets.csvwith 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.075fcf875-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.075fcf875-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.075fcf875-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.075fcf875-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

ForScope = Asset the columns are parsed into aPackageAsset class, whichconsists of 25 properties of which 22 arestrings. Each asset is accumulatedinto aList<PackageAsset>. Each column is accessed as astring regardless.

This means this benchmark is dominated by turning columns intostrings for thedecently fast parsers. Hence, the fastest libraries in this test employ stringpooling. That is, basically a custom dictionary fromReadOnlySpan<char> tostring, which avoids allocating a newstring for repeated values. And as canbe seen in the csv-file there are a lot of repeated values. Both Sylvan andCsvHelper do this in the benchmark. So does Sep and as with Sep this is anoptional configuration that has to be explicitly enable. For Sep this means thereader is created with something like:

usingvarreader=Sep.Reader(o=>owith{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. viaSepToString.PoolPerCol(...). This is based on the factthat often each column has its own set of values or strings that may be repeatedwithout any overlap to other columns. This also allows one to define per columnspecific handling ofToString behavior. Whether to pool or not. Or even to usea statically defined pool.

Sep supports unescaping via an option, seeSepReaderOptionsandUnescaping. Therefore, Sep has two methods being tested. ThedefaultSep without unescaping andSep_Unescape where unescaping is enabled.Note that only if there are quotes will there be any unescaping, but to supportunescape one has to track extra state during parsing which means there is aslight cost no matter what, most notably for theCols scope. Sep is still thefastest of all (by far in many cases).

PackageAssets Benchmark Results

The results below show Sep isthe fastest .NET CSV Parser (for thisbenchmark on these platforms and machines 😀). While for pure parsing allocatingonly a fraction of the memory due to extensive use of pooling and theArrayPool<T>.

This is in many aspects due to Sep having extremely optimized string pooling andoptimized hashing ofReadOnlySpan<char>, and thus not really due the thecsv-parsing itself, since that is not a big part of the time consumed. At leastnot for a decently fast csv-parser.

WithParallelEnumerate (MT) Sep is>2x faster than Sylvan and up to 9xfaster than CsvHelper.

At the lowest level of enumerating rows only, that is csv parsing only,Sephits a staggering 21 GB/s on 9950X. Single-threaded.

AMD.EPYC.7763 - PackageAssets Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500003.586 ms1.00298110.771.71.03 KB1.00
Sep_AsyncRow500003.646 ms1.02297978.272.91.03 KB1.00
Sep_UnescapeRow500003.564 ms0.99298161.371.31.15 KB1.12
Sylvan___Row500004.421 ms1.23296578.688.47.66 KB7.47
ReadLine_Row5000022.687 ms6.33291282.0453.788608.3 KB86,414.19
CsvHelperRow5000063.425 ms17.6929458.61268.520.12 KB19.62
Sep______Cols500004.788 ms1.00296075.395.81.03 KB1.00
Sep_UnescapeCols500005.757 ms1.20295052.2115.11.04 KB1.00
Sylvan___Cols500008.431 ms1.76293450.1168.68.03 KB7.78
ReadLine_Cols5000024.198 ms5.05291202.0484.088608.32 KB85,841.93
CsvHelperCols50000110.904 ms23.1729262.32218.1445.93 KB432.01
Sep______Asset5000040.065 ms1.0029726.0801.313802.85 KB1.00
Sep_MT___Asset5000028.673 ms0.72291014.4573.513858.63 KB1.00
Sylvan___Asset5000051.127 ms1.2829568.91022.513962.26 KB1.01
ReadLine_Asset50000122.110 ms3.0529238.22442.2102134.64 KB7.40
CsvHelperAsset50000127.405 ms3.1829228.32548.113974.95 KB1.01
Sep______Asset1000000860.730 ms1.00581676.0860.7266670.27 KB1.00
Sep_MT___Asset1000000500.834 ms0.585811161.8500.8274965.85 KB1.03
Sylvan___Asset10000001,039.885 ms1.21581559.61039.9266826.6 KB1.00
ReadLine_Asset10000002,666.458 ms3.10581218.22666.52038838.18 KB7.65
CsvHelperAsset10000002,589.531 ms3.01581224.72589.5266841.23 KB1.00
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - PackageAssets Benchmark Results (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500003.449 ms1.00298461.869.01.02 KB1.00
Sep_AsyncRow500003.650 ms1.06297995.573.01.02 KB1.00
Sep_UnescapeRow500003.565 ms1.03298184.971.31.02 KB1.00
Sylvan___Row500004.536 ms1.32296432.690.77.66 KB7.50
ReadLine_Row5000020.179 ms5.85291446.1403.688608.27 KB86,744.62
CsvHelperRow5000062.865 ms18.2329464.21257.320.07 KB19.64
Sep______Cols500005.030 ms1.00295801.5100.61.03 KB1.00
Sep_UnescapeCols500005.806 ms1.15295025.7116.11.03 KB1.00
Sylvan___Cols500007.502 ms1.49293889.7150.07.67 KB7.46
ReadLine_Cols5000021.399 ms4.25291363.7428.088608.28 KB86,167.97
CsvHelperCols50000109.575 ms21.7829266.32191.5448.88 KB436.51
Sep______Asset5000052.163 ms1.0029559.41043.313803.17 KB1.00
Sep_MT___Asset5000032.842 ms0.6329888.5656.813920.79 KB1.01
Sylvan___Asset5000058.391 ms1.1229499.81167.813962.64 KB1.01
ReadLine_Asset50000171.508 ms3.3029170.13430.2102134.46 KB7.40
CsvHelperAsset50000132.390 ms2.5529220.42647.813971.94 KB1.01
Sep______Asset1000000992.044 ms1.00583588.5992.0266670.83 KB1.00
Sep_MT___Asset1000000487.203 ms0.495831198.2487.2269058.01 KB1.01
Sylvan___Asset10000001,195.871 ms1.21583488.21195.9266826.02 KB1.00
ReadLine_Asset10000003,254.174 ms3.28583179.43254.22038843.73 KB7.65
CsvHelperAsset10000002,644.197 ms2.67583220.82644.2266841.13 KB1.00
AMD.Ryzen.9.5950X - PackageAssets Benchmark Results (Sep 0.9.0.0, Sylvan 1.3.9.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500002.230 ms1.002913088.444.61.09 KB1.00
Sep_AsyncRow500002.379 ms1.072912264.047.61.02 KB0.93
Sep_UnescapeRow500002.305 ms1.032912657.646.11.02 KB0.93
Sylvan___Row500002.993 ms1.33299750.259.97.65 KB7.52
ReadLine_Row5000012.106 ms5.36292410.5242.188608.25 KB87,077.59
CsvHelperRow5000043.313 ms19.1929673.7866.320.04 KB19.69
Sep______Cols500003.211 ms1.00299089.364.21.02 KB1.00
Sep_UnescapeCols500003.845 ms1.20297589.176.91.02 KB1.00
Sylvan___Cols500005.065 ms1.58295760.9101.37.66 KB7.52
ReadLine_Cols5000012.850 ms4.00292270.9257.088608.25 KB86,910.78
CsvHelperCols5000068.999 ms21.4929422.91380.0445.85 KB437.31
Sep______Asset5000033.615 ms1.0029868.1672.313802.47 KB1.00
Sep_MT___Asset5000020.231 ms0.60291442.4404.613992.1 KB1.01
Sylvan___Asset5000034.762 ms1.0329839.5695.213962.2 KB1.01
ReadLine_Asset5000097.204 ms2.8929300.21944.1102133.9 KB7.40
CsvHelperAsset5000083.550 ms2.4929349.31671.013970.66 KB1.01
Sep______Asset1000000629.552 ms1.00583927.3629.6266669.13 KB1.00
Sep_MT___Asset1000000261.089 ms0.415832236.0261.1267793.45 KB1.00
Sylvan___Asset1000000761.171 ms1.21583767.0761.2266825.09 KB1.00
ReadLine_Asset10000001,636.526 ms2.60583356.71636.52038835.59 KB7.65
CsvHelperAsset10000001,754.461 ms2.79583332.71754.5266833.16 KB1.00
AMD.Ryzen.9.9950X - PackageAssets Benchmark Results (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500001.365 ms1.002921384.927.31.01 KB1.00
Sep_AsyncRow500001.455 ms1.072920059.629.11.01 KB1.00
Sep_UnescapeRow500001.399 ms1.022920865.528.01.01 KB1.00
Sylvan___Row500001.934 ms1.422915085.438.77.72 KB7.63
ReadLine_Row500008.833 ms6.47293303.7176.788608.25 KB87,581.89
CsvHelperRow5000024.072 ms17.64291212.3481.420 KB19.76
Sep______Cols500001.895 ms1.002915399.837.91.02 KB1.00
Sep_UnescapeCols500002.321 ms1.222912573.846.41.02 KB1.00
Sylvan___Cols500003.141 ms1.66299290.562.87.66 KB7.54
ReadLine_Cols500009.248 ms4.88293155.2185.088608.25 KB87,245.04
CsvHelperCols5000044.453 ms23.4629656.4889.1445.7 KB438.84
Sep______Asset5000026.486 ms1.00291101.7529.713802.57 KB1.00
Sep_MT___Asset5000016.693 ms0.63291748.1333.913992.75 KB1.01
Sylvan___Asset5000029.711 ms1.1229982.2594.213963.15 KB1.01
ReadLine_Asset5000082.744 ms3.1329352.71654.9102134.11 KB7.40
CsvHelperAsset5000054.704 ms2.0729533.41094.113970.84 KB1.01
Sep______Asset1000000473.474 ms1.005831233.0473.5266668.7 KB1.00
Sep_MT___Asset1000000207.790 ms0.445832809.5207.8268542.84 KB1.01
Sylvan___Asset1000000573.016 ms1.215831018.8573.0266825.02 KB1.00
ReadLine_Asset10000001,303.550 ms2.75583447.81303.52038835.55 KB7.65
CsvHelperAsset10000001,149.642 ms2.43583507.81149.6266841.26 KB1.00
Apple.M1.(Virtual) - PackageAssets Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500003.049 ms1.00299539.761.01780 B1.00
Sep_AsyncRow500003.090 ms1.01299412.361.8968 B0.54
Sep_UnescapeRow500002.995 ms0.98299711.659.9967 B0.54
Sylvan___Row5000019.494 ms6.39291492.1389.96958 B3.91
ReadLine_Row5000018.381 ms6.03291582.4367.690734884 B50,974.65
CsvHelperRow5000043.358 ms14.2229670.8867.220692 B11.62
Sep______Cols500004.070 ms1.00297146.081.4974 B1.00
Sep_UnescapeCols500004.508 ms1.11296452.290.2975 B1.00
Sylvan___Cols5000021.949 ms5.39291325.1439.06725 B6.90
ReadLine_Cols5000019.604 ms4.82291483.7392.190734891 B93,156.97
CsvHelperCols5000071.862 ms17.6629404.71437.2457440 B469.65
Sep______Asset5000031.638 ms1.0029919.3632.814134042 B1.00
Sep_MT___Asset5000020.132 ms0.64291444.7402.614312402 B1.01
Sylvan___Asset5000052.805 ms1.6729550.81056.114296822 B1.01
ReadLine_Asset5000093.011 ms2.9529312.71860.2104585942 B7.40
CsvHelperAsset5000077.498 ms2.4629375.31550.014306376 B1.01
Sep______Asset1000000563.469 ms1.005811032.7563.5273070312 B1.00
Sep_MT___Asset1000000454.372 ms0.815811280.6454.4283171600 B1.04
Sylvan___Asset10000001,066.748 ms1.89581545.51066.7273235400 B1.00
ReadLine_Asset10000001,778.023 ms3.16581327.31778.02087769280 B7.65
CsvHelperAsset10000001,593.827 ms2.83581365.11593.8273247744 B1.00
Cobalt.100 - PackageAssets Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500004.770 ms1.00296117.795.4972 B1.00
Sep_AsyncRow500005.006 ms1.05295829.1100.1972 B1.00
Sep_UnescapeRow500004.789 ms1.00296092.995.8970 B1.00
Sylvan___Row5000019.965 ms4.19291461.6399.36656 B6.85
ReadLine_Row5000024.603 ms5.16291186.1492.190734858 B93,348.62
CsvHelperRow5000052.364 ms10.9829557.31047.320435 B21.02
Sep______Cols500006.103 ms1.00294781.5122.1955 B1.00
Sep_UnescapeCols500006.852 ms1.12294258.9137.0953 B1.00
Sylvan___Cols5000024.055 ms3.94291213.1481.16686 B7.00
ReadLine_Cols5000025.907 ms4.25291126.4518.190734829 B95,010.29
CsvHelperCols5000089.741 ms14.7029325.21794.8456396 B477.90
Sep______Asset5000045.045 ms1.0029647.8900.914134146 B1.00
Sep_MT___Asset5000028.571 ms0.64291021.4571.414198453 B1.00
Sylvan___Asset5000066.218 ms1.4729440.71324.414296192 B1.01
ReadLine_Asset50000131.307 ms2.9229222.22626.1104585118 B7.40
CsvHelperAsset50000110.400 ms2.4629264.32208.014305660 B1.01
Sep______Asset1000000907.416 ms1.00583643.3907.4273069856 B1.00
Sep_MT___Asset1000000425.985 ms0.475831370.4426.0281372840 B1.03
Sylvan___Asset10000001,374.743 ms1.52583424.61374.7273228480 B1.00
ReadLine_Asset10000002,282.225 ms2.52583255.82282.22087766512 B7.65
CsvHelperAsset10000002,247.721 ms2.48583259.72247.7273249784 B1.00
PackageAssets Benchmark Results (SERVER GC)

The package assets benchmark (ScopeAsset) has a very high base load in theform of the accumulated instances ofPackageAsset and since Sep is so fast theGC becomes a significant bottleneck for the benchmark, especially formulti-threaded parsing. Switching toSERVERGCcan, therefore, provide significant speedup as can be seen below.

WithParallelEnumerate and server GC Sep is>4x faster than Sylvan and up to18x faster than CsvHelper. Breaking 8 GB/s parsing speed on package assets on9950X.

AMD.EPYC.7763 - PackageAssets Benchmark Results (SERVER GC) (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000032.70 ms1.0029889.5653.913.48 MB1.00
Sep_MT___Asset5000016.64 ms0.51291748.0332.813.53 MB1.00
Sylvan___Asset5000044.32 ms1.3629656.2886.513.63 MB1.01
ReadLine_Asset5000058.67 ms1.7929495.71173.499.74 MB7.40
CsvHelperAsset50000120.06 ms3.6729242.32401.213.64 MB1.01
Sep______Asset1000000667.19 ms1.00581872.1667.2260.41 MB1.00
Sep_MT___Asset1000000343.62 ms0.525811693.4343.6269.07 MB1.03
Sylvan___Asset1000000884.52 ms1.33581657.8884.5260.57 MB1.00
ReadLine_Asset10000001,238.66 ms1.86581469.81238.71991.05 MB7.65
CsvHelperAsset10000002,422.78 ms3.63581240.22422.8260.58 MB1.00
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - PackageAssets Benchmark Results (SERVER GC) (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000032.427 ms1.0029899.9648.513.48 MB1.00
Sep_MT___Asset500009.377 ms0.29293112.1187.513.57 MB1.01
Sylvan___Asset5000043.724 ms1.3529667.4874.513.63 MB1.01
ReadLine_Asset5000048.915 ms1.5129596.6978.399.74 MB7.40
CsvHelperAsset50000118.160 ms3.6429247.02363.213.65 MB1.01
Sep______Asset1000000680.684 ms1.00583857.6680.7260.41 MB1.00
Sep_MT___Asset1000000264.669 ms0.395832205.7264.7262.35 MB1.01
Sylvan___Asset1000000910.222 ms1.34583641.4910.2260.57 MB1.00
ReadLine_Asset10000001,043.395 ms1.54583559.51043.41991.05 MB7.65
CsvHelperAsset10000002,433.801 ms3.58583239.92433.8260.58 MB1.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)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000020.951 ms1.00291392.9419.013.48 MB1.00
Sep_MT___Asset500006.614 ms0.32294411.8132.313.64 MB1.01
Sylvan___Asset5000027.761 ms1.33291051.2555.213.63 MB1.01
ReadLine_Asset5000033.516 ms1.6029870.7670.399.74 MB7.40
CsvHelperAsset5000077.007 ms3.6829378.91540.113.64 MB1.01
Sep______Asset1000000432.887 ms1.005831348.6432.9260.41 MB1.00
Sep_MT___Asset1000000119.430 ms0.285834888.1119.4261.39 MB1.00
Sylvan___Asset1000000559.550 ms1.295831043.3559.6260.57 MB1.00
ReadLine_Asset1000000573.637 ms1.335831017.7573.61991.05 MB7.65
CsvHelperAsset10000001,537.602 ms3.55583379.71537.6260.58 MB1.00
AMD.Ryzen.9.9950X - PackageAssets Benchmark Results (SERVER GC) (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000014.536 ms1.00292007.5290.713.48 MB1.00
Sep_MT___Asset500003.606 ms0.25298091.872.113.65 MB1.01
Sylvan___Asset5000020.457 ms1.41291426.5409.113.63 MB1.01
ReadLine_Asset5000020.307 ms1.40291437.0406.199.74 MB7.40
CsvHelperAsset5000054.022 ms3.7229540.21080.413.64 MB1.01
Sep______Asset1000000291.979 ms1.005831999.4292.0260.41 MB1.00
Sep_MT___Asset100000072.213 ms0.255838084.172.2261.63 MB1.00
Sylvan___Asset1000000413.265 ms1.425831412.6413.3260.57 MB1.00
ReadLine_Asset1000000377.033 ms1.295831548.4377.01991.04 MB7.65
CsvHelperAsset10000001,005.323 ms3.44583580.71005.3260.58 MB1.00
Apple.M1.(Virtual) - PackageAssets Benchmark Results (SERVER GC) (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000025.60 ms1.00291136.3512.013.48 MB1.00
Sep_MT___Asset5000018.61 ms0.73291562.6372.313.57 MB1.01
Sylvan___Asset5000066.50 ms2.6129437.41330.013.63 MB1.01
ReadLine_Asset5000053.19 ms2.0829546.91063.899.74 MB7.40
CsvHelperAsset5000091.97 ms3.6029316.21839.513.64 MB1.01
Sep______Asset1000000594.24 ms1.02581979.2594.2260.41 MB1.00
Sep_MT___Asset1000000300.93 ms0.515811933.6300.9271.08 MB1.04
Sylvan___Asset1000000974.34 ms1.66581597.2974.3260.57 MB1.00
ReadLine_Asset10000001,102.38 ms1.88581527.81102.41991.05 MB7.65
CsvHelperAsset10000001,547.16 ms2.64581376.11547.2260.58 MB1.00
Cobalt.100 - PackageAssets Benchmark Results (SERVER GC) (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000034.83 ms1.0029837.8696.613.48 MB1.00
Sep_MT___Asset5000011.85 ms0.34292462.6237.013.53 MB1.00
Sylvan___Asset5000058.84 ms1.6929496.01176.713.63 MB1.01
ReadLine_Asset5000046.17 ms1.3329632.0923.599.74 MB7.40
CsvHelperAsset50000103.20 ms2.9729282.82064.113.64 MB1.01
Sep______Asset1000000699.14 ms1.00583835.0699.1260.41 MB1.00
Sep_MT___Asset1000000219.50 ms0.315832659.6219.5268.09 MB1.03
Sylvan___Asset10000001,181.98 ms1.69583493.91182.0260.57 MB1.00
ReadLine_Asset10000001,025.30 ms1.47583569.41025.31991.04 MB7.65
CsvHelperAsset10000002,104.55 ms3.01583277.42104.5260.58 MB1.00
PackageAssets with Quotes Benchmark Results

NCsvPerf does not examine performance in the face of quotes in the csv. Thisis relevant since some libraries like Sylvan will revert to a slower (not SIMDvectorized) parsing code path if it encounters quotes. Sep was designed toalways use SIMD vectorization no matter what.

Since there are two extrachars to handle per column, it does have asignificant impact on performance, no matter what though. This is expected whenlooking 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 almosttriple the total to 76.

AMD.EPYC.7763 - PackageAssets with Quotes Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row5000010.57 ms1.00333147.7211.51.06 KB1.00
Sep_AsyncRow5000010.56 ms1.00333152.0211.21.06 KB1.00
Sep_UnescapeRow5000010.44 ms0.99333188.5208.81.06 KB1.00
Sylvan___Row5000023.18 ms2.19331435.6463.77.73 KB7.29
ReadLine_Row5000026.19 ms2.48331270.8523.8108778.8 KB102,568.59
CsvHelperRow5000077.54 ms7.3333429.21550.820.28 KB19.12
Sep______Cols5000011.85 ms1.00332808.1237.01.07 KB1.00
Sep_UnescapeCols5000013.74 ms1.16332422.9274.71.08 KB1.01
Sylvan___Cols5000027.56 ms2.33331207.5551.27.75 KB7.26
ReadLine_Cols5000028.25 ms2.38331178.1565.0108778.81 KB101,818.56
CsvHelperCols50000106.81 ms9.0133311.62136.1445.94 KB417.41
Sep______Asset5000047.05 ms1.0033707.4940.913802.84 KB1.00
Sep_MT___Asset5000030.50 ms0.65331091.2610.013860.99 KB1.00
Sylvan___Asset5000069.36 ms1.4733479.91387.213962.38 KB1.01
ReadLine_Asset50000148.40 ms3.1633224.32968.0122305.51 KB8.86
CsvHelperAsset50000126.52 ms2.6933263.12530.313971.3 KB1.01
Sep______Asset1000000990.05 ms1.00665672.5990.0266670.43 KB1.00
Sep_MT___Asset1000000573.36 ms0.586651161.2573.4271926.59 KB1.02
Sylvan___Asset10000001,426.57 ms1.44665466.71426.6266828.59 KB1.00
ReadLine_Asset10000003,090.02 ms3.12665215.53090.02442321.23 KB9.16
CsvHelperAsset10000002,609.20 ms2.64665255.22609.2266834.35 KB1.00
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - PackageAssets with Quotes Benchmark Results (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row5000011.24 ms1.00332970.2224.71.06 KB1.00
Sep_AsyncRow5000013.00 ms1.16332567.1260.01.06 KB1.00
Sep_UnescapeRow5000010.34 ms0.92333229.0206.71.04 KB0.99
Sylvan___Row5000026.59 ms2.37331255.1531.97.72 KB7.31
ReadLine_Row5000024.04 ms2.14331388.3480.8108778.78 KB103,042.99
CsvHelperRow5000071.28 ms6.3433468.31425.620.21 KB19.14
Sep______Cols5000012.42 ms1.00332686.7248.51.05 KB1.00
Sep_UnescapeCols5000013.22 ms1.06332524.0264.51.07 KB1.01
Sylvan___Cols5000030.14 ms2.43331107.3602.87.73 KB7.35
ReadLine_Cols5000026.19 ms2.11331274.3523.8108778.79 KB103,425.70
CsvHelperCols50000101.12 ms8.1433330.12022.4445.86 KB423.92
Sep______Asset5000059.60 ms1.0033560.01192.013803.13 KB1.00
Sep_MT___Asset5000042.87 ms0.7233778.6857.413947 KB1.01
Sylvan___Asset5000079.36 ms1.3333420.61587.213962.4 KB1.01
ReadLine_Asset50000197.46 ms3.3233169.03949.2122304.88 KB8.86
CsvHelperAsset50000126.66 ms2.1333263.52533.213971.72 KB1.01
Sep______Asset10000001,114.98 ms1.00667598.91115.0266678.59 KB1.00
Sep_MT___Asset1000000666.45 ms0.606671001.9666.4268990.3 KB1.01
Sylvan___Asset10000001,613.38 ms1.45667413.91613.4266825.61 KB1.00
ReadLine_Asset10000004,194.02 ms3.76667159.24194.02442318.66 KB9.16
CsvHelperAsset10000002,549.93 ms2.29667261.92549.9266834.65 KB1.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)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500007.046 ms1.00334737.2140.91.04 KB1.00
Sep_AsyncRow500008.137 ms1.15334101.8162.71.04 KB1.00
Sep_UnescapeRow500007.473 ms1.06334466.7149.51.04 KB1.00
Sylvan___Row5000017.571 ms2.38331899.5351.47.69 KB7.41
ReadLine_Row5000014.336 ms1.94332328.2286.7108778.75 KB104,689.33
CsvHelperRow5000052.672 ms7.1233633.71053.420.05 KB19.29
Sep______Cols500008.126 ms1.00334107.5162.51.04 KB1.00
Sep_UnescapeCols500009.748 ms1.20333424.0195.01.05 KB1.01
Sylvan___Cols5000020.503 ms2.52331628.0410.17.7 KB7.39
ReadLine_Cols5000016.513 ms2.03332021.3330.3108778.76 KB104,394.99
CsvHelperCols5000074.224 ms9.1333449.71484.5445.85 KB427.88
Sep______Asset5000039.523 ms1.0033844.5790.513802.63 KB1.00
Sep_MT___Asset5000023.386 ms0.59331427.2467.713981.76 KB1.01
Sylvan___Asset5000050.803 ms1.2933657.01016.113962.08 KB1.01
ReadLine_Asset50000114.306 ms2.8933292.02286.1122304.45 KB8.86
CsvHelperAsset5000088.786 ms2.2533375.91775.713970.43 KB1.01
Sep______Asset1000000752.681 ms1.00667887.1752.7266669 KB1.00
Sep_MT___Asset1000000377.733 ms0.506671767.7377.7267992.5 KB1.00
Sylvan___Asset10000001,091.345 ms1.45667611.81091.3266825.09 KB1.00
ReadLine_Asset10000002,615.390 ms3.47667255.32615.42442319.06 KB9.16
CsvHelperAsset10000001,756.409 ms2.33667380.21756.4266839.53 KB1.00
AMD.Ryzen.9.9950X - PackageAssets with Quotes Benchmark Results (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500004.233 ms1.00337884.484.71.03 KB1.00
Sep_AsyncRow500004.577 ms1.08337291.991.51.02 KB1.00
Sep_UnescapeRow500004.435 ms1.05337526.188.71.03 KB1.00
Sylvan___Row5000011.399 ms2.69332928.1228.07.67 KB7.48
ReadLine_Row500009.827 ms2.32333396.6196.5108778.75 KB106,085.18
CsvHelperRow5000027.453 ms6.48331215.8549.120 KB19.51
Sep______Cols500004.995 ms1.00336681.999.91.03 KB1.00
Sep_UnescapeCols500005.515 ms1.10336052.4110.31.03 KB1.00
Sylvan___Cols5000012.760 ms2.55332615.9255.27.67 KB7.46
ReadLine_Cols5000010.853 ms2.17333075.3217.1108778.74 KB105,782.94
CsvHelperCols5000041.586 ms8.3333802.6831.7445.7 KB433.42
Sep______Asset5000028.426 ms1.00331174.2568.513802.71 KB1.00
Sep_MT___Asset5000020.467 ms0.72331630.8409.313993.59 KB1.01
Sylvan___Asset5000038.589 ms1.3633864.9771.813962.21 KB1.01
ReadLine_Asset50000100.029 ms3.5233333.72000.6122305.28 KB8.86
CsvHelperAsset5000052.035 ms1.8333641.41040.713971.41 KB1.01
Sep______Asset1000000521.217 ms1.006671281.1521.2266668.73 KB1.00
Sep_MT___Asset1000000270.971 ms0.526672464.1271.0268699.4 KB1.01
Sylvan___Asset1000000776.453 ms1.49667859.9776.5266824.44 KB1.00
ReadLine_Asset10000001,850.722 ms3.55667360.81850.72442318.88 KB9.16
CsvHelperAsset10000001,117.789 ms2.14667597.31117.8266833.25 KB1.00
Apple.M1.(Virtual) - PackageAssets with Quotes Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500009.290 ms1.00333582.6185.8997 B1.00
Sep_AsyncRow500009.339 ms1.01333563.8186.81012 B1.02
Sep_UnescapeRow500009.897 ms1.07333363.0197.91201 B1.20
Sylvan___Row5000023.249 ms2.51331431.6465.06958 B6.98
ReadLine_Row5000022.531 ms2.44331477.1450.6111389493 B111,724.67
CsvHelperRow5000046.522 ms5.0333715.4930.420764 B20.83
Sep______Cols500008.664 ms1.00333841.6173.3999 B1.00
Sep_UnescapeCols500009.746 ms1.12333415.0194.91170 B1.17
Sylvan___Cols5000022.858 ms2.64331456.0457.26725 B6.73
ReadLine_Cols5000022.199 ms2.56331499.2444.0111389493 B111,500.99
CsvHelperCols5000071.417 ms8.2433466.01428.3456636 B457.09
Sep______Asset5000036.004 ms1.0033924.4720.114134024 B1.00
Sep_MT___Asset5000022.068 ms0.61331508.1441.414233705 B1.01
Sylvan___Asset5000051.940 ms1.4533640.81038.814296832 B1.01
ReadLine_Asset50000136.529 ms3.8033243.82730.6125241136 B8.86
CsvHelperAsset5000088.008 ms2.4533378.21760.214307374 B1.01
Sep______Asset1000000733.901 ms1.00665907.2733.9273075208 B1.00
Sep_MT___Asset1000000469.473 ms0.646651418.2469.5281562856 B1.03
Sylvan___Asset10000001,154.378 ms1.57665576.81154.4273228848 B1.00
ReadLine_Asset10000002,877.672 ms3.92665231.42877.72500938080 B9.16
CsvHelperAsset10000001,962.262 ms2.68665339.31962.3273242672 B1.00
Cobalt.100 - PackageAssets with Quotes Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row500009.740 ms1.00333426.9194.8995 B1.00
Sep_AsyncRow5000010.119 ms1.04333298.6202.4954 B0.96
Sep_UnescapeRow5000010.247 ms1.05333257.2204.9976 B0.98
Sylvan___Row5000023.525 ms2.42331418.8470.56683 B6.72
ReadLine_Row5000030.016 ms3.08331112.0600.3111389426 B111,949.17
CsvHelperRow5000063.934 ms6.5633522.11278.720512 B20.62
Sep______Cols5000010.978 ms1.00333040.4219.6954 B1.00
Sep_UnescapeCols5000012.836 ms1.17332600.3256.7954 B1.00
Sylvan___Cols5000029.183 ms2.66331143.7583.76874 B7.21
ReadLine_Cols5000030.967 ms2.82331077.8619.3111389422 B116,760.40
CsvHelperCols5000098.644 ms8.9933338.41972.9456312 B478.31
Sep______Asset5000049.729 ms1.0033671.2994.614133888 B1.00
Sep_MT___Asset5000028.551 ms0.58331169.0571.014193244 B1.00
Sylvan___Asset5000070.563 ms1.4233473.01411.314296674 B1.01
ReadLine_Asset50000165.448 ms3.3433201.73309.0125240196 B8.86
CsvHelperAsset50000117.307 ms2.3733284.52346.114306942 B1.01
Sep______Asset10000001,027.672 ms1.00667649.71027.7273072096 B1.00
Sep_MT___Asset1000000469.985 ms0.466671420.7470.0279329376 B1.02
Sylvan___Asset10000001,439.859 ms1.40667463.71439.9273227688 B1.00
ReadLine_Asset10000002,952.028 ms2.87667226.22952.02500934744 B9.16
CsvHelperAsset10000002,354.660 ms2.29667283.62354.7273241544 B1.00
PackageAssets with Quotes Benchmark Results (SERVER GC)

Here again are benchmark results with server garbage collection, which providessignificant speedup over workstation garbage collection.

AMD.EPYC.7763 - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000039.92 ms1.0033833.6798.513.48 MB1.00
Sep_MT___Asset5000022.60 ms0.57331472.9451.913.53 MB1.00
Sylvan___Asset5000063.33 ms1.5933525.51266.613.63 MB1.01
ReadLine_Asset5000067.28 ms1.6933494.71345.5119.44 MB8.86
CsvHelperAsset50000118.11 ms2.9633281.82362.213.64 MB1.01
Sep______Asset1000000819.11 ms1.00665812.8819.1260.41 MB1.00
Sep_MT___Asset1000000428.25 ms0.526651554.7428.2263.02 MB1.01
Sylvan___Asset10000001,281.90 ms1.57665519.41281.9260.57 MB1.00
ReadLine_Asset10000001,373.18 ms1.68665484.91373.22385.08 MB9.16
CsvHelperAsset10000002,404.07 ms2.94665276.92404.1260.58 MB1.00
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000039.91 ms1.0033836.4798.213.48 MB1.00
Sep_MT___Asset5000018.80 ms0.47331775.4376.013.58 MB1.01
Sylvan___Asset5000067.17 ms1.6833496.91343.513.63 MB1.01
ReadLine_Asset5000067.19 ms1.6833496.81343.7119.44 MB8.86
CsvHelperAsset50000113.44 ms2.8433294.22268.913.64 MB1.01
Sep______Asset1000000871.42 ms1.00667766.2871.4260.41 MB1.00
Sep_MT___Asset1000000343.11 ms0.396671946.0343.1261.3 MB1.00
Sylvan___Asset10000001,358.88 ms1.56667491.41358.9260.57 MB1.00
ReadLine_Asset10000001,157.77 ms1.33667576.71157.82385.07 MB9.16
CsvHelperAsset10000002,299.56 ms2.64667290.42299.6260.58 MB1.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)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000026.42 ms1.00331263.1528.513.48 MB1.00
Sep_MT___Asset5000011.53 ms0.44332894.1230.713.64 MB1.01
Sylvan___Asset5000043.05 ms1.6333775.3861.113.63 MB1.01
ReadLine_Asset5000037.30 ms1.4133894.8746.0119.44 MB8.86
CsvHelperAsset5000078.91 ms2.9933423.01578.113.64 MB1.01
Sep______Asset1000000538.48 ms1.006671240.0538.5260.43 MB1.00
Sep_MT___Asset1000000213.29 ms0.406673130.5213.3261.37 MB1.00
Sylvan___Asset1000000879.04 ms1.63667759.6879.0260.57 MB1.00
ReadLine_Asset1000000642.57 ms1.196671039.1642.62385.07 MB9.16
CsvHelperAsset10000001,598.79 ms2.97667417.61598.8260.58 MB1.00
AMD.Ryzen.9.9950X - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000017.916 ms1.00331862.9358.313.48 MB1.00
Sep_MT___Asset500006.753 ms0.38334942.7135.113.64 MB1.01
Sylvan___Asset5000030.678 ms1.71331088.0613.613.63 MB1.01
ReadLine_Asset5000022.998 ms1.28331451.3460.0119.44 MB8.86
CsvHelperAsset5000047.582 ms2.6633701.5951.613.64 MB1.01
Sep______Asset1000000360.848 ms1.006671850.4360.8260.41 MB1.00
Sep_MT___Asset1000000143.101 ms0.406674666.0143.1261.69 MB1.00
Sylvan___Asset1000000624.089 ms1.736671069.9624.1260.57 MB1.00
ReadLine_Asset1000000419.179 ms1.166671592.9419.22385.07 MB9.16
CsvHelperAsset1000000953.606 ms2.64667700.2953.6260.58 MB1.00
Apple.M1.(Virtual) - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000031.85 ms1.00331045.1636.913.48 MB1.00
Sep_MT___Asset5000012.27 ms0.39332712.4245.413.6 MB1.01
Sylvan___Asset5000049.61 ms1.5633670.9992.213.63 MB1.01
ReadLine_Asset5000040.30 ms1.2733825.9805.9119.44 MB8.86
CsvHelperAsset5000091.88 ms2.8933362.21837.613.64 MB1.01
Sep______Asset1000000803.91 ms1.01665828.2803.9260.41 MB1.00
Sep_MT___Asset1000000267.90 ms0.346652485.3267.9265.71 MB1.02
Sylvan___Asset1000000984.68 ms1.24665676.2984.7260.57 MB1.00
ReadLine_Asset10000001,483.17 ms1.87665448.91483.22385.08 MB9.16
CsvHelperAsset10000001,595.39 ms2.01665417.31595.4260.58 MB1.00
Cobalt.100 - PackageAssets with Quotes Benchmark Results (SERVER GC) (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Asset5000040.53 ms1.0033823.4810.713.48 MB1.00
Sep_MT___Asset5000014.65 ms0.36332278.6293.013.53 MB1.00
Sylvan___Asset5000062.73 ms1.5533532.11254.613.63 MB1.01
ReadLine_Asset5000054.84 ms1.3533608.71096.7119.44 MB8.86
CsvHelperAsset50000110.48 ms2.7333302.12209.613.64 MB1.01
Sep______Asset1000000837.31 ms1.00667797.4837.3260.41 MB1.00
Sep_MT___Asset1000000276.68 ms0.336672413.3276.7261.4 MB1.00
Sylvan___Asset10000001,245.45 ms1.49667536.11245.4260.57 MB1.00
ReadLine_Asset10000001,247.39 ms1.49667535.31247.42385.08 MB9.16
CsvHelperAsset10000002,256.19 ms2.69667295.92256.2260.58 MB1.00
PackageAssets with Spaces and Quotes Benchmark Results

Similar to the benchmark related to quotes here spaces and quotes" areadded to relevant columns to benchmark impact of trimming and unescape on lowlevel column access. That is, basically" is prepended and appended to eachcolumn. This will test the assumed most common case and fast path part oftrimming and unescaping in Sep. Sep is about 10x faster than CsvHelper for this.Sylvan does not appear to support automatic trimming and is, therefore, notincluded.

AMD.EPYC.7763 - PackageAssets with Spaces and Quotes Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep_Cols5000012.70 ms1.00413282.3253.91.07 KB1.00
Sep_TrimCols5000018.17 ms1.43412293.5363.41.1 KB1.02
Sep_TrimUnescapeCols5000019.20 ms1.51412170.4384.01.11 KB1.03
Sep_TrimUnescapeTrimCols5000020.91 ms1.65411992.9418.21.87 KB1.74
CsvHelper_TrimUnescapeCols50000144.27 ms11.3641288.92885.3457.89 KB426.65
CsvHelper_TrimUnescapeTrimCols50000145.88 ms11.4941285.72917.6446.2 KB415.75
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - PackageAssets with Spaces and Quotes Benchmark Results (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep_Cols5000013.81 ms1.00413023.7276.31.07 KB1.00
Sep_TrimCols5000019.30 ms1.40412163.7386.11.09 KB1.02
Sep_TrimUnescapeCols5000019.84 ms1.44412105.6396.71.09 KB1.02
Sep_TrimUnescapeTrimCols5000022.54 ms1.63411853.3450.81.1 KB1.03
CsvHelper_TrimUnescapeCols50000129.64 ms9.3941322.22592.7454.54 KB426.63
CsvHelper_TrimUnescapeTrimCols50000127.70 ms9.2541327.12554.0445.86 KB418.48
AMD.Ryzen.9.5950X - PackageAssets with Spaces and Quotes Benchmark Results (Sep 0.9.0.0, Sylvan 1.3.9.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep_Cols500009.467 ms1.00414412.2189.31.05 KB1.00
Sep_TrimCols5000012.972 ms1.37413219.9259.41.06 KB1.01
Sep_TrimUnescapeCols5000013.630 ms1.44413064.5272.61.06 KB1.02
Sep_TrimUnescapeTrimCols5000015.502 ms1.64412694.4310.01.07 KB1.03
CsvHelper_TrimUnescapeCols5000098.444 ms10.4041424.31968.9451.52 KB431.70
CsvHelper_TrimUnescapeTrimCols5000097.110 ms10.2641430.11942.2445.86 KB426.29
AMD.Ryzen.9.9950X - PackageAssets with Spaces and Quotes Benchmark Results (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep_Cols500005.235 ms1.00417978.5104.71.02 KB1.00
Sep_TrimCols500007.953 ms1.52415252.0159.11.04 KB1.02
Sep_TrimUnescapeCols500008.544 ms1.63414888.7170.91.04 KB1.02
Sep_TrimUnescapeTrimCols500009.254 ms1.77414513.7185.11.05 KB1.02
CsvHelper_TrimUnescapeCols5000062.406 ms11.9241669.31248.1451.34 KB440.58
CsvHelper_TrimUnescapeTrimCols5000061.412 ms11.7341680.21228.2445.72 KB435.10
Apple.M1.(Virtual) - PackageAssets with Spaces and Quotes Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep_Cols500009.479 ms1.00414396.3189.61.17 KB1.00
Sep_TrimCols5000013.018 ms1.37413201.2260.41.36 KB1.16
Sep_TrimUnescapeCols5000040.026 ms4.22411041.2800.51.36 KB1.16
Sep_TrimUnescapeTrimCols5000039.957 ms4.22411043.0799.11.36 KB1.16
CsvHelper_TrimUnescapeCols5000090.642 ms9.5641459.81812.8451.53 KB384.98
CsvHelper_TrimUnescapeTrimCols5000087.946 ms9.2841473.91758.9445.93 KB380.21
Cobalt.100 - PackageAssets with Spaces and Quotes Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep_Cols5000012.76 ms1.00413273.0255.2954 B1.00
Sep_TrimCols5000017.18 ms1.35412430.8343.7955 B1.00
Sep_TrimUnescapeCols5000018.28 ms1.43412284.9365.61698 B1.78
Sep_TrimUnescapeTrimCols5000020.09 ms1.57412079.4401.8977 B1.02
CsvHelper_TrimUnescapeCols50000118.88 ms9.3241351.42377.6462286 B484.58
CsvHelper_TrimUnescapeTrimCols50000116.82 ms9.1541357.52336.5459642 B481.81

Floats Reader Comparison Benchmarks

TheFloatsReaderBench.csbenchmark demonstrates what Sep is built for. Namely parsing 32-bit floatingpoints or features as in machine learning. Here a simple CSV-file is randomlygenerated withN ground truth values,N predicted result values and nothingelse (note this was changed from version 0.3.0, prior to that there were someextra leading columns).N = 20here. For example:

GT_Feature0;GT_Feature1;GT_Feature2;GT_Feature3;GT_Feature4;GT_Feature5;GT_Feature6;GT_Feature7;GT_Feature8;GT_Feature9;GT_Feature10;GT_Feature11;GT_Feature12;GT_Feature13;GT_Feature14;GT_Feature15;GT_Feature16;GT_Feature17;GT_Feature18;GT_Feature19;RE_Feature0;RE_Feature1;RE_Feature2;RE_Feature3;RE_Feature4;RE_Feature5;RE_Feature6;RE_Feature7;RE_Feature8;RE_Feature9;RE_Feature10;RE_Feature11;RE_Feature12;RE_Feature13;RE_Feature14;RE_Feature15;RE_Feature16;RE_Feature17;RE_Feature18;RE_Feature190.52276427;0.16843422;0.26259267;0.7244084;0.51292276;0.17365117;0.76125056;0.23458846;0.2573214;0.50560355;0.3202332;0.3809696;0.26024464;0.5174511;0.035318818;0.8141374;0.57719684;0.3974705;0.15219308;0.09011261;0.70515215;0.81618196;0.5399706;0.044147138;0.7111546;0.14776127;0.90621275;0.6925897;0.5164137;0.18637845;0.041509967;0.30819967;0.5831603;0.8210651;0.003954861;0.535722;0.8051845;0.7483589;0.3845737;0.149119080.6264564;0.11517637;0.24996082;0.77242833;0.2896067;0.6481459;0.14364648;0.044498358;0.6045593;0.51591337;0.050794687;0.42036617;0.7065823;0.6284636;0.21844554;0.013253775;0.36516154;0.2674384;0.06866083;0.71817476;0.07094294;0.46409357;0.012033525;0.7978093;0.43917948;0.5134962;0.4995968;0.008952909;0.82883793;0.012896823;0.0030740085;0.063773096;0.6541431;0.034539033;0.9135142;0.92897075;0.46119377;0.37533295;0.61660606;0.0444438160.7922863;0.5323656;0.400699;0.29737252;0.9072584;0.58673894;0.73510516;0.019412167;0.88168067;0.9576787;0.33283427;0.7107;0.1623628;0.10314285;0.4521515;0.33324885;0.7761104;0.14854911;0.13469358;0.21566042;0.59166247;0.5128394;0.98702157;0.766223;0.67204326;0.7149494;0.2894748;0.55206;0.9898286;0.65083236;0.02421702;0.34540752;0.92906284;0.027142895;0.21974725;0.26544374;0.03848049;0.2161237;0.59233844;0.422213970.10609442;0.32130885;0.32383907;0.7511514;0.8258279;0.00904226;0.0420841;0.84049565;0.8958947;0.23807365;0.92621964;0.8452882;0.2794469;0.545344;0.63447595;0.62532926;0.19230893;0.29726416;0.18304513;0.029583583;0.23084833;0.93346167;0.98742676;0.78163713;0.13521992;0.8833956;0.18670778;0.29476836;0.5599867;0.5562107;0.7124796;0.121927656;0.5981778;0.39144602;0.88092715;0.4449142;0.34820423;0.96379805;0.46364686;0.54301775

ForScope=Floats the benchmark will parse the features as two spans offloats; one for ground truth values and one for predicted result values. Thencalculates the mean squared error (MSE) of those as an example. For Sep thiscode is succinct and still incredibly efficient:

usingvarreader=Sep.Reader().From(Reader.CreateReader());vargroundTruthColNames=reader.Header.NamesStartingWith("GT_");varresultColNames=groundTruthColNames.Select(n=>n.Replace("GT_","RE_",StringComparison.Ordinal)).ToArray();varsum=0.0;varcount=0;foreach(varrowinreader){vargts=row[groundTruthColNames].Parse<float>();varres=row[resultColNames].Parse<float>();sum+=MeanSquaredError(gts,res);++count;}returnsum/count;

Note how one can access and parse multiple columns easily while there are norepeated allocations for the parsed floating points. Sep internally handles apool of arrays for handling multiple columns and returns spans for them.

The benchmark is based on an assumption of accessing columns by name perrow. Ideally, one would look up the indices of the columns by name beforeenumerating rows, but this is a repeated nuisance to have to handle and Sep wasbuilt to avoid this. Hence, the comparison is based on looking up by name foreach, even if this ends up adding a bit more code in the benchmark for otherapproaches.

As can be seen below, the actual low level parsing of the separated values is atiny part of the total runtime for Sep for which the runtime is dominated byparsing the floating points. Since Sep usescsFastFloat for an integrated fastfloating point parser, it is>2x faster than Sylvan for example. If usingSylvan one may consider using csFastFloat if that is an option. With themulti-threaded (MT)ParallelEnumerate implementation Sep isup to 23x fasterthan Sylvan.

CsvHelper suffers from the fact that one can only access the column as a stringso this has to be allocated for each column (ReadLine by definition alwaysallocates a string per column). Still CsvHelper is significantly slower than thenaiveReadLine approach. With Sep being>4x faster than CsvHelper andup to35x times faster when usingParallelEnumerate.

Note thatParallelEnumerate provides significant speedup over single-threadedparsing even though the source is only about 20 MB. This underlines howefficientParallelEnumerate is, but bear in mind that this is for the case ofrepeated micro-benchmark runs.

It is a testament to how good the .NET and the .NET GC is that the ReadLine ispretty good compared to CsvHelper regardless of allocating a lot of strings.

AMD.EPYC.7763 - FloatsReader Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row250002.936 ms1.00206905.5117.41.26 KB1.00
Sylvan___Row250003.499 ms1.19205793.9140.010.71 KB8.51
ReadLine_Row2500018.762 ms6.39201080.5750.573489.67 KB58,426.57
CsvHelperRow2500038.301 ms13.0520529.31532.019.95 KB15.86
Sep______Cols250003.919 ms1.00205172.2156.81.26 KB1.00
Sylvan___Cols250006.266 ms1.60203235.1250.710.72 KB8.51
ReadLine_Cols2500019.850 ms5.06201021.3794.073489.71 KB58,336.02
CsvHelperCols2500039.623 ms10.1120511.61584.921340.29 KB16,939.89
Sep______Floats2500031.224 ms1.0020649.21249.08.08 KB1.00
Sep_MT___Floats2500012.827 ms0.41201580.4513.168.8 KB8.52
Sylvan___Floats2500084.321 ms2.7020240.43372.818.96 KB2.35
ReadLine_Floats25000113.918 ms3.6520178.04556.773493.2 KB9,101.10
CsvHelperFloats25000166.182 ms5.3220122.06647.322062.48 KB2,732.13
AMD.Ryzen.7.PRO.7840U.w.Radeon.780M - FloatsReader Benchmark Results (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row250003.119 ms1.00206514.3124.81.25 KB1.00
Sylvan___Row250003.690 ms1.18205506.9147.610.71 KB8.53
ReadLine_Row2500016.859 ms5.40201205.3674.473489.66 KB58,562.97
CsvHelperRow2500039.719 ms12.7320511.61588.820.03 KB15.96
Sep______Cols250004.215 ms1.00204821.0168.61.26 KB1.00
Sylvan___Cols250006.074 ms1.44203345.4243.010.71 KB8.52
ReadLine_Cols2500017.072 ms4.05201190.3682.973489.65 KB58,471.95
CsvHelperCols2500042.474 ms10.0820478.41699.021340.26 KB16,979.35
Sep______Floats2500032.362 ms1.0020627.91294.58.32 KB1.00
Sep_MT___Floats250005.940 ms0.18203420.6237.6114.06 KB13.71
Sylvan___Floats2500081.574 ms2.5220249.13263.018.88 KB2.27
ReadLine_Floats25000106.297 ms3.2820191.24251.973493.12 KB8,837.12
CsvHelperFloats25000156.658 ms4.8420129.76266.322062.72 KB2,652.91
AMD.Ryzen.9.5950X - FloatsReader Benchmark Results (Sep 0.9.0.0, Sylvan 1.3.9.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row250002.013 ms1.002010093.480.51.25 KB1.00
Sylvan___Row250002.355 ms1.17208627.494.210.7 KB8.56
ReadLine_Row250009.787 ms4.86202076.1391.573489.63 KB58,791.71
CsvHelperRow2500025.143 ms12.4920808.21005.720 KB16.00
Sep______Cols250002.666 ms1.00207622.2106.61.25 KB1.00
Sylvan___Cols250003.702 ms1.39205488.4148.110.71 KB8.54
ReadLine_Cols2500010.544 ms3.96201927.1421.873489.63 KB58,654.23
CsvHelperCols2500027.442 ms10.2920740.51097.721340.34 KB17,032.36
Sep______Floats2500020.297 ms1.00201001.1811.97.97 KB1.00
Sep_MT___Floats250003.780 ms0.19205375.6151.2179.49 KB22.51
Sylvan___Floats2500052.343 ms2.5820388.22093.718.88 KB2.37
ReadLine_Floats2500068.698 ms3.3820295.82747.973493.12 KB9,215.89
CsvHelperFloats25000100.913 ms4.9720201.44036.522061.69 KB2,766.49
AMD.Ryzen.9.9950X - FloatsReader Benchmark Results (Sep 0.10.0.0, Sylvan 1.4.1.0, CsvHelper 33.0.1.24)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row250001.371 ms1.002014824.554.81.25 KB1.00
Sylvan___Row250001.715 ms1.252011845.168.610.7 KB8.59
ReadLine_Row250007.060 ms5.15202878.2282.473489.63 KB58,976.01
CsvHelperRow2500015.281 ms11.15201329.7611.219.96 KB16.02
Sep______Cols250001.764 ms1.002011520.070.61.25 KB1.00
Sylvan___Cols250002.675 ms1.52207596.7107.010.7 KB8.59
ReadLine_Cols250008.170 ms4.63202487.0326.873489.63 KB58,976.01
CsvHelperCols2500016.694 ms9.47201217.2667.821340.2 KB17,125.68
Sep______Floats2500016.182 ms1.00201255.7647.37.94 KB1.00
Sep_MT___Floats250002.497 ms0.15208136.899.9179.81 KB22.64
Sylvan___Floats2500038.800 ms2.4020523.71552.018.72 KB2.36
ReadLine_Floats2500054.117 ms3.3420375.52164.773493.05 KB9,253.27
CsvHelperFloats2500071.601 ms4.4220283.82864.122061.55 KB2,777.70
Apple.M1.(Virtual) - FloatsReader Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row250002.471 ms1.00208203.598.81.18 KB1.00
Sylvan___Row2500018.834 ms7.63201076.3753.410.62 KB9.01
ReadLine_Row2500017.200 ms6.97201178.6688.073489.65 KB62,399.17
CsvHelperRow2500032.108 ms13.0120631.41284.320.21 KB17.16
Sep______Cols250003.581 ms1.00205660.5143.31.18 KB1.00
Sylvan___Cols2500021.596 ms6.0320938.7863.810.38 KB8.79
ReadLine_Cols2500015.587 ms4.35201300.6623.573489.65 KB62,192.89
CsvHelperCols2500030.036 ms8.3920674.91201.421340.5 KB18,060.06
Sep______Floats2500024.179 ms1.0020838.4967.28.32 KB1.00
Sep_MT___Floats250009.109 ms0.38202225.6364.390.77 KB10.91
Sylvan___Floats2500069.538 ms2.8820291.52781.518.57 KB2.23
ReadLine_Floats2500078.501 ms3.2520258.23140.173493.2 KB8,832.99
CsvHelperFloats25000110.386 ms4.5720183.64415.522063.34 KB2,651.74
Cobalt.100 - FloatsReader Benchmark Results (Sep 0.11.0.0, Sylvan 1.4.2.0, CsvHelper 33.1.0.26)
MethodScopeRowsMeanRatioMBMB/sns/rowAllocatedAlloc Ratio
Sep______Row250003.308 ms1.00206142.8132.31.17 KB1.00
Sylvan___Row2500023.737 ms7.1820856.0949.510.35 KB8.81
ReadLine_Row2500021.754 ms6.5820934.1870.273489.65 KB62,606.82
CsvHelperRow2500032.397 ms9.7920627.21295.920.01 KB17.05
Sep______Cols250004.301 ms1.00204724.2172.01.18 KB1.00
Sylvan___Cols2500026.464 ms6.1520767.81058.610.35 KB8.79
ReadLine_Cols2500022.615 ms5.2620898.5904.673489.67 KB62,399.19
CsvHelperCols2500035.721 ms8.3120568.91428.821340.21 KB18,119.71
Sep______Floats2500030.363 ms1.0020669.21214.57.96 KB1.00
Sep_MT___Floats250008.361 ms0.28202430.4334.466.37 KB8.34
Sylvan___Floats2500096.160 ms3.1720211.33846.418.42 KB2.32
ReadLine_Floats25000103.393 ms3.4120196.54135.773493 KB9,238.50
CsvHelperFloats25000143.722 ms4.7320141.45748.922061.91 KB2,773.31

Writer Comparison Benchmarks

Writer benchmarks are still pending, but Sep is unlikely to be the fastest heresince it is explicitly designed to make writing more convenient and flexible.Still efficient, but not necessarily fastest. That is, Sep does not requirewriting header up front and hence having to keep header column order and rowvalues column order the same. This means Sep does not write columnsdirectlyupon definition but defers this until a new row has been fully defined and thenis ended.

Example Catalogue

The following examples are available inReadMeTest.cs.

Example - Copy Rows

vartext="""           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 endingusingvarreader=Sep.Reader().FromText(text);usingvarwriter=reader.Spec.Writer().ToText();foreach(varreadRowinreader){usingvarwriteRow=writer.NewRow(readRow);}Assert.AreEqual(text,writer.ToString());

Example - Copy Rows (Async)

vartext="""           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 endingusingvarreader=awaitSep.Reader().FromTextAsync(text);awaitusingvarwriter=reader.Spec.Writer().ToText();awaitforeach(varreadRowinreader){awaitusingvarwriteRow=writer.NewRow(readRow);}Assert.AreEqual(text,writer.ToString());

Example - Skip Empty Rows

vartext="""           A           1           2           3           4           """;// Empty line at end is for line endingvarexpected=new[]{1,2,3,4};// Disable col count check to allow empty rowsusingvarreader=Sep.Reader(o=>owith{DisableColCountCheck=true}).FromText(text);varactual=newList<int>();foreach(varrowinreader){// Skip empty rowif(row.Span.Length==0){continue;}actual.Add(row["A"].Parse<int>());}CollectionAssert.AreEqual(expected,actual);

Example - Use Extension Method Enumerate within async/await Context (prior to C# 13.0)

SinceSepReader.Row is aref struct as covered above, one has to avoidreferencing it directly in async context for C# prior to 13.0. This can be donein a number of ways, but one way is to useEnumerate extension method toparse/extract data from row like shown below.

vartext="""           C           1           2           """;usingvarreader=Sep.Reader().FromText(text);varsquaredSum=0;// Use Enumerate to avoid referencing SepReader.Row in async contextforeach(varvalueinreader.Enumerate(row=>row["C"].Parse<int>())){squaredSum+=awaitTask.Run(()=>value*value);}Assert.AreEqual(5,squaredSum);

Example - Use Local Function within async/await Context

Another way to avoid referencingSepReader.Row directly in async context is touse custom iterator viayield return to parse/extract data from row like shownbelow.

vartext="""           C           1           2           """;usingvarreader=Sep.Reader().FromText(text);varsquaredSum=0;// Use custom local function Enumerate to avoid referencing// SepReader.Row in async contextforeach(varvalueinEnumerate(reader)){squaredSum+=awaitTask.Run(()=>value*value);}Assert.AreEqual(5,squaredSum);staticIEnumerable<int>Enumerate(SepReaderreader){foreach(varrinreader){yieldreturnr["C"].Parse<int>();}}

Example - Skip Lines/Rows Starting with Comment#

Below shows how one can skip lines starting with comment# since Sep does nothave built-in support for this. Note that this presumes lines to be skippedbefore header do not contain quotes or rather line endings within quotes as thatis not handled by thePeek() skipping. The rows starting with comment#after header are skipped if handling quoting is enabled in Sep options.

vartext="""           # Comment 1           # Comment 2           A           # Comment 3           1           2           # Comment 4           """;constcharComment='#';usingvartextReader=newStringReader(text);// Skip initial lines (not rows) before headerwhile(textReader.Peek()==Comment&&textReader.ReadLine()isstringline){}usingvarreader=Sep.Reader().From(textReader);varvalues=newList<int>();foreach(varrowinreader){// Skip rows starting with commentif(row.Span.StartsWith([Comment])){continue;}varvalue=row["A"].Parse<int>();values.Add(value);}CollectionAssert.AreEqual(newint[]{1,2},values);

RFC-4180

While theRFC-4180 requires\r\n(CR,LF) as line ending, the well-known line endings (\r\n,\n and\r) aresupported similar to .NET.Environment.NewLine is used when writing. Quotingis supported by simply matching pairs of quotes, no matter what.

Note that some libraries will claim conformance but the RFC is, perhapsnaturally, quite strict e.g. only comma is supported as separator/delimiter. Sepdefaults to using; as separator if writing, while auto-detecting supportedseparators when reading. This is decidedly non-conforming.

The RFC defines the following condensedABNFgrammar:

file = [header CRLF] record *(CRLF record) [CRLF]header = name *(COMMA name)record = field *(COMMA field)name = fieldfield = (escaped / non-escaped)escaped = DQUOTE *(TEXTDATA / COMMA / CR / LF / 2DQUOTE) DQUOTEnon-escaped = *TEXTDATACOMMA = %x2CCR = %x0D ;as per section 6.1 of RFC 2234 [2]DQUOTE =  %x22 ;as per section 6.1 of RFC 2234 [2]LF = %x0A ;as per section 6.1 of RFC 2234 [2]CRLF = CR LF ;as per section 6.1 of RFC 2234 [2]TEXTDATA =  %x20-21 / %x23-2B / %x2D-7E

Note howTEXTDATA is restricted too, yet many will allow any character incl.emojis or similar (which Sep supports), but is not in conformance with the RFC.

Quotes inside an escaped field e.g."fie""ld" are only allowed to be doublequotes. Sep currently allows any pairs of quotes and quoting doesn't need to beat start of or end of field (col or column in Sep terminology).

All in all Sep takes a pretty pragmatic approach here as the primary use case isnot exchanging data on the internet, but for use in machine learningpipelines or similar.

Frequently Asked Questions (FAQ)

Ask questions on GitHub and this section will be expanded. :)

  • Does Sep supportobject mapping likeCsvHelper?No, Sep is a minimal library and does not support object mapping. First, thisis usually supported via reflection, which Sep avoids. Second, object mappingoften only works well in a few cases without actually writing custom mappingfor each property, which then basically amounts to writing the parsing codeyourself. If object mapping is a must have, consider writing your ownsourcegeneratorfor it if you want to use Sep. Maybe some day Sep will have a built-in sourcegenerator, but not in the foreseeable future.

SepReader FAQ

SepWriter FAQ

Links

Public API Reference

[assembly:System.CLSCompliant(false)][assembly:System.Reflection.AssemblyMetadata("IsTrimmable","True")][assembly:System.Reflection.AssemblyMetadata("RepositoryUrl","https://github.com/nietras/Sep/")][assembly:System.Resources.NeutralResourcesLanguage("en")][assembly:System.Runtime.CompilerServices.InternalsVisibleTo("Sep.Benchmarks")][assembly:System.Runtime.CompilerServices.InternalsVisibleTo("Sep.ComparisonBenchmarks")][assembly:System.Runtime.CompilerServices.InternalsVisibleTo("Sep.Test")][assembly:System.Runtime.CompilerServices.InternalsVisibleTo("Sep.XyzTest")][assembly:System.Runtime.Versioning.TargetFramework(".NETCoreApp,Version=v9.0",FrameworkDisplayName=".NET 9.0")]namespacenietras.SeparatedValues{publicreadonlystructSep:System.IEquatable<nietras.SeparatedValues.Sep>{publicSep(){}publicSep(charseparator){}publiccharSeparator{get;init;}publicstaticnietras.SeparatedValues.Sep?Auto{get;}publicstaticnietras.SeparatedValues.SepDefault{get;}publicstaticnietras.SeparatedValues.SepNew(charseparator){}publicstaticnietras.SeparatedValues.SepReaderOptionsReader(){}publicstaticnietras.SeparatedValues.SepReaderOptionsReader(System.Func<nietras.SeparatedValues.SepReaderOptions,nietras.SeparatedValues.SepReaderOptions>configure){}publicstaticnietras.SeparatedValues.SepWriterOptionsWriter(){}publicstaticnietras.SeparatedValues.SepWriterOptionsWriter(System.Func<nietras.SeparatedValues.SepWriterOptions,nietras.SeparatedValues.SepWriterOptions>configure){}}publicenumSepColNotSetOption:byte{Throw=0,Empty=1,Skip=2,}publicdelegatenietras.SeparatedValues.SepToStringSepCreateToString(nietras.SeparatedValues.SepReaderHeader?maybeHeader,intcolCount);publicstaticclassSepDefaults{publicstaticSystem.StringComparerColNameComparer{get;}publicstaticSystem.Globalization.CultureInfoCultureInfo{get;}publicstaticcharSeparator{get;}}[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")]publicsealedclassSepReader:nietras.SeparatedValues.SepReaderState,System.Collections.Generic.IAsyncEnumerable<nietras.SeparatedValues.SepReader.Row>,System.Collections.Generic.IEnumerable<nietras.SeparatedValues.SepReader.Row>,System.Collections.Generic.IEnumerator<nietras.SeparatedValues.SepReader.Row>,System.Collections.IEnumerable,System.Collections.IEnumerator,System.IDisposable{publicnietras.SeparatedValues.SepReader.RowCurrent{get;}publicboolHasHeader{get;}publicboolHasRows{get;}publicnietras.SeparatedValues.SepReaderHeaderHeader{get;}publicboolIsEmpty{get;}publicnietras.SeparatedValues.SepSpecSpec{get;}publicnietras.SeparatedValues.SepReader.AsyncEnumeratorGetAsyncEnumerator(System.Threading.CancellationTokencancellationToken=default){}publicnietras.SeparatedValues.SepReaderGetEnumerator(){}publicboolMoveNext(){}publicSystem.Threading.Tasks.ValueTask<bool>MoveNextAsync(System.Threading.CancellationTokencancellationToken=default){}publicstringToString(intindex){}publicreadonlystructAsyncEnumerator:System.Collections.Generic.IAsyncEnumerator<nietras.SeparatedValues.SepReader.Row>,System.IAsyncDisposable{publicnietras.SeparatedValues.SepReader.RowCurrent{get;}publicSystem.Threading.Tasks.ValueTaskDisposeAsync(){}publicSystem.Threading.Tasks.ValueTask<bool>MoveNextAsync(){}}[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay}")]publicreadonlyrefstructCol{publicSystem.ReadOnlySpan<char>Span{get;}publicTParse<T>()whereT:System.ISpanParsable<T>{}publicoverridestringToString(){}publicT?TryParse<T>()whereT:struct,System.ISpanParsable<T>{}publicboolTryParse<T>(outTvalue)whereT:System.ISpanParsable<T>{}}publicreadonlyrefstructCols{publicintCount{get;}publicnietras.SeparatedValues.SepReader.Colthis[intindex]{get;}publicstringCombinePathsToString(){}publicSystem.ReadOnlySpan<char>Join(System.ReadOnlySpan<char>separator){}publicstringJoinPathsToString(){}publicstringJoinToString(System.ReadOnlySpan<char>separator){}publicSystem.Span<T>Parse<T>()whereT:System.ISpanParsable<T>{}publicvoidParse<T>(System.Span<T>span)whereT:System.ISpanParsable<T>{}publicT[]ParseToArray<T>()whereT:System.ISpanParsable<T>{}publicSystem.Span<T>Select<T>(methodselector){}publicSystem.Span<T>Select<T>(nietras.SeparatedValues.SepReader.ColFunc<T>selector){}publicSystem.Span<string>ToStrings(){}publicstring[]ToStringsArray(){}publicSystem.Span<T?>TryParse<T>()whereT:struct,System.ISpanParsable<T>{}publicvoidTryParse<T>(System.Span<T?>span)whereT:struct,System.ISpanParsable<T>{}}[System.Diagnostics.DebuggerDisplay("{DebuggerDisplayPrefix,nq}{Span}")][System.Diagnostics.DebuggerTypeProxy(typeof(nietras.SeparatedValues.SepReader.Row.DebugView))]publicreadonlyrefstructRow{publicintColCount{get;}publicnietras.SeparatedValues.SepReader.Colthis[intindex]{get;}publicnietras.SeparatedValues.SepReader.Colthis[System.Indexindex]{get;}publicnietras.SeparatedValues.SepReader.Colthis[stringcolName]{get;}publicnietras.SeparatedValues.SepReader.Colsthis[System.Rangerange]{get;}publicnietras.SeparatedValues.SepReader.Colsthis[System.ReadOnlySpan<int>indices]{get;}publicnietras.SeparatedValues.SepReader.Colsthis[System.Collections.Generic.IReadOnlyList<int>indices]{get;}publicnietras.SeparatedValues.SepReader.Colsthis[int[]indices]{get;}publicnietras.SeparatedValues.SepReader.Colsthis[System.ReadOnlySpan<string>colNames]{get;}publicnietras.SeparatedValues.SepReader.Colsthis[System.Collections.Generic.IReadOnlyList<string>colNames]{get;}publicnietras.SeparatedValues.SepReader.Colsthis[string[]colNames]{get;}publicintLineNumberFrom{get;}publicintLineNumberToExcl{get;}publicintRowIndex{get;}publicSystem.ReadOnlySpan<char>Span{get;}publicSystem.Func<int,string>UnsafeToStringDelegate{get;}publicoverridestringToString(){}}publicdelegatevoidColAction(nietras.SeparatedValues.SepReader.Colcol);publicdelegateTColFunc<T>(nietras.SeparatedValues.SepReader.Colcol);publicdelegatevoidColsAction(nietras.SeparatedValues.SepReader.Colscol);publicdelegatevoidRowAction(nietras.SeparatedValues.SepReader.Rowrow);publicdelegateTRowFunc<T>(nietras.SeparatedValues.SepReader.Rowrow);publicdelegateboolRowTryFunc<T>(nietras.SeparatedValues.SepReader.Rowrow,outTvalue);}publicstaticclassSepReaderExtensions{publicstaticSystem.Collections.Generic.IEnumerable<T>Enumerate<T>(thisnietras.SeparatedValues.SepReaderreader,nietras.SeparatedValues.SepReader.RowFunc<T>select){}publicstaticSystem.Collections.Generic.IEnumerable<T>Enumerate<T>(thisnietras.SeparatedValues.SepReaderreader,nietras.SeparatedValues.SepReader.RowTryFunc<T>trySelect){}publicstaticSystem.Collections.Generic.IAsyncEnumerable<T>EnumerateAsync<T>(thisnietras.SeparatedValues.SepReaderreader,nietras.SeparatedValues.SepReader.RowFunc<T>select){}publicstaticSystem.Collections.Generic.IAsyncEnumerable<T>EnumerateAsync<T>(thisnietras.SeparatedValues.SepReaderreader,nietras.SeparatedValues.SepReader.RowTryFunc<T>trySelect){}publicstaticnietras.SeparatedValues.SepReaderFrom(thisinnietras.SeparatedValues.SepReaderOptionsoptions,System.IO.Streamstream){}publicstaticnietras.SeparatedValues.SepReaderFrom(thisinnietras.SeparatedValues.SepReaderOptionsoptions,System.IO.TextReaderreader){}publicstaticnietras.SeparatedValues.SepReaderFrom(thisinnietras.SeparatedValues.SepReaderOptionsoptions,byte[]buffer){}publicstaticnietras.SeparatedValues.SepReaderFrom(thisinnietras.SeparatedValues.SepReaderOptionsoptions,stringname,System.Func<string,System.IO.Stream>nameToStream){}publicstaticnietras.SeparatedValues.SepReaderFrom(thisinnietras.SeparatedValues.SepReaderOptionsoptions,stringname,System.Func<string,System.IO.TextReader>nameToReader){}publicstaticSystem.Threading.Tasks.ValueTask<nietras.SeparatedValues.SepReader>FromAsync(thisnietras.SeparatedValues.SepReaderOptionsoptions,System.IO.Streamstream,System.Threading.CancellationTokencancellationToken=default){}publicstaticSystem.Threading.Tasks.ValueTask<nietras.SeparatedValues.SepReader>FromAsync(thisnietras.SeparatedValues.SepReaderOptionsoptions,System.IO.TextReaderreader,System.Threading.CancellationTokencancellationToken=default){}publicstaticSystem.Threading.Tasks.ValueTask<nietras.SeparatedValues.SepReader>FromAsync(thisnietras.SeparatedValues.SepReaderOptionsoptions,byte[]buffer,System.Threading.CancellationTokencancellationToken=default){}publicstaticSystem.Threading.Tasks.ValueTask<nietras.SeparatedValues.SepReader>FromAsync(thisnietras.SeparatedValues.SepReaderOptionsoptions,stringname,System.Func<string,System.IO.Stream>nameToStream,System.Threading.CancellationTokencancellationToken=default){}publicstaticSystem.Threading.Tasks.ValueTask<nietras.SeparatedValues.SepReader>FromAsync(thisnietras.SeparatedValues.SepReaderOptionsoptions,stringname,System.Func<string,System.IO.TextReader>nameToReader,System.Threading.CancellationTokencancellationToken=default){}publicstaticnietras.SeparatedValues.SepReaderFromFile(thisinnietras.SeparatedValues.SepReaderOptionsoptions,stringfilePath){}publicstaticSystem.Threading.Tasks.ValueTask<nietras.SeparatedValues.SepReader>FromFileAsync(thisnietras.SeparatedValues.SepReaderOptionsoptions,stringfilePath,System.Threading.CancellationTokencancellationToken=default){}publicstaticnietras.SeparatedValues.SepReaderFromText(thisinnietras.SeparatedValues.SepReaderOptionsoptions,stringtext){}publicstaticSystem.Threading.Tasks.ValueTask<nietras.SeparatedValues.SepReader>FromTextAsync(thisnietras.SeparatedValues.SepReaderOptionsoptions,stringtext,System.Threading.CancellationTokencancellationToken=default){}publicstaticSystem.Collections.Generic.IEnumerable<T>ParallelEnumerate<T>(thisnietras.SeparatedValues.SepReaderreader,nietras.SeparatedValues.SepReader.RowFunc<T>select){}publicstaticSystem.Collections.Generic.IEnumerable<T>ParallelEnumerate<T>(thisnietras.SeparatedValues.SepReaderreader,nietras.SeparatedValues.SepReader.RowTryFunc<T>trySelect){}publicstaticSystem.Collections.Generic.IEnumerable<T>ParallelEnumerate<T>(thisnietras.SeparatedValues.SepReaderreader,nietras.SeparatedValues.SepReader.RowFunc<T>select,intdegreeOfParallism){}publicstaticSystem.Collections.Generic.IEnumerable<T>ParallelEnumerate<T>(thisnietras.SeparatedValues.SepReaderreader,nietras.SeparatedValues.SepReader.RowTryFunc<T>trySelect,intdegreeOfParallism){}publicstaticnietras.SeparatedValues.SepReaderOptionsReader(thisnietras.SeparatedValues.Sepsep){}publicstaticnietras.SeparatedValues.SepReaderOptionsReader(thisnietras.SeparatedValues.Sep?sep){}publicstaticnietras.SeparatedValues.SepReaderOptionsReader(thisnietras.SeparatedValues.SepSpecspec){}publicstaticnietras.SeparatedValues.SepReaderOptionsReader(thisnietras.SeparatedValues.Sepsep,System.Func<nietras.SeparatedValues.SepReaderOptions,nietras.SeparatedValues.SepReaderOptions>configure){}publicstaticnietras.SeparatedValues.SepReaderOptionsReader(thisnietras.SeparatedValues.Sep?sep,System.Func<nietras.SeparatedValues.SepReaderOptions,nietras.SeparatedValues.SepReaderOptions>configure){}publicstaticnietras.SeparatedValues.SepReaderOptionsReader(thisnietras.SeparatedValues.SepSpecspec,System.Func<nietras.SeparatedValues.SepReaderOptions,nietras.SeparatedValues.SepReaderOptions>configure){}}publicsealedclassSepReaderHeader{publicSystem.Collections.Generic.IReadOnlyList<string>ColNames{get;}publicboolIsEmpty{get;}publicstaticnietras.SeparatedValues.SepReaderHeaderEmpty{get;}publicintIndexOf(System.ReadOnlySpan<char>colName){}publicintIndexOf(stringcolName){}publicint[]IndicesOf(System.Collections.Generic.IReadOnlyList<string>colNames){}publicint[]IndicesOf([System.Runtime.CompilerServices.ParamCollection][System.Runtime.CompilerServices.ScopedRef]System.ReadOnlySpan<string>colNames){}publicint[]IndicesOf(paramsstring[]colNames){}publicvoidIndicesOf(System.ReadOnlySpan<string>colNames,System.Span<int>colIndices){}publicSystem.Collections.Generic.IReadOnlyList<string>NamesStartingWith(stringprefix,System.StringComparisoncomparison=4){}publicoverridestringToString(){}publicboolTryIndexOf(System.ReadOnlySpan<char>colName,outintcolIndex){}publicboolTryIndexOf(stringcolName,outintcolIndex){}}publicreadonlystructSepReaderOptions:System.IEquatable<nietras.SeparatedValues.SepReaderOptions>{publicSepReaderOptions(){}publicSepReaderOptions(nietras.SeparatedValues.Sep?sep){}publicboolAsyncContinueOnCapturedContext{get;init;}publicSystem.Collections.Generic.IEqualityComparer<string>ColNameComparer{get;init;}publicnietras.SeparatedValues.SepCreateToStringCreateToString{get;init;}publicSystem.Globalization.CultureInfo?CultureInfo{get;init;}publicboolDisableColCountCheck{get;init;}publicboolDisableFastFloat{get;init;}publicboolDisableQuotesParsing{get;init;}publicboolHasHeader{get;init;}publicintInitialBufferLength{get;init;}publicnietras.SeparatedValues.Sep?Sep{get;init;}publicnietras.SeparatedValues.SepTrimTrim{get;init;}publicboolUnescape{get;init;}}publicclassSepReaderState:System.IDisposable{publicvoidDispose(){}}publicstaticclassSepReaderWriterExtensions{publicstaticvoidCopyTo(thisnietras.SeparatedValues.SepReader.RowreaderRow,nietras.SeparatedValues.SepWriter.RowwriterRow){}publicstaticnietras.SeparatedValues.SepWriter.RowNewRow(thisnietras.SeparatedValues.SepWriterwriter,nietras.SeparatedValues.SepReader.RowrowToCopy){}publicstaticnietras.SeparatedValues.SepWriter.RowNewRow(thisnietras.SeparatedValues.SepWriterwriter,nietras.SeparatedValues.SepReader.RowrowToCopy,System.Threading.CancellationTokencancellationToken){}}publicreadonlystructSepSpec:System.IEquatable<nietras.SeparatedValues.SepSpec>{publicSepSpec(){}publicSepSpec(nietras.SeparatedValues.Sepsep,System.Globalization.CultureInfo?cultureInfo){}publicSepSpec(nietras.SeparatedValues.Sepsep,System.Globalization.CultureInfo?cultureInfo,boolasyncContinueOnCapturedContext){}publicboolAsyncContinueOnCapturedContext{get;init;}publicSystem.Globalization.CultureInfo?CultureInfo{get;init;}publicnietras.SeparatedValues.SepSep{get;init;}}publicabstractclassSepToString:System.IDisposable{protectedSepToString(){}publicvirtualboolIsThreadSafe{get;}publicstaticnietras.SeparatedValues.SepCreateToStringDirect{get;}publicvoidDispose(){}protectedvirtualvoidDispose(booldisposing){}publicabstractstringToString(System.ReadOnlySpan<char>colSpan,intcolIndex);publicstaticnietras.SeparatedValues.SepCreateToStringOnePool(intmaximumStringLength=32,intinitialCapacity=64,intmaximumCapacity=4096){}publicstaticnietras.SeparatedValues.SepCreateToStringPoolPerCol(intmaximumStringLength=32,intinitialCapacity=64,intmaximumCapacity=4096){}publicstaticnietras.SeparatedValues.SepCreateToStringPoolPerColThreadSafe(intmaximumStringLength=32,intinitialCapacity=64,intmaximumCapacity=4096){}publicstaticnietras.SeparatedValues.SepCreateToStringPoolPerColThreadSafeFixedCapacity(intmaximumStringLength=32,intcapacity=2048){}}[System.Flags]publicenumSepTrim:byte{None=0,Outer=1,AfterUnescape=2,All=3,}[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")]publicsealedclassSepWriter:System.IAsyncDisposable,System.IDisposable{publicnietras.SeparatedValues.SepWriterHeaderHeader{get;}publicnietras.SeparatedValues.SepSpecSpec{get;}publicvoidDispose(){}publicSystem.Threading.Tasks.ValueTaskDisposeAsync(){}publicvoidFlush(){}publicSystem.Threading.Tasks.TaskFlushAsync(System.Threading.CancellationTokencancellationToken=default){}publicnietras.SeparatedValues.SepWriter.RowNewRow(){}publicnietras.SeparatedValues.SepWriter.RowNewRow(System.Threading.CancellationTokencancellationToken){}publicoverridestringToString(){}publicreadonlyrefstructCol{publicvoidFormat<T>(Tvalue)whereT:System.ISpanFormattable{}publicvoidSet(System.ReadOnlySpan<char>span){}publicvoidSet([System.Runtime.CompilerServices.InterpolatedStringHandlerArgument("")]refnietras.SeparatedValues.SepWriter.Col.FormatInterpolatedStringHandlerhandler){}publicvoidSet(System.IFormatProvider?provider,[System.Runtime.CompilerServices.InterpolatedStringHandlerArgument(newstring?[]?[]{"","provider"})]refnietras.SeparatedValues.SepWriter.Col.FormatInterpolatedStringHandlerhandler){}[System.Runtime.CompilerServices.InterpolatedStringHandler]publicrefstructFormatInterpolatedStringHandler{publicFormatInterpolatedStringHandler(intliteralLength,intformattedCount,nietras.SeparatedValues.SepWriter.Colcol){}publicFormatInterpolatedStringHandler(intliteralLength,intformattedCount,nietras.SeparatedValues.SepWriter.Colcol,System.IFormatProvider?provider){}publicvoidAppendFormatted(System.ReadOnlySpan<char>value){}publicvoidAppendFormatted(string?value){}publicvoidAppendFormatted(System.ReadOnlySpan<char>value,intalignment=0,string?format=null){}publicvoidAppendFormatted(object?value,intalignment=0,string?format=null){}publicvoidAppendFormatted(string?value,intalignment=0,string?format=null){}publicvoidAppendFormatted<T>(Tvalue){}publicvoidAppendFormatted<T>(Tvalue,intalignment){}publicvoidAppendFormatted<T>(Tvalue,string?format){}publicvoidAppendFormatted<T>(Tvalue,intalignment,string?format){}publicvoidAppendLiteral(stringvalue){}}}publicreadonlyrefstructCols{publicintCount{get;}publicnietras.SeparatedValues.SepWriter.Colthis[intcolIndex]{get;}publicvoidFormat<T>(System.Collections.Generic.IReadOnlyList<T>values)whereT:System.ISpanFormattable{}publicvoidFormat<T>([System.Runtime.CompilerServices.ParamCollection][System.Runtime.CompilerServices.ScopedRef]System.ReadOnlySpan<T>values)whereT:System.ISpanFormattable{}publicvoidFormat<T>(System.Span<T>values)whereT:System.ISpanFormattable{}publicvoidFormat<T>(T[]values)whereT:System.ISpanFormattable{}publicvoidFormat<T>(System.ReadOnlySpan<T>values,nietras.SeparatedValues.SepWriter.ColAction<T>format){}publicvoidSet(System.Collections.Generic.IReadOnlyList<string>values){}publicvoidSet([System.Runtime.CompilerServices.ParamCollection][System.Runtime.CompilerServices.ScopedRef]System.ReadOnlySpan<string>values){}publicvoidSet(nietras.SeparatedValues.SepReader.Colscols){}publicvoidSet(string[]values){}}publicrefstructRow:System.IAsyncDisposable,System.IDisposable{publicnietras.SeparatedValues.SepWriter.Colthis[intcolIndex]{get;}publicnietras.SeparatedValues.SepWriter.Colthis[stringcolName]{get;}publicnietras.SeparatedValues.SepWriter.Colsthis[System.ReadOnlySpan<int>indices]{get;}publicnietras.SeparatedValues.SepWriter.Colsthis[System.ReadOnlySpan<string>colNames]{get;}publicnietras.SeparatedValues.SepWriter.Colsthis[System.Collections.Generic.IReadOnlyList<string>colNames]{get;}publicnietras.SeparatedValues.SepWriter.Colsthis[string[]colNames]{get;}publicvoidDispose(){}publicSystem.Threading.Tasks.ValueTaskDisposeAsync(){}}publicdelegatevoidColAction(nietras.SeparatedValues.SepWriter.Colcol);publicdelegatevoidColAction<T>(nietras.SeparatedValues.SepWriter.Colcol,Tvalue);publicdelegatevoidRowAction(nietras.SeparatedValues.SepWriter.Rowrow);}publicstaticclassSepWriterExtensions{publicstaticnietras.SeparatedValues.SepWriterTo(thisinnietras.SeparatedValues.SepWriterOptionsoptions,System.IO.Streamstream){}publicstaticnietras.SeparatedValues.SepWriterTo(thisinnietras.SeparatedValues.SepWriterOptionsoptions,System.IO.TextWriterwriter){}publicstaticnietras.SeparatedValues.SepWriterTo(thisinnietras.SeparatedValues.SepWriterOptionsoptions,System.Text.StringBuilderstringBuilder){}publicstaticnietras.SeparatedValues.SepWriterTo(thisinnietras.SeparatedValues.SepWriterOptionsoptions,System.IO.Streamstream,boolleaveOpen){}publicstaticnietras.SeparatedValues.SepWriterTo(thisinnietras.SeparatedValues.SepWriterOptionsoptions,System.IO.TextWriterwriter,boolleaveOpen){}publicstaticnietras.SeparatedValues.SepWriterTo(thisinnietras.SeparatedValues.SepWriterOptionsoptions,stringname,System.Func<string,System.IO.Stream>nameToStream,boolleaveOpen=false){}publicstaticnietras.SeparatedValues.SepWriterTo(thisinnietras.SeparatedValues.SepWriterOptionsoptions,stringname,System.Func<string,System.IO.TextWriter>nameToWriter,boolleaveOpen=false){}publicstaticnietras.SeparatedValues.SepWriterToFile(thisinnietras.SeparatedValues.SepWriterOptionsoptions,stringfilePath){}publicstaticnietras.SeparatedValues.SepWriterToText(thisinnietras.SeparatedValues.SepWriterOptionsoptions){}publicstaticnietras.SeparatedValues.SepWriterToText(thisinnietras.SeparatedValues.SepWriterOptionsoptions,intcapacity){}publicstaticnietras.SeparatedValues.SepWriterOptionsWriter(thisnietras.SeparatedValues.Sepsep){}publicstaticnietras.SeparatedValues.SepWriterOptionsWriter(thisnietras.SeparatedValues.SepSpecspec){}publicstaticnietras.SeparatedValues.SepWriterOptionsWriter(thisnietras.SeparatedValues.Sepsep,System.Func<nietras.SeparatedValues.SepWriterOptions,nietras.SeparatedValues.SepWriterOptions>configure){}publicstaticnietras.SeparatedValues.SepWriterOptionsWriter(thisnietras.SeparatedValues.SepSpecspec,System.Func<nietras.SeparatedValues.SepWriterOptions,nietras.SeparatedValues.SepWriterOptions>configure){}}[System.Diagnostics.DebuggerDisplay("{DebuggerDisplay,nq}")][System.Diagnostics.DebuggerTypeProxy(typeof(nietras.SeparatedValues.SepWriterHeader.DebugView))]publicsealedclassSepWriterHeader{publicvoidAdd(System.Collections.Generic.IReadOnlyList<string>colNames){}publicvoidAdd([System.Runtime.CompilerServices.ParamCollection][System.Runtime.CompilerServices.ScopedRef]System.ReadOnlySpan<string>colNames){}publicvoidAdd(stringcolName){}publicvoidAdd(string[]colNames){}publicboolContains(stringcolName){}publicboolTryAdd(stringcolName){}publicvoidWrite(){}publicSystem.Threading.Tasks.ValueTaskWriteAsync(System.Threading.CancellationTokencancellationToken=default){}}publicreadonlystructSepWriterOptions:System.IEquatable<nietras.SeparatedValues.SepWriterOptions>{publicSepWriterOptions(){}publicSepWriterOptions(nietras.SeparatedValues.Sepsep){}publicboolAsyncContinueOnCapturedContext{get;init;}publicnietras.SeparatedValues.SepColNotSetOptionColNotSetOption{get;init;}publicSystem.Globalization.CultureInfo?CultureInfo{get;init;}publicboolDisableColCountCheck{get;init;}publicboolEscape{get;init;}publicnietras.SeparatedValues.SepSep{get;init;}publicboolWriteHeader{get;init;}}}

About

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.

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Languages


[8]ページ先頭

©2009-2025 Movatter.jp