Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions nuspec/Itinero.nuspec
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@
<tags>osm, openstreetmap, routing, mapping</tags>
<dependencies>
<group targetFramework=".NETFramework4.5">
<dependency id="Reminiscence" version="[1.2.0, )" />
<dependency id="Reminiscence" version="[1.3.0-pre06, )" />
</group>
<group targetFramework=".NETStandard1.3">
<dependency id="NETStandard.Library" version="[1.6.0, )" />
<dependency id="System.Reflection.TypeExtensions" version="[4.3.0, )" />
<dependency id="System.Xml.XmlSerializer" version="[4.0.11, )" />
<dependency id="Reminiscence" version="[1.2.0, )" />
<dependency id="Reminiscence" version="[1.3.0-pre06, )" />
</group>
<group targetFramework=".NETStandard2.0">
<dependency id="Reminiscence" version="[1.2.0, )" />
<dependency id="Reminiscence" version="[1.3.0-pre06, )" />
</group>
</dependencies>
</metadata>
Expand Down
91 changes: 90 additions & 1 deletion src/Itinero/DefaultArrayFactory.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Reminiscence.Arrays;
using System;
using Reminiscence.Arrays;

namespace Itinero
{
Expand All @@ -8,7 +9,95 @@ namespace Itinero
/// </summary>
public sealed class DefaultArrayFactory : IArrayFactory
{
#if !HAS_NATIVE_MEMORY_ARRAY
/// <inheritdoc />
public ArrayBase<T> CreateMemoryBackedArray<T>(long size) => new MemoryArray<T>(size);
#else
/// <summary>
/// Gets or sets an <see cref="IUnmanagedMemoryAllocator"/> instance to use to allocate
/// contiguous blocks of virtual memory.
/// <para>
/// The default is an allocator that uses the global heap, i.e., the trio of:
/// <see cref="System.Runtime.InteropServices.Marshal.AllocHGlobal(IntPtr)"/>,
/// <see cref="System.Runtime.InteropServices.Marshal.ReAllocHGlobal(IntPtr, IntPtr)"/>, and
/// <see cref="System.Runtime.InteropServices.Marshal.FreeHGlobal(IntPtr)"/>.
/// </para>
/// </summary>
public IUnmanagedMemoryAllocator UnmanagedMemoryAllocator { get; set; } = DefaultUnmanagedMemoryAllocator.Instance;

// overwritten only by tests, temporarily, to validate behavior on 32-bit systems.
internal bool Is32BitProcess { get; set; } = IntPtr.Size == 4;

/// <inheritdoc />
public ArrayBase<T> CreateMemoryBackedArray<T>(long size)
{
// 32-bit processes risk running out of contiguous virtual memory blocks big enough for
// the kinds of arrays we use long before they risk running out of memory, so if the
// native array would be larger than a single MemoryArray<T> block on a 32-bit process,
// then fall back to MemoryArray<T> for safety.
const long MemoryArrayThresholdFor32Bits = 1 << 20;
if (Is32BitProcess && size > MemoryArrayThresholdFor32Bits)
{
return new MemoryArray<T>(size);
}

// NativeMemoryArray<T> is constrained to unmanaged types, so we have to do a bit of a
// workaround in order to use it this way. Don't worry about performance of all the type
// checks; they should happen at JIT-time, not at runtime.
// RuntimeHelpers.IsReferenceOrContainsReferences<T>() is available in .NET Core 2.0 and
// .NET Standard 2.1 which we could use as part of a solution that eliminates this stack
// of copy-paste and hardcoded list of types.
object arrayObj = null;
if (typeof(T) == typeof(byte))
{
arrayObj = new NativeMemoryArray<byte>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(sbyte))
{
arrayObj = new NativeMemoryArray<sbyte>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(short))
{
arrayObj = new NativeMemoryArray<short>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(ushort))
{
arrayObj = new NativeMemoryArray<ushort>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(char))
{
arrayObj = new NativeMemoryArray<char>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(int))
{
arrayObj = new NativeMemoryArray<int>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(uint))
{
arrayObj = new NativeMemoryArray<uint>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(long))
{
arrayObj = new NativeMemoryArray<long>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(ulong))
{
arrayObj = new NativeMemoryArray<ulong>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(float))
{
arrayObj = new NativeMemoryArray<float>(this.UnmanagedMemoryAllocator, size);
}
else if (typeof(T) == typeof(double))
{
arrayObj = new NativeMemoryArray<double>(this.UnmanagedMemoryAllocator, size);
}
// else it's either a compatible type that we never bothered to add (in which case,
// please add it here!), or it is / contains references (in which case, we cannot return
// NativeMemoryArray<T>, though a future specific optimization could handle it).

return ((ArrayBase<T>)arrayObj) ?? new MemoryArray<T>(size);
}
#endif
}
}
8 changes: 4 additions & 4 deletions src/Itinero/Itinero.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Reminiscence" Version="1.2.0" />
<PackageReference Include="Reminiscence" Version="1.3.0-pre06" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.3.0" />
Expand All @@ -24,13 +24,13 @@
<Reference Include="Microsoft.CSharp" />
</ItemGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<DefineConstants>$(DefineConstants);NETFX_CORE;DOTNET_CORE</DefineConstants>
<DefineConstants>$(DefineConstants);NETFX_CORE;DOTNET_CORE;HAS_NATIVE_MEMORY_ARRAY</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<DefineConstants>$(DefineConstants);NETFX_CORE;DOTNET_CORE</DefineConstants>
<DefineConstants>$(DefineConstants);NETFX_CORE;DOTNET_CORE;HAS_NATIVE_MEMORY_ARRAY</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net45' ">
<DefineConstants>$(DefineConstants);NET45</DefineConstants>
<DefineConstants>$(DefineConstants);NET45;HAS_NATIVE_MEMORY_ARRAY</DefineConstants>
</PropertyGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net45' ">
<Compile Include="..\..\SharedAssemblyVersion.cs" />
Expand Down
86 changes: 80 additions & 6 deletions test/Itinero.Test/ContextTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;

using NUnit.Framework;
using Reminiscence.Arrays;

Expand All @@ -17,15 +16,90 @@ public class ContextTests
/// <summary>
/// The default factory in the context should be initialized properly.
/// </summary>
[TestCase(default(byte))]
[TestCase(default(sbyte))]
[TestCase(default(short))]
[TestCase(default(ushort))]
[TestCase(default(char))]
[TestCase(default(int))]
[TestCase(default(uint))]
[TestCase(default(long))]
[TestCase(default(ulong))]
[TestCase(default(double))]
[TestCase(default(float))]
public void DefaultArrayFactoryShouldReturnNativeMemoryArrayWhenAppropriate<T>(T exemplar)
{
const long SmallSize = 592;
const long LargeSize = 1 << 20 + 1;

if (!(Context.ArrayFactory is DefaultArrayFactory defaultArrayFactory))
{
Assert.Inconclusive("Need the default to be a DefaultArrayFactory.");
return;
}

Type nativeMemoryArrayType = typeof(NativeMemoryArray<>).MakeGenericType(typeof(T));

bool old32BitProcessValue = defaultArrayFactory.Is32BitProcess;
try
{
// test behavior in a 32-bit process for the first round of tests.
defaultArrayFactory.Is32BitProcess = true;

// test 32-bit with a huge block
using (var array = defaultArrayFactory.CreateMemoryBackedArray<T>(LargeSize))
{
Assert.AreEqual(LargeSize, array.Length);
Assert.IsInstanceOf<MemoryArray<T>>(array, "32-bit processes are expected to have virtual memory that's more cramped, so we should use MemoryArray<T>'s chunked allocator.");
}

// test 32-bit with a small block
using (var array = defaultArrayFactory.CreateMemoryBackedArray<T>(SmallSize))
{
Assert.AreEqual(SmallSize, array.Length);
Assert.IsInstanceOf(nativeMemoryArrayType, array, "MemoryArray<T> would allocate this in a single block anyway, so there's no reason not to use NativeMemoryArray<T>.");
}

// test behavior in a 64-bit process for the next round of tests.
defaultArrayFactory.Is32BitProcess = false;

// test 64-bit with a small block
using (var array = defaultArrayFactory.CreateMemoryBackedArray<T>(SmallSize))
{
Assert.AreEqual(SmallSize, array.Length);
Assert.IsInstanceOf(nativeMemoryArrayType, array, "64-bit should always use NativeMemoryArray<T> for compatible types when available.");
}

// test 64-bit with a huge block
using (var array = defaultArrayFactory.CreateMemoryBackedArray<T>(LargeSize))
{
Assert.AreEqual(LargeSize, array.Length);
Assert.IsInstanceOf(nativeMemoryArrayType, array, "64-bit should always use NativeMemoryArray<T> for compatible types when available.");
}
}
finally
{
defaultArrayFactory.Is32BitProcess = old32BitProcessValue;
}
}

[Test]
public void DefaultArrayFactoryShouldReturnProperMemoryBackedArray()
public void DefaultArrayFactoryShouldNeverUseNativeMemoryArrayForIncompatibleType()
{
const long SizeToUse = 592;
using (var array = Context.ArrayFactory.CreateMemoryBackedArray<Guid>(SizeToUse))
using (var array = Context.ArrayFactory.CreateMemoryBackedArray<object>(1))
{
Assert.AreEqual(SizeToUse, array.Length, "Default returns an array that doesn't even obey the contract.");
Assert.IsInstanceOf<MemoryArray<Guid>>(array, "Default should return a standard MemoryArray<T> to ensure perfect backwards-compatibility.");
Assert.IsInstanceOf<MemoryArray<object>>(array, "Never use NativeMemoryArray<T> for arbitrary reference types.");
}

using (var array = Context.ArrayFactory.CreateMemoryBackedArray<StructWithReferences>(1))
{
Assert.IsInstanceOf<MemoryArray<StructWithReferences>>(array, "Never use NativeMemoryArray<T> for structs that embed arbitrary reference types.");
}
}

private struct StructWithReferences
{
private object obj;
}
}
}