19

I'm currently looking at porting my metro hash implementon to use C#7 features, as several parts might profit from ref locals to improve performance. The hash does the calculations on a ulong[4] array, but the result is a 16 byte array. Currently I'm copying the ulong array to the result byte buffer, but this takes a bit of time. So i'm wondering if System.Runtime.CompilerServices.Unsafe is safe to use here:

var result = new byte[16];
ulong[] state = Unsafe.As<byte[], ulong[]>(ref result);
ref var firstState = ref state[0];
ref var secondState = ref state[1];
ulong thirdState = 0;
ulong fourthState = 0;

The above code snippet means that I'm using the result buffer also for parts of my state calculations and not only for the final output.

My unit tests are successful and according to benchmarkdotnet skipping the block copy would result in a 20% performance increase, which is high enough for me to find out if it is correct to use it.

5
  • Welcome here and good first question! Commented Mar 9, 2017 at 12:19
  • 3
    What you are doing is the old "trick" of casting through a struct (this one stackoverflow.com/a/35841815/613130)... If you check the state.Length you'll see that it is "wrong". Commented Mar 9, 2017 at 12:19
  • 2
    Still very interesting library you have found :-) Commented Mar 9, 2017 at 12:35
  • Thank you for answer, I know that explicit struct trick but actually didn't make the connection that it's actually the same as Unsafe.As<>. Commented Mar 9, 2017 at 13:16
  • 1
    @Tornhoof The Unsafe.As<> doesn't need that trick, but in the end it does the same thing. It reinterprets what is passed as the parameter to another type. In ILAsm it is very easy: ldarg.0; ret :-) Nothing to be done :-) Commented Mar 9, 2017 at 13:21

3 Answers 3

13

In current .NET terms, this would be a good fit for Span<T>:

Span<byte> result = new byte[16];
Span<ulong> state = MemoryMarshal.Cast<byte, ulong>(result);

This enforces lengths etc, while having good JIT behaviour and not requiring unsafe. You can even stackalloc the original buffer (from C# 7.2 onwards):

Span<byte> result = stackalloc byte[16];
Span<ulong> state = MemoryMarshal.Cast<byte, ulong>(result);

Note that Span<T> gets the length change correct; it is also trivial to cast into a Span<Vector<T>> if you want to use SIMD for hardware acceleration.

Sign up to request clarification or add additional context in comments.

Comments

1

What you're doing seems fine, just be careful because there's nothing to stop you from doing this:

byte[] x = new byte[16];
long[] y = Unsafe.As<byte[], long[]>(ref x);

Console.WriteLine(y.Length); // still 16

for (int i = 0; i < y.Length; i++)
    Console.WriteLine(y[i]); // reads random memory from your program, could cause crash

2 Comments

re the length problem: Span<T> could help here - see my answer
@MarcGravell Yeah that's the safer option, though I believe a touch slower because of the length conversions and such. I haven't benchmarked the difference though. As long as he's careful he might be able to eek a touch more perf out of the way he's doing it now. I'm completely in love the new span/memory/unsafe stuff, it makes me want to go and rewrite everything.
0

C# supports "fixed buffers", here's the kind of thing we can do:

    public unsafe struct Bytes
    {
        public fixed byte bytes[16];
    }

then

    public unsafe static Bytes Convert (long[] longs)
    {
        fixed (long * longs_ptr = longs)
              return *((Bytes*)(longs_ptr));
    }

Try it. (1D arrays of primitive types in C# are always stored as a contiguous block of memory which is why taking the address of the (managed) arrays is fine).

You could also even return the pointer for more speed:

    public unsafe static Bytes * Convert (long[] longs)
    {
        fixed (long * longs_ptr = longs)
        return ((Bytes*)(longs_ptr));
    }

and manipulate/access the bytes as you want.

        var s = Convert(longs);
        var b = s->bytes[0];

1 Comment

"You could also" - no! don't do that; as soon as you leave the fixed block, the pointer is unreliable; in reality it will rarely move, but: it can (GC); never pass a pointer outwards from a fixed block (inwards is fine)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.