From 0cdacdfb354bec1832bb525e1394754debbf6195 Mon Sep 17 00:00:00 2001 From: Eric Erhardt Date: Thu, 21 Feb 2019 16:36:55 -0600 Subject: [PATCH] Remove byte* property in ByteBufferAllocator (#5191) * Remove byte* property in ByteBufferAllocator. This allows consumers to read/write into native memory, but without having to always pin the managed `byte[]` when working with managed memory. This allows for users to not need to Dispose() ByteBuffers when they are using the default ByteArrayAllocator class. Instead, we use `Span GetSpan()` methods to get access to the underlying memory buffer. Fix #5181 * Add a set of benchmark tests. * Add ReadOnly spans. This allows consumers to use ReadOnlyMemory as the backing storage for ByteBuffers, which is useful in read-only scenarios. * Run tests using ENABLE_SPAN_T in appveyor. * Fix FlatBuffers.Test.csproj to work on older MSBuild versions. * Change the test script to test UNSAFE_BYTEBUFFER * Address PR feedback. Remove IDisposable from ByteBuffer. * Respond to PR feedback. --- appveyor.yml | 3 + net/FlatBuffers/ByteBuffer.cs | 315 +++++++++++---------- .../FlatBufferBuilderBenchmark.cs | 101 +++++++ .../FlatBuffers.Benchmarks.csproj | 21 ++ tests/FlatBuffers.Benchmarks/Program.cs | 30 ++ tests/FlatBuffers.Test/FlatBuffers.Test.csproj | 4 + 6 files changed, 322 insertions(+), 152 deletions(-) create mode 100644 tests/FlatBuffers.Benchmarks/FlatBufferBuilderBenchmark.cs create mode 100644 tests/FlatBuffers.Benchmarks/FlatBuffers.Benchmarks.csproj create mode 100644 tests/FlatBuffers.Benchmarks/Program.cs diff --git a/appveyor.yml b/appveyor.yml index 93ed5ec..75a63c8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -92,6 +92,9 @@ test_script: - "cd FlatBuffers.Test" - "msbuild.exe /property:Configuration=Release;OutputPath=tempcs /verbosity:minimal FlatBuffers.Test.csproj" - "tempcs\\FlatBuffers.Test.exe" + # Run tests with UNSAFE_BYTEBUFFER + - "msbuild.exe /property:Configuration=Release;UnsafeByteBuffer=true;OutputPath=tempcsUnsafe /verbosity:minimal FlatBuffers.Test.csproj" + - "tempcsUnsafe\\FlatBuffers.Test.exe" # TODO: add more languages. - "cd ..\\.." diff --git a/net/FlatBuffers/ByteBuffer.cs b/net/FlatBuffers/ByteBuffer.cs index 1b2e1af..5e212dd 100644 --- a/net/FlatBuffers/ByteBuffer.cs +++ b/net/FlatBuffers/ByteBuffer.cs @@ -42,20 +42,24 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; +#if ENABLE_SPAN_T +using System.Buffers.Binary; +#endif + #if ENABLE_SPAN_T && !UNSAFE_BYTEBUFFER #error ENABLE_SPAN_T requires UNSAFE_BYTEBUFFER to also be defined #endif namespace FlatBuffers { - public abstract class ByteBufferAllocator : IDisposable + public abstract class ByteBufferAllocator { -#if UNSAFE_BYTEBUFFER - public unsafe byte* Buffer - { - get; - protected set; - } +#if ENABLE_SPAN_T + public abstract Span Span { get; } + public abstract ReadOnlySpan ReadOnlySpan { get; } + public abstract Memory Memory { get; } + public abstract ReadOnlyMemory ReadOnlyMemory { get; } + #else public byte[] Buffer { @@ -70,23 +74,17 @@ namespace FlatBuffers protected set; } - public abstract void Dispose(); - public abstract void GrowFront(int newSize); - -#if !ENABLE_SPAN_T - public abstract byte[] ByteArray { get; } -#endif } - public class ByteArrayAllocator : ByteBufferAllocator + public sealed class ByteArrayAllocator : ByteBufferAllocator { private byte[] _buffer; public ByteArrayAllocator(byte[] buffer) { _buffer = buffer; - InitPointer(); + InitBuffer(); } public override void GrowFront(int newSize) @@ -101,63 +99,29 @@ namespace FlatBuffers byte[] newBuffer = new byte[newSize]; System.Buffer.BlockCopy(_buffer, 0, newBuffer, newSize - Length, Length); _buffer = newBuffer; - InitPointer(); + InitBuffer(); } - public override void Dispose() - { - GC.SuppressFinalize(this); -#if UNSAFE_BYTEBUFFER - if (_handle.IsAllocated) - { - _handle.Free(); - } -#endif - } - -#if !ENABLE_SPAN_T - public override byte[] ByteArray - { - get { return _buffer; } - } -#endif - -#if UNSAFE_BYTEBUFFER - private GCHandle _handle; - - ~ByteArrayAllocator() - { - if (_handle.IsAllocated) - { - _handle.Free(); - } - } +#if ENABLE_SPAN_T + public override Span Span => _buffer; + public override ReadOnlySpan ReadOnlySpan => _buffer; + public override Memory Memory => _buffer; + public override ReadOnlyMemory ReadOnlyMemory => _buffer; #endif - private void InitPointer() + private void InitBuffer() { Length = _buffer.Length; -#if UNSAFE_BYTEBUFFER - if (_handle.IsAllocated) - { - _handle.Free(); - } - _handle = GCHandle.Alloc(_buffer, GCHandleType.Pinned); - unsafe - { - Buffer = (byte*)_handle.AddrOfPinnedObject().ToPointer(); - } -#else +#if !ENABLE_SPAN_T Buffer = _buffer; #endif } } - /// /// Class to mimic Java's ByteBuffer which is used heavily in Flatbuffers. /// - public class ByteBuffer : IDisposable + public class ByteBuffer { private ByteBufferAllocator _buffer; private int _pos; // Must track start of the buffer. @@ -178,15 +142,8 @@ namespace FlatBuffers _pos = pos; } - public void Dispose() + public int Position { - if (_buffer != null) - { - _buffer.Dispose(); - } - } - - public int Position { get { return _pos; } set { _pos = value; } } @@ -278,16 +235,10 @@ namespace FlatBuffers // the buffer position and length. #if ENABLE_SPAN_T public T[] ToArray(int pos, int len) - where T: struct + where T : struct { - unsafe - { - AssertOffsetAndLength(pos, len); - T[] arr = new T[len]; - var typed = MemoryMarshal.Cast(new Span(_buffer.Buffer + pos, _buffer.Length)); - typed.Slice(0, arr.Length).CopyTo(arr); - return arr; - } + AssertOffsetAndLength(pos, len); + return MemoryMarshal.Cast(_buffer.ReadOnlySpan.Slice(pos)).Slice(0, len).ToArray(); } #else public T[] ToArray(int pos, int len) @@ -295,7 +246,7 @@ namespace FlatBuffers { AssertOffsetAndLength(pos, len); T[] arr = new T[len]; - Buffer.BlockCopy(_buffer.ByteArray, pos, arr, 0, ArraySize(arr)); + Buffer.BlockCopy(_buffer.Buffer, pos, arr, 0, ArraySize(arr)); return arr; } #endif @@ -310,23 +261,30 @@ namespace FlatBuffers return ToArray(0, Length); } - #if ENABLE_SPAN_T - public unsafe Span ToSpan(int pos, int len) + public ReadOnlyMemory ToReadOnlyMemory(int pos, int len) + { + return _buffer.ReadOnlyMemory.Slice(pos, len); + } + + public Memory ToMemory(int pos, int len) + { + return _buffer.Memory.Slice(pos, len); + } + + public Span ToSpan(int pos, int len) { - return new Span(_buffer.Buffer, _buffer.Length).Slice(pos, len); + return _buffer.Span.Slice(pos, len); } #else public ArraySegment ToArraySegment(int pos, int len) { - return new ArraySegment(_buffer.ByteArray, pos, len); + return new ArraySegment(_buffer.Buffer, pos, len); } -#endif -#if !ENABLE_SPAN_T public MemoryStream ToMemoryStream(int pos, int len) { - return new MemoryStream(_buffer.ByteArray, pos, len); + return new MemoryStream(_buffer.Buffer, pos, len); } #endif @@ -391,15 +349,15 @@ namespace FlatBuffers { for (int i = 0; i < count; i++) { - r |= (ulong)_buffer.Buffer[offset + i] << i * 8; + r |= (ulong)_buffer.Buffer[offset + i] << i * 8; } } else { - for (int i = 0; i < count; i++) - { - r |= (ulong)_buffer.Buffer[offset + count - 1 - i] << i * 8; - } + for (int i = 0; i < count; i++) + { + r |= (ulong)_buffer.Buffer[offset + count - 1 - i] << i * 8; + } } return r; } @@ -414,31 +372,26 @@ namespace FlatBuffers #endif } -#if UNSAFE_BYTEBUFFER +#if ENABLE_SPAN_T - public unsafe void PutSbyte(int offset, sbyte value) + public void PutSbyte(int offset, sbyte value) { AssertOffsetAndLength(offset, sizeof(sbyte)); - _buffer.Buffer[offset] = (byte)value; + _buffer.Span[offset] = (byte)value; } - public unsafe void PutByte(int offset, byte value) + public void PutByte(int offset, byte value) { AssertOffsetAndLength(offset, sizeof(byte)); - _buffer.Buffer[offset] = value; + _buffer.Span[offset] = value; } - public unsafe void PutByte(int offset, byte value, int count) + public void PutByte(int offset, byte value, int count) { AssertOffsetAndLength(offset, sizeof(byte) * count); - for (var i = 0; i < count; ++i) - _buffer.Buffer[offset + i] = value; - } - - // this method exists in order to conform with Java ByteBuffer standards - public void Put(int offset, byte value) - { - PutByte(offset, value); + Span span = _buffer.Span.Slice(offset, count); + for (var i = 0; i < span.Length; ++i) + span[i] = value; } #else public void PutSbyte(int offset, sbyte value) @@ -459,13 +412,13 @@ namespace FlatBuffers for (var i = 0; i < count; ++i) _buffer.Buffer[offset + i] = value; } +#endif // this method exists in order to conform with Java ByteBuffer standards public void Put(int offset, byte value) { PutByte(offset, value); } -#endif #if ENABLE_SPAN_T public unsafe void PutStringUTF8(int offset, string value) @@ -473,7 +426,10 @@ namespace FlatBuffers AssertOffsetAndLength(offset, value.Length); fixed (char* s = value) { - Encoding.UTF8.GetBytes(s, value.Length, _buffer.Buffer + offset, Length - offset); + fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.Span)) + { + Encoding.UTF8.GetBytes(s, value.Length, buffer + offset, Length - offset); + } } } #else @@ -481,7 +437,7 @@ namespace FlatBuffers { AssertOffsetAndLength(offset, value.Length); Encoding.UTF8.GetBytes(value, 0, value.Length, - _buffer.ByteArray, offset); + _buffer.Buffer, offset); } #endif @@ -495,10 +451,17 @@ namespace FlatBuffers public unsafe void PutUshort(int offset, ushort value) { AssertOffsetAndLength(offset, sizeof(ushort)); - byte* ptr = _buffer.Buffer; - *(ushort*)(ptr + offset) = BitConverter.IsLittleEndian - ? value - : ReverseBytes(value); +#if ENABLE_SPAN_T + Span span = _buffer.Span.Slice(offset); + BinaryPrimitives.WriteUInt16LittleEndian(span, value); +#else + fixed (byte* ptr = _buffer.Buffer) + { + *(ushort*)(ptr + offset) = BitConverter.IsLittleEndian + ? value + : ReverseBytes(value); + } +#endif } public void PutInt(int offset, int value) @@ -509,10 +472,17 @@ namespace FlatBuffers public unsafe void PutUint(int offset, uint value) { AssertOffsetAndLength(offset, sizeof(uint)); - byte* ptr = _buffer.Buffer; - *(uint*)(ptr + offset) = BitConverter.IsLittleEndian - ? value - : ReverseBytes(value); +#if ENABLE_SPAN_T + Span span = _buffer.Span.Slice(offset); + BinaryPrimitives.WriteUInt32LittleEndian(span, value); +#else + fixed (byte* ptr = _buffer.Buffer) + { + *(uint*)(ptr + offset) = BitConverter.IsLittleEndian + ? value + : ReverseBytes(value); + } +#endif } public unsafe void PutLong(int offset, long value) @@ -523,38 +493,56 @@ namespace FlatBuffers public unsafe void PutUlong(int offset, ulong value) { AssertOffsetAndLength(offset, sizeof(ulong)); - byte* ptr = _buffer.Buffer; - *(ulong*)(ptr + offset) = BitConverter.IsLittleEndian - ? value - : ReverseBytes(value); +#if ENABLE_SPAN_T + Span span = _buffer.Span.Slice(offset); + BinaryPrimitives.WriteUInt64LittleEndian(span, value); +#else + fixed (byte* ptr = _buffer.Buffer) + { + *(ulong*)(ptr + offset) = BitConverter.IsLittleEndian + ? value + : ReverseBytes(value); + } +#endif } public unsafe void PutFloat(int offset, float value) { AssertOffsetAndLength(offset, sizeof(float)); - byte* ptr = _buffer.Buffer; - if (BitConverter.IsLittleEndian) - { - *(float*)(ptr + offset) = value; - } - else +#if ENABLE_SPAN_T + fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span)) +#else + fixed (byte* ptr = _buffer.Buffer) +#endif { - *(uint*)(ptr + offset) = ReverseBytes(*(uint*)(&value)); + if (BitConverter.IsLittleEndian) + { + *(float*)(ptr + offset) = value; + } + else + { + *(uint*)(ptr + offset) = ReverseBytes(*(uint*)(&value)); + } } } public unsafe void PutDouble(int offset, double value) { AssertOffsetAndLength(offset, sizeof(double)); - byte* ptr = _buffer.Buffer; - if (BitConverter.IsLittleEndian) - { - *(double*)(ptr + offset) = value; - - } - else +#if ENABLE_SPAN_T + fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.Span)) +#else + fixed (byte* ptr = _buffer.Buffer) +#endif { - *(ulong*)(ptr + offset) = ReverseBytes(*(ulong*)(&value)); + if (BitConverter.IsLittleEndian) + { + *(double*)(ptr + offset) = value; + } + else + { + *(ulong*)(ptr + offset) = ReverseBytes(*(ulong*)(&value)); + } } } #else // !UNSAFE_BYTEBUFFER @@ -613,17 +601,17 @@ namespace FlatBuffers #endif // UNSAFE_BYTEBUFFER -#if UNSAFE_BYTEBUFFER - public unsafe sbyte GetSbyte(int index) +#if ENABLE_SPAN_T + public sbyte GetSbyte(int index) { AssertOffsetAndLength(index, sizeof(sbyte)); - return (sbyte)_buffer.Buffer[index]; + return (sbyte)_buffer.ReadOnlySpan[index]; } - public unsafe byte Get(int index) + public byte Get(int index) { AssertOffsetAndLength(index, sizeof(byte)); - return _buffer.Buffer[index]; + return _buffer.ReadOnlySpan[index]; } #else public sbyte GetSbyte(int index) @@ -642,12 +630,15 @@ namespace FlatBuffers #if ENABLE_SPAN_T public unsafe string GetStringUTF8(int startPos, int len) { - return Encoding.UTF8.GetString(_buffer.Buffer + startPos, len); + fixed (byte* buffer = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan.Slice(startPos))) + { + return Encoding.UTF8.GetString(buffer, len); + } } #else public string GetStringUTF8(int startPos, int len) { - return Encoding.UTF8.GetString(_buffer.ByteArray, startPos, len); + return Encoding.UTF8.GetString(_buffer.Buffer, startPos, len); } #endif @@ -661,12 +652,17 @@ namespace FlatBuffers public unsafe ushort GetUshort(int offset) { AssertOffsetAndLength(offset, sizeof(ushort)); - byte* ptr = _buffer.Buffer; +#if ENABLE_SPAN_T + ReadOnlySpan span = _buffer.ReadOnlySpan.Slice(offset); + return BinaryPrimitives.ReadUInt16LittleEndian(span); +#else + fixed (byte* ptr = _buffer.Buffer) { return BitConverter.IsLittleEndian ? *(ushort*)(ptr + offset) : ReverseBytes(*(ushort*)(ptr + offset)); } +#endif } public int GetInt(int offset) @@ -677,12 +673,17 @@ namespace FlatBuffers public unsafe uint GetUint(int offset) { AssertOffsetAndLength(offset, sizeof(uint)); - byte* ptr = _buffer.Buffer; +#if ENABLE_SPAN_T + ReadOnlySpan span = _buffer.ReadOnlySpan.Slice(offset); + return BinaryPrimitives.ReadUInt32LittleEndian(span); +#else + fixed (byte* ptr = _buffer.Buffer) { return BitConverter.IsLittleEndian ? *(uint*)(ptr + offset) : ReverseBytes(*(uint*)(ptr + offset)); } +#endif } public long GetLong(int offset) @@ -693,18 +694,27 @@ namespace FlatBuffers public unsafe ulong GetUlong(int offset) { AssertOffsetAndLength(offset, sizeof(ulong)); - byte* ptr = _buffer.Buffer; +#if ENABLE_SPAN_T + ReadOnlySpan span = _buffer.ReadOnlySpan.Slice(offset); + return BinaryPrimitives.ReadUInt64LittleEndian(span); +#else + fixed (byte* ptr = _buffer.Buffer) { return BitConverter.IsLittleEndian ? *(ulong*)(ptr + offset) : ReverseBytes(*(ulong*)(ptr + offset)); } +#endif } public unsafe float GetFloat(int offset) { AssertOffsetAndLength(offset, sizeof(float)); - byte* ptr = _buffer.Buffer; +#if ENABLE_SPAN_T + fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan)) +#else + fixed (byte* ptr = _buffer.Buffer) +#endif { if (BitConverter.IsLittleEndian) { @@ -721,7 +731,11 @@ namespace FlatBuffers public unsafe double GetDouble(int offset) { AssertOffsetAndLength(offset, sizeof(double)); - byte* ptr = _buffer.Buffer; +#if ENABLE_SPAN_T + fixed (byte* ptr = &MemoryMarshal.GetReference(_buffer.ReadOnlySpan)) +#else + fixed (byte* ptr = _buffer.Buffer) +#endif { if (BitConverter.IsLittleEndian) { @@ -758,7 +772,7 @@ namespace FlatBuffers public long GetLong(int index) { - return (long)ReadLittleEndian(index, sizeof(long)); + return (long)ReadLittleEndian(index, sizeof(long)); } public ulong GetUlong(int index) @@ -819,12 +833,9 @@ namespace FlatBuffers AssertOffsetAndLength(offset, numBytes); // if we are LE, just do a block copy #if ENABLE_SPAN_T - unsafe - { - MemoryMarshal.Cast(x).CopyTo(new Span(_buffer.Buffer, _buffer.Length).Slice(offset, numBytes)); - } + MemoryMarshal.Cast(x).CopyTo(_buffer.Span.Slice(offset, numBytes)); #else - Buffer.BlockCopy(x, 0, _buffer.ByteArray, offset, numBytes); + Buffer.BlockCopy(x, 0, _buffer.Buffer, offset, numBytes); #endif } else @@ -841,7 +852,7 @@ namespace FlatBuffers } #if ENABLE_SPAN_T - public unsafe int Put(int offset, Span x) + public int Put(int offset, Span x) where T : struct { if (x.Length == 0) @@ -861,7 +872,7 @@ namespace FlatBuffers offset -= numBytes; AssertOffsetAndLength(offset, numBytes); // if we are LE, just do a block copy - MemoryMarshal.Cast(x).CopyTo(new Span(_buffer.Buffer, _buffer.Length).Slice(offset, numBytes)); + MemoryMarshal.Cast(x).CopyTo(_buffer.Span.Slice(offset, numBytes)); } else { diff --git a/tests/FlatBuffers.Benchmarks/FlatBufferBuilderBenchmark.cs b/tests/FlatBuffers.Benchmarks/FlatBufferBuilderBenchmark.cs new file mode 100644 index 0000000..1df5ac3 --- /dev/null +++ b/tests/FlatBuffers.Benchmarks/FlatBufferBuilderBenchmark.cs @@ -0,0 +1,101 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using BenchmarkDotNet.Attributes; +using MyGame.Example; + +namespace FlatBuffers.Benchmarks +{ + //[EtwProfiler] - needs elevated privileges + [MemoryDiagnoser] + public class FlatBufferBuilderBenchmark + { + private const int NumberOfRows = 10_000; + + [Benchmark] + public void BuildNestedMonster() + { + const string nestedMonsterName = "NestedMonsterName"; + const short nestedMonsterHp = 600; + const short nestedMonsterMana = 1024; + + for (int i = 0; i < NumberOfRows; i++) + { + // Create nested buffer as a Monster type + var fbb1 = new FlatBufferBuilder(16); + var str1 = fbb1.CreateString(nestedMonsterName); + Monster.StartMonster(fbb1); + Monster.AddName(fbb1, str1); + Monster.AddHp(fbb1, nestedMonsterHp); + Monster.AddMana(fbb1, nestedMonsterMana); + var monster1 = Monster.EndMonster(fbb1); + Monster.FinishMonsterBuffer(fbb1, monster1); + var fbb1Bytes = fbb1.SizedByteArray(); + fbb1 = null; + + // Create a Monster which has the first buffer as a nested buffer + var fbb2 = new FlatBufferBuilder(16); + var str2 = fbb2.CreateString("My Monster"); + var nestedBuffer = Monster.CreateTestnestedflatbufferVector(fbb2, fbb1Bytes); + Monster.StartMonster(fbb2); + Monster.AddName(fbb2, str2); + Monster.AddHp(fbb2, 50); + Monster.AddMana(fbb2, 32); + Monster.AddTestnestedflatbuffer(fbb2, nestedBuffer); + var monster = Monster.EndMonster(fbb2); + Monster.FinishMonsterBuffer(fbb2, monster); + } + } + + [Benchmark] + public void BuildMonster() + { + for (int i = 0; i < NumberOfRows; i++) + { + var builder = new FlatBufferBuilder(16); + var str1 = builder.CreateString("MonsterName"); + Monster.StartMonster(builder); + Monster.AddName(builder, str1); + Monster.AddHp(builder, 600); + Monster.AddMana(builder, 1024); + Monster.AddColor(builder, Color.Blue); + Monster.AddTestbool(builder, true); + Monster.AddTestf(builder, 0.3f); + Monster.AddTestf2(builder, 0.2f); + Monster.AddTestf3(builder, 0.1f); + + var monster1 = Monster.EndMonster(builder); + Monster.FinishMonsterBuffer(builder, monster1); + } + } + + [Benchmark] + public void TestTables() + { + FlatBufferBuilder builder = new FlatBufferBuilder(1024 * 1024 * 32); + for (int x = 0; x < 500000; ++x) + { + var offset = builder.CreateString("T"); + builder.StartObject(4); + builder.AddDouble(3.2); + builder.AddDouble(4.2); + builder.AddDouble(5.2); + builder.AddOffset(offset.Value); + builder.EndObject(); + } + } + } +} diff --git a/tests/FlatBuffers.Benchmarks/FlatBuffers.Benchmarks.csproj b/tests/FlatBuffers.Benchmarks/FlatBuffers.Benchmarks.csproj new file mode 100644 index 0000000..b900384 --- /dev/null +++ b/tests/FlatBuffers.Benchmarks/FlatBuffers.Benchmarks.csproj @@ -0,0 +1,21 @@ + + + + Exe + netcoreapp2.1 + latest + true + $(DefineConstants);UNSAFE_BYTEBUFFER;BYTEBUFFER_NO_BOUNDS_CHECK;ENABLE_SPAN_T + + + + + + + + + + + + + diff --git a/tests/FlatBuffers.Benchmarks/Program.cs b/tests/FlatBuffers.Benchmarks/Program.cs new file mode 100644 index 0000000..9e63b4b --- /dev/null +++ b/tests/FlatBuffers.Benchmarks/Program.cs @@ -0,0 +1,30 @@ +/* + * Copyright 2014 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +using BenchmarkDotNet.Running; + +namespace FlatBuffers.Benchmarks +{ + public static class Program + { + public static void Main(string[] args) + { + BenchmarkSwitcher + .FromAssembly(typeof(Program).Assembly) + .Run(args); + } + } +} \ No newline at end of file diff --git a/tests/FlatBuffers.Test/FlatBuffers.Test.csproj b/tests/FlatBuffers.Test/FlatBuffers.Test.csproj index 7510f9d..bbba231 100644 --- a/tests/FlatBuffers.Test/FlatBuffers.Test.csproj +++ b/tests/FlatBuffers.Test/FlatBuffers.Test.csproj @@ -31,6 +31,10 @@ + + true + $(DefineConstants);UNSAFE_BYTEBUFFER + -- 2.7.4