Skip to content

[SIMD] Unary Ops: Add Floor/Ceil/Round and Transcendental Vectorization #577

Closed

Description

@Nucs

Overview

Extend the IL kernel generator's SIMD unary operation support beyond the current Negate/Abs/Sqrt.

Parent issue: #545

Current State

Operation SIMD Status Implementation
Negate ✅ SIMD Vector256.op_UnaryNegation
Abs ✅ SIMD Vector256.Abs()
Sqrt ✅ SIMD Vector256.Sqrt()
Floor ❌ Scalar Math.Floor per-element
Ceil ❌ Scalar Math.Ceiling per-element
Round ❌ Scalar Math.Round per-element
Exp ❌ Scalar Math.Exp per-element
Log ❌ Scalar Math.Log per-element
Sin/Cos/Tan ❌ Scalar Math.Sin/Cos/Tan per-element

SIMD eligibility check: ILKernelGenerator.cs:2086
Vector operation dispatch: ILKernelGenerator.cs:2980-3014

Task List

Tier 1: Quick Wins (Vector256 methods exist in .NET)

  • SIMD Floor

    • .NET has Vector256.Floor()
    • Add to CanUseUnarySimd() eligibility
    • Add to EmitUnaryVectorOperation() dispatch
    • Expected: 2× speedup
  • SIMD Ceiling

    • .NET has Vector256.Ceiling()
    • Same implementation pattern as Floor
    • Expected: 2× speedup
  • SIMD Truncate

    • .NET has Vector256.Truncate()
    • Same implementation pattern
    • Expected: 2× speedup

Tier 2: Medium Effort

  • SIMD Round
    • May need Vector256.Round() or composition
    • Check .NET 8+ availability
    • Expected: 1.5-2× speedup

Tier 3: Transcendentals (Complex)

  • SIMD Exp/Log (research)

    • No Vector256.Exp() in .NET BCL
    • Options:
      1. Polynomial approximation (Remez/minimax)
      2. External library (MathNet.Numerics)
      3. P/Invoke to Intel SVML
      4. Wait for .NET Tensor primitives
    • Expected: 2-4× speedup if implemented
  • SIMD Sin/Cos/Tan (research)

    • Same challenge as Exp/Log
    • Range reduction + polynomial approximation
    • More complex due to periodicity

Implementation Details

Floor/Ceil (Tier 1)

// In CanUseUnarySimd (line ~2086), add:
|| key.Op == UnaryOp.Floor
|| key.Op == UnaryOp.Ceil

// In EmitUnaryVectorOperation (line ~2980), add:
case UnaryOp.Floor:
    var floorMethod = typeof(Vector256).GetMethod("Floor", 
        new[] { typeof(Vector256<>).MakeGenericType(GetClrType(type)) });
    il.EmitCall(OpCodes.Call, floorMethod, null);
    break;
    
case UnaryOp.Ceil:
    var ceilMethod = typeof(Vector256).GetMethod("Ceiling", 
        new[] { typeof(Vector256<>).MakeGenericType(GetClrType(type)) });
    il.EmitCall(OpCodes.Call, ceilMethod, null);
    break;

Transcendentals (Tier 3) - Research Notes

Exp approximation approach:

// Exp(x) via range reduction + polynomial
// 1. Clamp x to avoid overflow
// 2. n = round(x / ln2), r = x - n*ln2
// 3. exp(r) ≈ polynomial (|r| < ln2/2)
// 4. result = 2^n * exp(r)
public static Vector256<float> Exp(Vector256<float> x)
{
    var ln2 = Vector256.Create(0.693147180559945f);
    var invLn2 = Vector256.Create(1.44269504088896f);
    // ... polynomial coefficients ...
}

Files to Modify

File Changes
ILKernelGenerator.cs:2086 Add Floor/Ceil/Round to eligibility
ILKernelGenerator.cs:2980 Add vector operation dispatch
SimdKernels.cs (optional) C# fallback implementations

