Skip to content

[API Proposal]: Blackhole #104877

Open
Open
@timcassell

Description

@timcassell

Background and motivation

A JIT intrinsic API that does nothing. Similar to Java's Blackhole. Blackhole is used to prevent dead code elimination and out-of-order instructions generated by a compiler (not by the CPU, so it doesn't emit any barriers). The actual call does nothing, and is stripped out of the assembly.

The purpose of this API is to make it easier/possible to benchmark sensitive operations. Without this API, heavier alternatives are required like calling a method with NoInlining, or writing to a field (see Consumer class in BDN), which can spoil the results of the measurement. The cost of this has already been observed (#89940) (also credit to @EgorBo for this idea in that issue).

API Proposal

namespace System.Runtime.CompilerServices;

public static class Blackhole
{
    public static void Consume<T>(scoped T value) where T : allows ref struct;
    public static void Consume<T>(scoped ref readonly T value) where T : allows ref struct;
    public static unsafe void Consume(void* value);
}

API Usage

Old code:

public class HalfCast
{
    private readonly Consumer consumer = new();
    
    // 4 operations per invoke averages the cost of the 4 operations
    [Benchmark(OperationsPerInvoke = 4)]
    [Arguments(6.097555E-05f, 12345.0f, 65520.0f, float.NaN)]
    public void SingleToHalfAverage(float arg1, float arg2, float arg3, float arg4)
    {
        // Oh no, the consumer's volatile write interferes with the results!
        consumer.Consume((Half) arg1);
        consumer.Consume((Half) arg2);
        consumer.Consume((Half) arg3);
        consumer.Consume((Half) arg4);
    }
}

New code:

public class HalfCast
{
    // 4 operations per invoke averages the cost of the 4 operations
    [Benchmark(OperationsPerInvoke = 4)]
    [Arguments(6.097555E-05f, 12345.0f, 65520.0f, float.NaN)]
    public void SingleToHalfAverage(float arg1, float arg2, float arg3, float arg4)
    {
        // Yay, we get accurate results!
        Blackhole.Consume((Half) arg1);
        Blackhole.Consume((Half) arg2);
        Blackhole.Consume((Half) arg3);
        Blackhole.Consume((Half) arg4);
    }
}

Alternative Designs

Without any new APIs - Pass the value to a method with NoInlining, or write to a field. Both of these have some overhead.

Alternative new APIs (discussed in comments below):

namespace System.Runtime.CompilerServices;

public static class BlackBox
{
    public static T Identity<T>(scoped T value) where T : allows ref struct;
    public static ref readonly T Identity<T>(scoped ref readonly T value) where T : allows ref struct;
    public static unsafe void* Identity(void* value);
}

Risks

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    api-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-System.Runtime.CompilerServicesneeds-further-triageIssue has been initially triaged, but needs deeper consideration or reconsideration

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions