Skip to content

FSharp.Core #if NET fast-path opportunities — SIMD / span / TensorPrimitives / formatting (unblocked by #20229) #20283

Description

@T-Gro

#20229 ships FSharp.Core with a net10.0 TFM. A #if NET8_0_OR_GREATER / #if NET block therefore compiles only into the net asset — netstandard2.0/netstandard2.1 stay bit-identical. This tracks the additive, guarded fast paths that unlocks.

Important gate distinction some span APIs are already in netstandard2.1, so FSharp.Core's ns2.1 asset could use them too — those are perf wins but not strictly gated on #20229.

Format per row: F# affectedFSharp.Core file:line.NET API → guard → win.

1 · Numeric reductions → SIMD TensorPrimitives(net-only)

F# affectedFSharp.Core file.NET APIguardwin
Array.sum / Array.averagearray.fs:1628/1722TensorPrimitives.Sum (net8)#if NET8_0_OR_GREATER2–5× · gated: float reassociation; int drops Checked.(+) overflow
Array.max / Array.minarray.fs:1685/1648TensorPrimitives.Max/Min (net8)#if NET8_0_OR_GREATER3–4× · gated: IEEE vs F# NaN — safe for ints
Seq.sum/average/min/max (array fast-path)seq.fs:1465/1485/1517/1557TensorPrimitives behind :? ('T[]) (net8)#if NET8_0_OR_GREATER2–4× · inherits above; array-backed only

2 · Primitive-array equality & comparison (ns2.1+)

F# affectedFSharp.Core file.NET APIguardwin
GenericEquality{Byte,Int32,Int64,Char}Arrayprim-types.fs:1481/1496/1558/1543MemoryExtensions.SequenceEqual<'T>`#if NETSTANDARD2_1_OR_GREATER
GenericComparisonByteArrayprim-types.fs:1230MemoryExtensions.SequenceCompareTo<byte>`#if NETSTANDARD2_1_OR_GREATER

3 · Array search & reorder (ns2.1+)

F# affectedFSharp.Core file.NET APIguardwin
Array.contains (single primitive)array.fs:~488MemoryExtensions.Contains / ReadOnlySpan.IndexOf`#if NETSTANDARD2_1_OR_GREATER
Array.revarray.fs:1386System.Array.Reverseany TFMsmall

4 · Primitive-array hashing (net-only)

F# affectedFSharp.Core file.NET APIguardwin
GenericHash{Byte,Int32,Int64}Arrayprim-types.fs:964/975/986HashCode.AddBytes (net6)#if NET6_0_OR_GREATER15–30% · keep in-process hash stability; 10-node cap

5 · String building (ns2.1+)

F# affectedFSharp.Core file.NET APIguardwin
String.map / String.mapi / String.filterstring.fs:57/71/89String.Create(len, state, writer)`#if NETSTANDARD2_1_OR_GREATER

6 · Formatting / printf (mixed)

F# affectedFSharp.Core file.NET APIguardwin
%O/%P/interpoland/user types/decimal/DateTimeprintf.fs:911–929ObjectToString/InterpolandToString (+ span sink 1073–1083)ISpanFormattable.TryFormat (net6) + String.Create#if NET6_0_OR_GREATERskip 1 temp string per arg; polymorphic (no per-type match). No-width case first — padding is string-based today
numeric specifiers %d %i %u %x %X %f %e %g %M (no width/prec)printf.fs:746–770/852–859concrete Int32/Int64/Double/Single/Decimal.TryFormat into Span<char>`#if NETSTANDARD2_1_OR_GREATER
StringBuilder / TextWriter sinksprintf.fs:1073–1083StringBuilder.Append(ReadOnlySpan<char>) / TextWriter.Write(ReadOnlySpan<char>)`#if NETSTANDARD2_1_OR_GREATER
sprintf string sinkprintf.fs:1029–1071pooled char[] (ArrayPool) + String.Create`#if NETSTANDARD2_1_OR_GREATER

7 · Generic math (net-only)

F# affectedFSharp.Core file.NET APIguardwin
GenericZero/OneDynamicImplTable (Half)prim-types.fs:2667/2700typeof<System.Half> fast case (net5)#if NET5_0_OR_GREATERavoid reflection

No boost (documented negatives)

  • GenericEquality{Single,Double}Array (prim-types.fs:1511/1527) — NaN-reflexivity (er flag: NaN = NaN) diverges from IEEE SequenceEqual; keep scalar.
  • DefaultInterpolatedStringHandler as the printf sink — it is a ref struct, cannot be a field of the heap PrintfEnv class; and sprintf's ToStringAndClear() is itself just String.Create over a pool. DISH belongs to a compiler lowering.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status
    New

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions