Commit 0cdacdfb authored by Eric Erhardt's avatar Eric Erhardt Committed by Wouter van Oortmerssen

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<byte> 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<byte> 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.
parent bb584420
......@@ -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 ..\\.."
......
......@@ -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<byte> Span { get; }
public abstract ReadOnlySpan<byte> ReadOnlySpan { get; }
public abstract Memory<byte> Memory { get; }
public abstract ReadOnlyMemory<byte> 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<byte> Span => _buffer;
public override ReadOnlySpan<byte> ReadOnlySpan => _buffer;
public override Memory<byte> Memory => _buffer;
public override ReadOnlyMemory<byte> 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
}
}
/// <summary>
/// Class to mimic Java's ByteBuffer which is used heavily in Flatbuffers.
/// </summary>
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<T>(int pos, int len)
where T: struct
where T : struct
{
unsafe
{
AssertOffsetAndLength(pos, len);
T[] arr = new T[len];
var typed = MemoryMarshal.Cast<byte, T>(new Span<byte>(_buffer.Buffer + pos, _buffer.Length));
typed.Slice(0, arr.Length).CopyTo(arr);
return arr;
}
AssertOffsetAndLength(pos, len);
return MemoryMarshal.Cast<byte, T>(_buffer.ReadOnlySpan.Slice(pos)).Slice(0, len).ToArray();
}
#else
public T[] ToArray<T>(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<byte>(0, Length);
}
#if ENABLE_SPAN_T
public unsafe Span<byte> ToSpan(int pos, int len)
public ReadOnlyMemory<byte> ToReadOnlyMemory(int pos, int len)
{
return _buffer.ReadOnlyMemory.Slice(pos, len);
}
public Memory<byte> ToMemory(int pos, int len)
{
return _buffer.Memory.Slice(pos, len);
}
public Span<byte> ToSpan(int pos, int len)
{
return new Span<byte>(_buffer.Buffer, _buffer.Length).Slice(pos, len);
return _buffer.Span.Slice(pos, len);
}
#else
public ArraySegment<byte> ToArraySegment(int pos, int len)
{
return new ArraySegment<byte>(_buffer.ByteArray, pos, len);
return new ArraySegment<byte>(_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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<byte> 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<T, byte>(x).CopyTo(new Span<byte>(_buffer.Buffer, _buffer.Length).Slice(offset, numBytes));
}
MemoryMarshal.Cast<T, byte>(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<T>(int offset, Span<T> x)
public int Put<T>(int offset, Span<T> 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<T, byte>(x).CopyTo(new Span<byte>(_buffer.Buffer, _buffer.Length).Slice(offset, numBytes));
MemoryMarshal.Cast<T, byte>(x).CopyTo(_buffer.Span.Slice(offset, numBytes));
}
else
{
......
/*
* 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();
}
}
}
}
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<LangVersion>latest</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>$(DefineConstants);UNSAFE_BYTEBUFFER;BYTEBUFFER_NO_BOUNDS_CHECK;ENABLE_SPAN_T</DefineConstants>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.11.3" />
<PackageReference Include="BenchmarkDotNet.Diagnostics.Windows" Version="0.11.3" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\net\FlatBuffers\*.cs" Link="FlatBuffers\%(FileName).cs" />
<Compile Include="..\MyGame\**\*.cs" Link="MyGame\Example\%(FileName).cs" />
</ItemGroup>
</Project>
/*
* 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
......@@ -31,6 +31,10 @@
<PropertyGroup>
<StartupObject />
</PropertyGroup>
<PropertyGroup Condition="'$(UnsafeByteBuffer)' == 'true'">
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<DefineConstants>$(DefineConstants);UNSAFE_BYTEBUFFER</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core">
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment