Repository: ignite Updated Branches: refs/heads/ignite-2542 e5763b8c4 -> 52e7d9de6
IGNITE-1759: .NET: Improved GUID handling for different platforms and endians. This closes #437. Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/f07adff7 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/f07adff7 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/f07adff7 Branch: refs/heads/ignite-2542 Commit: f07adff7bb601971bb6c83b0459113678f387592 Parents: e88cc67 Author: Pavel Tupitsyn <[email protected]> Authored: Mon Feb 8 10:30:57 2016 +0300 Committer: vozerov-gridgain <[email protected]> Committed: Mon Feb 8 10:30:57 2016 +0300 ---------------------------------------------------------------------- .../Binary/BinarySelfTest.cs | 32 ++++ .../Impl/Binary/BinaryUtils.cs | 166 ++++++++++++++++--- 2 files changed, 172 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/f07adff7/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs index 44db6f7..f49a28a 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Binary/BinarySelfTest.cs @@ -26,12 +26,15 @@ namespace Apache.Ignite.Core.Tests.Binary using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.IO; using System.Linq; using Apache.Ignite.Core.Binary; using Apache.Ignite.Core.Common; using Apache.Ignite.Core.Impl.Binary; using Apache.Ignite.Core.Impl.Binary.IO; using NUnit.Framework; + using BinaryReader = Apache.Ignite.Core.Impl.Binary.BinaryReader; + using BinaryWriter = Apache.Ignite.Core.Impl.Binary.BinaryWriter; /// <summary> /// @@ -476,6 +479,35 @@ namespace Apache.Ignite.Core.Tests.Binary Assert.AreEqual(vals, newVals); } + /// <summary> + /// Checks that both methods produce identical results. + /// </summary> + [Test] + public void TestGuidSlowFast() + { + var stream = new BinaryHeapStream(128); + + var guid = Guid.NewGuid(); + + BinaryUtils.WriteGuidFast(guid, stream); + + stream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual(guid, BinaryUtils.ReadGuidFast(stream)); + + stream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual(guid, BinaryUtils.ReadGuidSlow(stream)); + + + stream.Seek(0, SeekOrigin.Begin); + BinaryUtils.WriteGuidFast(guid, stream); + + stream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual(guid, BinaryUtils.ReadGuidFast(stream)); + + stream.Seek(0, SeekOrigin.Begin); + Assert.AreEqual(guid, BinaryUtils.ReadGuidSlow(stream)); + } + /** * <summary>Check write of enum.</summary> */ http://git-wip-us.apache.org/repos/asf/ignite/blob/f07adff7/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs index 06dec2c..9066bd1 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/BinaryUtils.cs @@ -243,6 +243,17 @@ namespace Apache.Ignite.Core.Impl.Binary private static readonly CopyOnWriteConcurrentDictionary<Type, Func<BinaryReader, bool, object>> ArrayReaders = new CopyOnWriteConcurrentDictionary<Type, Func<BinaryReader, bool, object>>(); + /** Flag indicating whether Guid struct is sequential in current runtime. */ + private static readonly bool IsGuidSequential = GetIsGuidSequential(); + + /** Guid writer. */ + public static readonly Action<Guid, IBinaryStream> WriteGuid = IsGuidSequential + ? (Action<Guid, IBinaryStream>)WriteGuidFast : WriteGuidSlow; + + /** Guid reader. */ + public static readonly Func<IBinaryStream, Guid?> ReadGuid = IsGuidSequential + ? (Func<IBinaryStream, Guid?>)ReadGuidFast : ReadGuidSlow; + /// <summary> /// Default marshaller. /// </summary> @@ -900,12 +911,33 @@ namespace Apache.Ignite.Core.Impl.Binary return vals; } - /** - * <summary>Write GUID.</summary> - * <param name="val">GUID.</param> - * <param name="stream">Stream.</param> - */ - public static unsafe void WriteGuid(Guid val, IBinaryStream stream) + /// <summary> + /// Gets a value indicating whether <see cref="Guid"/> fields are stored sequentially in memory. + /// </summary> + /// <returns></returns> + private static unsafe bool GetIsGuidSequential() + { + // Check that bitwise conversion returns correct result + var guid = Guid.NewGuid(); + + var bytes = guid.ToByteArray(); + + var bytes0 = (byte*) &guid; + + for (var i = 0; i < bytes.Length; i++) + if (bytes[i] != bytes0[i]) + return false; + + return true; + } + + /// <summary> + /// Writes a guid with bitwise conversion, assuming that <see cref="Guid"/> + /// is laid out in memory sequentially and without gaps between fields. + /// </summary> + /// <param name="val">The value.</param> + /// <param name="stream">The stream.</param> + public static unsafe void WriteGuidFast(Guid val, IBinaryStream stream) { var jguid = new JavaGuid(val); @@ -913,13 +945,47 @@ namespace Apache.Ignite.Core.Impl.Binary stream.Write((byte*) ptr, 16); } - - /** - * <summary>Read GUID.</summary> - * <param name="stream">Stream.</param> - * <returns>GUID</returns> - */ - public static unsafe Guid? ReadGuid(IBinaryStream stream) + + /// <summary> + /// Writes a guid byte by byte. + /// </summary> + /// <param name="val">The value.</param> + /// <param name="stream">The stream.</param> + public static unsafe void WriteGuidSlow(Guid val, IBinaryStream stream) + { + var bytes = val.ToByteArray(); + byte* jBytes = stackalloc byte[16]; + + jBytes[0] = bytes[6]; // c1 + jBytes[1] = bytes[7]; // c2 + + jBytes[2] = bytes[4]; // b1 + jBytes[3] = bytes[5]; // b2 + + jBytes[4] = bytes[0]; // a1 + jBytes[5] = bytes[1]; // a2 + jBytes[6] = bytes[2]; // a3 + jBytes[7] = bytes[3]; // a4 + + jBytes[8] = bytes[15]; // k + jBytes[9] = bytes[14]; // j + jBytes[10] = bytes[13]; // i + jBytes[11] = bytes[12]; // h + jBytes[12] = bytes[11]; // g + jBytes[13] = bytes[10]; // f + jBytes[14] = bytes[9]; // e + jBytes[15] = bytes[8]; // d + + stream.Write(jBytes, 16); + } + + /// <summary> + /// Reads a guid with bitwise conversion, assuming that <see cref="Guid"/> + /// is laid out in memory sequentially and without gaps between fields. + /// </summary> + /// <param name="stream">The stream.</param> + /// <returns>Guid.</returns> + public static unsafe Guid? ReadGuidFast(IBinaryStream stream) { JavaGuid jguid; @@ -931,7 +997,43 @@ namespace Apache.Ignite.Core.Impl.Binary return *(Guid*) (&dotnetGuid); } - + + /// <summary> + /// Reads a guid byte by byte. + /// </summary> + /// <param name="stream">The stream.</param> + /// <returns>Guid.</returns> + public static unsafe Guid? ReadGuidSlow(IBinaryStream stream) + { + byte* jBytes = stackalloc byte[16]; + + stream.Read(jBytes, 16); + + var bytes = new byte[16]; + + bytes[0] = jBytes[4]; // a1 + bytes[1] = jBytes[5]; // a2 + bytes[2] = jBytes[6]; // a3 + bytes[3] = jBytes[7]; // a4 + + bytes[4] = jBytes[2]; // b1 + bytes[5] = jBytes[3]; // b2 + + bytes[6] = jBytes[0]; // c1 + bytes[7] = jBytes[1]; // c2 + + bytes[8] = jBytes[15]; // d + bytes[9] = jBytes[14]; // e + bytes[10] = jBytes[13]; // f + bytes[11] = jBytes[12]; // g + bytes[12] = jBytes[11]; // h + bytes[13] = jBytes[10]; // i + bytes[14] = jBytes[9]; // j + bytes[15] = jBytes[8]; // k + + return new Guid(bytes); + } + /// <summary> /// Write GUID array. /// </summary> @@ -1689,7 +1791,7 @@ namespace Apache.Ignite.Core.Impl.Binary private struct GuidAccessor { public readonly ulong ABC; - public readonly ulong DEGHIJK; + public readonly ulong DEFGHIJK; /// <summary> /// Initializes a new instance of the <see cref="GuidAccessor"/> struct. @@ -1699,21 +1801,28 @@ namespace Apache.Ignite.Core.Impl.Binary { var l = val.CBA; - ABC = ((l >> 32) & 0x00000000FFFFFFFF) | ((l << 48) & 0xFFFF000000000000) | - ((l << 16) & 0x0000FFFF00000000); + if (BitConverter.IsLittleEndian) + ABC = ((l >> 32) & 0x00000000FFFFFFFF) | ((l << 48) & 0xFFFF000000000000) | + ((l << 16) & 0x0000FFFF00000000); + else + ABC = ((l << 32) & 0xFFFFFFFF00000000) | ((l >> 48) & 0x000000000000FFFF) | + ((l >> 16) & 0x00000000FFFF0000); - DEGHIJK = ReverseByteOrder(val.KJIHGED); + // This is valid in any endianness (symmetrical) + DEFGHIJK = ReverseByteOrder(val.KJIHGFED); } } /// <summary> /// Struct with Java-style Guid memory layout. /// </summary> - [StructLayout(LayoutKind.Sequential, Pack = 0)] + [StructLayout(LayoutKind.Explicit)] private struct JavaGuid { - public readonly ulong CBA; - public readonly ulong KJIHGED; + [FieldOffset(0)] public readonly ulong CBA; + [FieldOffset(8)] public readonly ulong KJIHGFED; + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] + [FieldOffset(0)] public unsafe fixed byte Bytes [16]; /// <summary> /// Initializes a new instance of the <see cref="JavaGuid"/> struct. @@ -1721,17 +1830,22 @@ namespace Apache.Ignite.Core.Impl.Binary /// <param name="val">The value.</param> public unsafe JavaGuid(Guid val) { - // .Net returns bytes in the following order: _a(4), _b(2), _c(2), _d, _e, _g, _h, _i, _j, _k. + // .Net returns bytes in the following order: _a(4), _b(2), _c(2), _d, _e, _f, _g, _h, _i, _j, _k. // And _a, _b and _c are always in little endian format irrespective of system configuration. - // To be compliant with Java we rearrange them as follows: _c, _b_, a_, _k, _j, _i, _h, _g, _e, _d. + // To be compliant with Java we rearrange them as follows: _c, _b_, a_, _k, _j, _i, _h, _g, _f, _e, _d. var accessor = *((GuidAccessor*)&val); var l = accessor.ABC; - CBA = ((l << 32) & 0xFFFFFFFF00000000) | ((l >> 48) & 0x000000000000FFFF) | - ((l >> 16) & 0x00000000FFFF0000); + if (BitConverter.IsLittleEndian) + CBA = ((l << 32) & 0xFFFFFFFF00000000) | ((l >> 48) & 0x000000000000FFFF) | + ((l >> 16) & 0x00000000FFFF0000); + else + CBA = ((l >> 32) & 0x00000000FFFFFFFF) | ((l << 48) & 0xFFFF000000000000) | + ((l << 16) & 0x0000FFFF00000000); - KJIHGED = ReverseByteOrder(accessor.DEGHIJK); + // This is valid in any endianness (symmetrical) + KJIHGFED = ReverseByteOrder(accessor.DEFGHIJK); } } }
