Description
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