diff --git a/benchmark/BooleanAttributesBenchmark.cs b/benchmark/BooleanAttributesBenchmark.cs new file mode 100644 index 0000000..04478bc --- /dev/null +++ b/benchmark/BooleanAttributesBenchmark.cs @@ -0,0 +1,67 @@ +using BenchmarkDotNet.Attributes; +using Mapbox.Vector.Tile; +using System; +using System.Collections.Generic; +using System.IO; + +namespace mapbox.vector.tile.benchmark; + +/// +/// Benchmarks parsing of a tile with many boolean attributes, to measure +/// the impact of the pre-boxed Boxes.Boolean_True/False optimisation. +/// +[MemoryDiagnoser] +public class BooleanAttributesBenchmark +{ + private MemoryStream _tileStream = null!; + + [GlobalSetup] + public void Setup() + { + const int featureCount = 100_000; + const uint extent = 4096; + + var attributes = new List> + { + new("is_active", true), + new("is_visible", true), + new("is_selected", false), + new("has_children", false), + new("is_root", true), + }; + + var features = new List(featureCount); + for (var i = 0; i < featureCount; i++) + { + var geometry = new List> + { + new([new Coordinate { X = 0, Y = 0 }]), + }; + features.Add(new VectorTileFeature( + id: i.ToString(), + geometry: geometry, + attributes: attributes, + geometryType: Tile.GeomType.Point, + extent: extent)); + } + + var layer = new VectorTileLayer("bool_layer", 2, extent) + { + VectorTileFeatures = features, + }; + + var buffer = new MemoryStream(); + VectorTileEncoder.Encode([layer], buffer); + _tileStream = new MemoryStream(buffer.ToArray()); + } + + [IterationSetup] + public void ResetStream() => _tileStream.Position = 0; + + [Benchmark] + public List ParseBooleanAttributeTile() + { + _tileStream.Position = 0; + return VectorTileParser.Parse(_tileStream); + } +} diff --git a/benchmark/Program.cs b/benchmark/Program.cs index ddb2072..046d24d 100644 --- a/benchmark/Program.cs +++ b/benchmark/Program.cs @@ -7,7 +7,8 @@ class Program { static void Main(string[] args) { - var summary = BenchmarkRunner.Run(); + BenchmarkRunner.Run(); + BenchmarkRunner.Run(); Console.ReadKey(); } } diff --git a/benchmark/Properties/launchSettings.json b/benchmark/Properties/launchSettings.json new file mode 100644 index 0000000..b530c83 --- /dev/null +++ b/benchmark/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "BooleanAttributes": { + "commandName": "Project", + "commandLineArgs": "BooleanAttributes" + }, + "Parsing": { + "commandName": "Project", + "commandLineArgs": "Parsing" + } + } +} \ No newline at end of file diff --git a/src/AttributesParser.cs b/src/AttributesParser.cs index 53cce64..62552f5 100644 --- a/src/AttributesParser.cs +++ b/src/AttributesParser.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace Mapbox.Vector.Tile; @@ -8,53 +7,67 @@ public static class AttributesParser { public static List> Parse(List keys, List values, List tags) { - var result = new List>(); - var odds = tags.GetOdds().ToList(); - var evens = tags.GetEvens().ToList(); + var result = new List>(keys.Count); - for (var i = 0; i < evens.Count; i++) + for (var i = 0; i < tags.Count; i += 2) { - var key = keys[(int)evens[i]]; - var val = values[(int)odds[i]]; - var valObject = GetAttr(val); - result.Add(new KeyValuePair(key, valObject)); + var key = keys[(int)tags[i]]; + var val = values[(int)tags[i + 1]]; + result.Add(new(key, GetAttr(val))); } return result; } - private static object GetAttr(Tile.Value value) + private static object GetAttr(Tile.Value value) => value switch { - if (value.HasBoolValue) + { HasStringValue: true } => value.StringValue, + { HasBoolValue: true } => value.BoolValue ? Boxes.Boolean_True : Boxes.Boolean_False, + + { HasDoubleValue: true } => value.DoubleValue switch { - return value.BoolValue; - } - else if (value.HasDoubleValue) - { - return value.DoubleValue; - } - else if (value.HasFloatValue) - { - return value.FloatValue; - } - else if (value.HasIntValue) - { - return value.IntValue; - } - else if (value.HasStringValue) + 0 => Boxes.Double_0, + 1 => Boxes.Double_1, + _ => value.DoubleValue, + }, + { HasFloatValue: true } => value.FloatValue switch { - return value.StringValue; - } - else if (value.HasSIntValue) + 0 => Boxes.Single_0, + 1 => Boxes.Single_1, + _ => value.FloatValue, + }, + { HasIntValue: true } => value.IntValue switch { - return value.SintValue; - } - else if (value.HasUIntValue) + 0 => Boxes.Int64_0, + 1 => Boxes.Int64_1, + _ => value.IntValue, + }, + { HasSIntValue: true } => value.SintValue switch { - return value.UintValue; - } - else + 0 => Boxes.Int64_0, + 1 => Boxes.Int64_1, + _ => value.SintValue, + }, + { HasUIntValue: true } => value.UintValue switch { - throw new NotImplementedException("Unknown attribute type"); - } + 0 => Boxes.UInt64_0, + 1 => Boxes.UInt64_1, + _ => value.UintValue, + }, + + _ => throw new NotImplementedException("Unknown attribute type"), + }; + + private static class Boxes + { + public static readonly object Boolean_True = true; + public static readonly object Boolean_False = false; + public static readonly object Int64_0 = 0L; + public static readonly object Int64_1 = 1L; + public static readonly object Single_0 = 0f; + public static readonly object Single_1 = 1f; + public static readonly object Double_0 = 0d; + public static readonly object Double_1 = 1d; + public static readonly object UInt64_0 = 0ul; + public static readonly object UInt64_1 = 1ul; } } diff --git a/src/ExtensionMethods/IEnumerableExtensions.cs b/src/ExtensionMethods/IEnumerableExtensions.cs deleted file mode 100644 index 4de1c2c..0000000 --- a/src/ExtensionMethods/IEnumerableExtensions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Mapbox.Vector.Tile; - -public static class EnumerableExtensions -{ - public static IEnumerable GetOdds(this IEnumerable sequence) - { - return sequence.Where((item, index) => index % 2 != 0); - } - - public static IEnumerable GetEvens(this IEnumerable sequence) - { - return sequence.Where((item, index) => index % 2 == 0); - } -} diff --git a/src/VectorTileParser.cs b/src/VectorTileParser.cs index ce5c279..8a5d104 100644 --- a/src/VectorTileParser.cs +++ b/src/VectorTileParser.cs @@ -9,11 +9,12 @@ public static class VectorTileParser public static List Parse(Stream stream) { var tile = Serializer.Deserialize(stream); - var list = new List(); + var list = new List(tile.Layers.Count); foreach (var layer in tile.Layers) { var extent = layer.Extent; var vectorTileLayer = new VectorTileLayer(layer.Name ?? "[Unnamed]", layer.Version, extent); + vectorTileLayer.VectorTileFeatures.Capacity = layer.Features.Count; foreach (var feature in layer.Features) { diff --git a/tests/IEnumerableExtensionTests.cs b/tests/IEnumerableExtensionTests.cs deleted file mode 100644 index cb58670..0000000 --- a/tests/IEnumerableExtensionTests.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; - -namespace Mapbox.Vector.Tile.tests; - -public class EnumerableExtensionTests -{ - [Test] - public void TestEvensMethod() - { - // arrange - var sequence = new List { 0, 1, 2, 3 }; - - // act - var evens = sequence.GetEvens().ToList(); - - // assert - Assert.That(evens[0] == 0); - Assert.That(evens[1] == 2); - } - - [Test] - public void TestOddsMethod() - { - // arrange - var sequence = new List { 0, 1, 2, 3 }; - - // act - var evens = sequence.GetOdds().ToList(); - - // assert - Assert.That(evens[0] == 1); - Assert.That(evens[1] == 3); - } -}