Skip to content

Instantly share code, notes, and snippets.

@john-h-k
Created September 4, 2019 10:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save john-h-k/6afa22a904ed81c8e1c0e82b3e5b7600 to your computer and use it in GitHub Desktop.
Save john-h-k/6afa22a904ed81c8e1c0e82b3e5b7600 to your computer and use it in GitHub Desktop.
[CoreJob]
public class CopySignSingleBenchmark
{
public enum SignType
{
Random,
Same,
Different,
Alternating
}
private const int Seed = 98412310;
private const int Count = 32_768;
private float[] _dest;
public float[] Source1 { get; private set; }
public float[] Source2 { get; private set; }
[Params(SignType.Same, SignType.Different, SignType.Alternating, SignType.Random)]
public SignType Scenario { get; set; }
[GlobalSetup]
public void Setup()
{
Source1 = new float[Count];
Source2 = new float[Count];
_dest = new float[Count];
var rng = new Random(Seed);
for (int i = 0; i < Count; i++)
{
Source1[i] = (float)rng.NextDouble();
Source2[i] = (float)rng.NextDouble();
if (Scenario == SignType.Different)
{
Source2[i] = -Source2[i];
}
else if (Scenario == SignType.Alternating)
{
// Every other element should be inverted
if ((i % 2) == 0)
{
Source2[i] = -Source2[i];
}
}
else if (Scenario == SignType.Random)
{
var bits = BitConverter.SingleToInt32Bits(Source2[i]);
// Value generated is already random, so do a parity check
// to determine if we should invert the value or not
if ((BitOperations.PopCount((uint)bits) % 2) == 0)
{
Source2[i] = -Source2[i];
}
}
}
}
[Benchmark]
public void Standard()
{
for (var i = 0; i < Count; i++)
{
float x = Source1[i];
float y = Source2[i];
// This method is required to work for all inputs,
// including NaN, so we operate on the raw bits.
int xbits = BitConverter.SingleToInt32Bits(x);
int ybits = BitConverter.SingleToInt32Bits(y);
// If the sign bits of x and y are not the same,
// flip the sign bit of x and return the new value;
// otherwise, just return x
if ((xbits ^ ybits) < 0)
{
x = BitConverter.Int32BitsToSingle(xbits ^ int.MinValue);
}
_dest[i] = x;
}
}
[Benchmark]
public void John()
{
for (var i = 0; i < Count; i++)
{
float x = Source1[i];
float y = Source2[i];
// This method is required to work for all inputs,
// including NaN, so we operate on the raw bits.
int xbits = BitConverter.SingleToInt32Bits(x);
int ybits = BitConverter.SingleToInt32Bits(y);
// Remove the sign from x, and remove everything but the sign from y
const int signMask = unchecked((int)0b_1000_0000__0000_0000__0000_0000__0000_0000);
xbits &= ~signMask;
ybits &= signMask;
// Simply OR them to get the correct sign
x = BitConverter.Int32BitsToSingle(xbits | ybits);
_dest[i] = x;
}
}
[Benchmark]
public void John_Intrinsic()
{
for (var i = 0; i < Count; i++)
{
float x = Source1[i];
float y = Source2[i];
const int signMask = unchecked((int)0b_1000_0000__0000_0000__0000_0000__0000_0000);
if (Sse.IsSupported)
{
var xvec = Vector128.CreateScalarUnsafe(x);
var yvec = Vector128.CreateScalarUnsafe(y);
xvec = Sse.And(xvec, Vector128.CreateScalarUnsafe(~signMask).AsSingle());
yvec = Sse.And(yvec, Vector128.CreateScalarUnsafe(signMask).AsSingle());
x = Sse.Or(xvec, yvec).ToScalar();
}
else
{
// This method is required to work for all inputs,
// including NaN, so we operate on the raw bits.
int xbits = BitConverter.SingleToInt32Bits(x);
int ybits = BitConverter.SingleToInt32Bits(y);
// Remove the sign from x, and remove everything but the sign from y
xbits &= ~signMask;
ybits &= signMask;
// Simply OR them to get the correct sign
x = BitConverter.Int32BitsToSingle(xbits | ybits);
}
_dest[i] = x;
}
}
}
[CoreJob]
public class CopySignDoubleBenchmark
{
public enum SignType
{
Random,
Same,
Different,
Alternating
}
private const int Seed = 98412310;
private const int Count = 32_768;
private double[] _dest;
public double[] Source1 { get; private set; }
public double[] Source2 { get; private set; }
[Params(SignType.Same, SignType.Different, SignType.Alternating, SignType.Random)]
public SignType Scenario { get; set; }
[GlobalSetup]
public void Setup()
{
Source1 = new double[Count];
Source2 = new double[Count];
_dest = new double[Count];
var rng = new Random(Seed);
for (int i = 0; i < Count; i++)
{
Source1[i] = rng.NextDouble();
Source2[i] = rng.NextDouble();
if (Scenario == SignType.Different)
{
Source2[i] = -Source2[i];
}
else if (Scenario == SignType.Alternating)
{
// Every other element should be inverted
if ((i % 2) == 0)
{
Source2[i] = -Source2[i];
}
}
else if (Scenario == SignType.Random)
{
var bits = BitConverter.DoubleToInt64Bits(Source2[i]);
// Value generated is already random, so do a parity check
// to determine if we should invert the value or not
if ((BitOperations.PopCount((ulong)bits) % 2) == 0)
{
Source2[i] = -Source2[i];
}
}
}
}
[Benchmark]
public void Standard()
{
for (var i = 0; i < Count; i++)
{
double x = Source1[i];
double y = Source2[i];
// This method is required to work for all inputs,
// including NaN, so we operate on the raw bits.
long xbits = BitConverter.DoubleToInt64Bits(x);
long ybits = BitConverter.DoubleToInt64Bits(y);
// If the sign bits of x and y are not the same,
// flip the sign bit of x and return the new value;
// otherwise, just return x
if ((xbits ^ ybits) < 0)
{
x = BitConverter.Int64BitsToDouble(xbits ^ long.MinValue);
}
_dest[i] = x;
}
}
[Benchmark]
public void John()
{
for (var i = 0; i < Count; i++)
{
double x = Source1[i];
double y = Source2[i];
// This method is required to work for all inputs,
// including NaN, so we operate on the raw bits.
long xbits = BitConverter.DoubleToInt64Bits(x);
long ybits = BitConverter.DoubleToInt64Bits(y);
// Remove the sign from x, and remove everything but the sign from y
const long signMask
= unchecked((long)0b_1000_0000__0000_0000__0000_0000__0000_0000__0000_0000__0000_0000__0000_0000__0000_0000);
xbits &= ~signMask;
ybits &= signMask;
// Simply OR them to get the correct sign
x = BitConverter.Int64BitsToDouble(xbits | ybits);
_dest[i] = x;
}
}
[Benchmark]
public void John_Intrinsic()
{
for (var i = 0; i < Count; i++)
{
double x = Source1[i];
double y = Source2[i];
const long signMask
= unchecked((long)0b_1000_0000__0000_0000__0000_0000__0000_0000__0000_0000__0000_0000__0000_0000__0000_0000);
if (Sse.IsSupported)
{
var xvec = Vector128.CreateScalarUnsafe(x).AsSingle();
var yvec = Vector128.CreateScalarUnsafe(y).AsSingle();
xvec = Sse.And(xvec, Vector128.CreateScalarUnsafe(~signMask).AsSingle());
yvec = Sse.And(yvec, Vector128.CreateScalarUnsafe(signMask).AsSingle());
x = Sse.Or(xvec, yvec).AsDouble().ToScalar();
}
else
{
// This method is required to work for all inputs,
// including NaN, so we operate on the raw bits.
long xbits = BitConverter.DoubleToInt64Bits(x);
long ybits = BitConverter.DoubleToInt64Bits(y);
// Remove the sign from x, and remove everything but the sign from y
xbits &= ~signMask;
ybits &= signMask;
// Simply OR them to get the correct sign
x = BitConverter.Int64BitsToDouble(xbits | ybits);
}
_dest[i] = x;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment