itinero / reminiscence

A library for cross-platform memory mapping.
MIT License
9 stars 6 forks source link

Add a new implementation of ArrayBase<T> backed by unmanaged memory. #18

Closed airbreather closed 5 years ago

airbreather commented 5 years ago

According to my testing, I/O for serializing / deserializing Itinero objects is significantly faster with this, to the extent that I'm satisfied that this is worth pursuing on that basis alone.

There are occasionally some other places that see speedups as well, primarily during the initial build and contraction hierarchy creation (which is what I was targeting with these improvements), but these are very minor (presumably because netcoreapp2.2 has a much smarter JIT than netcoreapp2.0, which is what I was originally targeting when I embarked on this roughly a year and a half ago).

I just did one run for each row because the times can be so long. All files were entirely served from the filesystem cache for these tests. Timings were captured on netcoreapp2.2 with server GC. The only difference between "vanilla" and "new array" is whether or not the test injected a custom IArrayFactory implementation.

Data set Itinero Method Time (seconds, vanilla) Time (seconds, new array)
denmark-latest.osm.pbf (2019-01-19) (from here) RouterDb.LoadOsmData 86.612 83.852
denmark-latest.osm.pbf (2019-01-19) (from here) RouterDb.AddContracted(Profile) 138.024 120.296
North America extract of planet.osm.pbf (2017-04-25) RouterDb.Deserialize(Stream) 114.106 9.894
North America extract of planet.osm.pbf (2017-04-25) RouterDb.DeserializeAndAddContracted(Stream) 77.702 6.530

I did not observe any differences in timings for actual calculations worth discussing.

airbreather commented 5 years ago

Appendix 1

MyArrayFactory.cs

using System;
using Itinero;
using Reminiscence.Arrays;

public sealed class MyArrayFactory : IArrayFactory
{
    public ArrayBase<T> CreateMemoryBackedArray<T>(long size) => ArrayCreatorCache<T>.CreateNewArray(size);

    private sealed class ArrayCreatorCache<T>
    {
        public static readonly Func<long, ArrayBase<T>> CreateNewArray = Create();

        private static Func<long, ArrayBase<T>> Create()
        {
            if (typeof(T) == typeof(byte) ||
                typeof(T) == typeof(sbyte) ||
                typeof(T) == typeof(short) ||
                typeof(T) == typeof(ushort) ||
                typeof(T) == typeof(int) ||
                typeof(T) == typeof(uint) ||
                typeof(T) == typeof(long) ||
                typeof(T) == typeof(ulong) ||
                typeof(T) == typeof(float) ||
                typeof(T) == typeof(double))
            {
                var types = new[] { typeof(IUnmanagedMemoryAllocator), typeof(long) };
                var ctor = typeof(NativeMemoryArray<>).MakeGenericType(typeof(T)).GetConstructor(types);
                var allocator = DefaultUnmanagedMemoryAllocator.Instance;
                return x => (ArrayBase<T>)ctor.Invoke(new object[] { allocator, x });
            }

            return x => new MemoryArray<T>(x);
        }
    }
}
xivk commented 5 years ago

This is great! Especially the RouterDb.Deserialize(Stream)... Give me some time to review this but is looking very good...

airbreather commented 5 years ago

Give me some time to review this but is looking very good...

Have you had the opportunity to take a look at this one yet?

Following this, I have a proper implementation of ArrayBase<T> using "true" memory-mapped files which is almost ready-to-go.

Following both of those, I can work towards updating Itinero proper to take advantage of the new types.