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# affected FSharp.Core file .NET API guard win
Array.sum / Array.average array.fs:1628/1722 TensorPrimitives.Sum (net8) #if NET8_0_OR_GREATER 2–5× · gated: float reassociation; int drops Checked.(+) overflow
Array.max / Array.min array.fs:1685/1648 TensorPrimitives.Max/Min (net8) #if NET8_0_OR_GREATER 3–4× · gated: IEEE vs F# NaN — safe for ints
Seq.sum/average/min/max (array fast-path) seq.fs:1465/1485/1517/1557 TensorPrimitives behind :? ('T[]) (net8) #if NET8_0_OR_GREATER 2–4× · inherits above; array-backed only

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

F# affected FSharp.Core file .NET API guard win
GenericEquality{Byte,Int32,Int64,Char}Array prim-types.fs:1481/1496/1558/1543 MemoryExtensions.SequenceEqual<'T> `#if NETSTANDARD2_1_OR_GREATER
GenericComparisonByteArray prim-types.fs:1230 MemoryExtensions.SequenceCompareTo<byte> `#if NETSTANDARD2_1_OR_GREATER

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

F# affected FSharp.Core file .NET API guard win
Array.contains (single primitive) array.fs:~488 MemoryExtensions.Contains / ReadOnlySpan.IndexOf `#if NETSTANDARD2_1_OR_GREATER
Array.rev array.fs:1386 System.Array.Reverse any TFM small

4 · Primitive-array hashing (net-only)

F# affected FSharp.Core file .NET API guard win
GenericHash{Byte,Int32,Int64}Array prim-types.fs:964/975/986 HashCode.AddBytes (net6) #if NET6_0_OR_GREATER 15–30% · keep in-process hash stability; 10-node cap

5 · String building (ns2.1+)

F# affected FSharp.Core file .NET API guard win
String.map / String.mapi / String.filter string.fs:57/71/89 String.Create(len, state, writer) `#if NETSTANDARD2_1_OR_GREATER

6 · Formatting / printf (mixed)

F# affected FSharp.Core file .NET API guard win
%O/%P/interpoland/user types/decimal/DateTime printf.fs:911–929 ObjectToString/InterpolandToString (+ span sink 1073–1083) ISpanFormattable.TryFormat (net6) + String.Create #if NET6_0_OR_GREATER skip 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–859 concrete Int32/Int64/Double/Single/Decimal.TryFormat into Span<char> `#if NETSTANDARD2_1_OR_GREATER
StringBuilder / TextWriter sinks printf.fs:1073–1083 StringBuilder.Append(ReadOnlySpan<char>) / TextWriter.Write(ReadOnlySpan<char>) `#if NETSTANDARD2_1_OR_GREATER
sprintf string sink printf.fs:1029–1071 pooled char[] (ArrayPool) + String.Create `#if NETSTANDARD2_1_OR_GREATER

7 · Generic math (net-only)

F# affected FSharp.Core file .NET API guard win
GenericZero/OneDynamicImplTable (Half) prim-types.fs:2667/2700 typeof<System.Half> fast case (net5) #if NET5_0_OR_GREATER avoid 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