Benchmarks

[Benchmark] public NDArray Floor_10M() => np.floor(_array);
[Benchmark] public NDArray Ceil_10M() => np.ceil(_array);
[Benchmark] public NDArray Exp_10M() => np.exp(_array);
[Benchmark] public NDArray Sin_10M() => np.sin(_array);

NumPy Baseline (10M float64)

Operation NumPy Time
np.floor ~8 ms
np.ceil ~8 ms
np.exp ~20 ms
np.sin ~50 ms

Success Criteria

  1. Floor/Ceil SIMD: ≥1.5× faster than current scalar
  2. All existing unary tests pass
  3. No accuracy regression vs scalar implementation

Activity

  1. added
    enhancementNew feature or request
    performancePerformance improvements or optimizations
    coreInternal engine: Shape, Storage, TensorEngine, iterators
    on Feb 21, 2026
  2. Nucs commented on Mar 14, 2026

    @Nucs
    MemberAuthor

    Analysis: SIMD Transcendentals Feasibility

    After thorough investigation, here's the assessment:

    Current State

    • Transcendentals (Sin, Cos, Tan, Exp, Log, Pow, etc.) use scalar Math.Sin/MathF.Sin calls
    • Each element is processed individually in a loop
    • The JIT does optimize these scalar calls, but no vectorization

    The Core Problem

    .NET does NOT provide vectorized transcendental functions.

    Vector256<float> has: Add, Multiply, Min, Max, Abs, Sqrt, Floor, Ceiling
    Vector256<float> does NOT have: Sin, Cos, Tan, Exp, Log, Pow

    Available Options

    Option Pros Cons
    Intel SVML Very fast (4-8x), Intel-optimized x86/x64 only, native interop, proprietary
    Polynomial approx Pure .NET, cross-platform Accuracy risk, significant effort
    Intel MKL/VML Optimized, supports SIMD +100MB dependency, licensing
    Wait for .NET Clean integration Uncertain timeline

    NumPy's Approach

    NumPy uses Intel SVML on x86 (see numpy/_core/src/umath/svml/). This is why NumPy is fast for transcendentals - but it's x86-only and adds native dependencies.

    Recommendation: Defer / Close as Won't Fix

    Reasons:

    1. No built-in .NET solution exists
    2. Adding native dependencies (SVML/MKL) breaks cross-platform purity
    3. Polynomial approximations require extensive accuracy validation
    4. Current scalar Math.Sin() is already JIT-optimized
    5. Most NumSharp use cases don't bottleneck on transcendentals

    The effort-to-benefit ratio is unfavorable - significant implementation work for a feature that would either:

    • Only work on x86 (SVML)
    • Add large dependencies (MKL)
    • Risk accuracy issues (polynomials)

    Future Possibilities

    • .NET may add Vector.Sin() etc. in future versions
    • If added, implementation would be straightforward using existing IL infrastructure

    Marking as deferred/won't fix for now. The PR #573 can proceed without this.

  3. Nucs commented on Sep 7, 2026

    @Nucs
    MemberAuthor

    Shipped in 0.70.0 (#628). Floor/ceil/trunc/round are vectorized (bit-level AVX2 for float16, SIMD for float32/float64), and the float32 transcendentals (exp/log/sin/cos/tanh/exp2) are bit-exact NumPy kernel ports:

    Console.WriteLine(np.floor(np.array(new[] {1.5f, 2.5f, -1.5f})));   // [ 1.  2. -2.]
    Console.WriteLine(np.ceil(np.array(new[] {1.1f, 2.9f})));           // [2. 3.]
    Console.WriteLine(np.around(np.array(new[] {1.5, 2.5, 3.5})));      // [2. 2. 4.]  (half-to-even)

    Closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    coreInternal engine: Shape, Storage, TensorEngine, iteratorsenhancementNew feature or requestperformancePerformance improvements or optimizations

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions