Added Tmj parsing to benchmarking

This commit is contained in:
Daniel Cronqvist 2024-08-11 12:10:56 +02:00
parent bb74d3ccee
commit cc57b172a8
6 changed files with 112 additions and 101 deletions

View file

@ -15,68 +15,54 @@ namespace MyBenchmarks
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
[Orderer(SummaryOrderPolicy.FastestToSlowest)]
[HideColumns(["StdDev", "Error", "RatioSD"])]
public class MapLoading
{
private string _tmxPath = @"C:\Users\Daniel\winrepos\DotTiled\DotTiled.Tests\Serialization\Tmx\TestData\Map\empty-map-csv.tmx";
private string _tmxPath = @"C:\Users\Daniel\winrepos\DotTiled\DotTiled.Tests\Serialization\TestData\Map\empty-map-csv.tmx";
private string _tmxContents = "";
private string _tmjPath = @"C:\Users\Daniel\winrepos\DotTiled\DotTiled.Tests\Serialization\TestData\Map\empty-map-csv.tmj";
private string _tmjContents = "";
public MapLoading()
{
_tmxContents = System.IO.File.ReadAllText(_tmxPath);
_tmjContents = System.IO.File.ReadAllText(_tmjPath);
}
[BenchmarkCategory("MapFromInMemoryTmxString")]
[Benchmark(Baseline = true, Description = "DotTiled")]
public DotTiled.Map LoadWithDotTiledFromInMemoryString()
public DotTiled.Map LoadWithDotTiledFromInMemoryTmxString()
{
using var stringReader = new StringReader(_tmxContents);
using var xmlReader = XmlReader.Create(stringReader);
using var mapReader = new DotTiled.TmxMapReader(xmlReader, _ => throw new Exception(), _ => throw new Exception());
using var mapReader = new DotTiled.TmxMapReader(xmlReader, _ => throw new Exception(), _ => throw new Exception(), []);
return mapReader.ReadMap();
}
[BenchmarkCategory("MapFromTmxFile")]
[BenchmarkCategory("MapFromInMemoryTmjString")]
[Benchmark(Baseline = true, Description = "DotTiled")]
public DotTiled.Map LoadWithDotTiledFromFile()
public DotTiled.Map LoadWithDotTiledFromInMemoryTmjString()
{
using var fileStream = System.IO.File.OpenRead(_tmxPath);
using var xmlReader = XmlReader.Create(fileStream);
using var mapReader = new DotTiled.TmxMapReader(xmlReader, _ => throw new Exception(), _ => throw new Exception());
using var mapReader = new DotTiled.TmjMapReader(_tmjContents, _ => throw new Exception(), _ => throw new Exception(), []);
return mapReader.ReadMap();
}
[BenchmarkCategory("MapFromInMemoryTmxString")]
[Benchmark(Description = "TiledLib")]
public TiledLib.Map LoadWithTiledLibFromInMemoryString()
public TiledLib.Map LoadWithTiledLibFromInMemoryTmxString()
{
using var memStream = new MemoryStream(Encoding.UTF8.GetBytes(_tmxContents));
return TiledLib.Map.FromStream(memStream);
}
[BenchmarkCategory("MapFromTmxFile")]
[Benchmark(Description = "TiledLib")]
public TiledLib.Map LoadWithTiledLibFromFile()
{
using var fileStream = System.IO.File.OpenRead(_tmxPath);
var map = TiledLib.Map.FromStream(fileStream);
return map;
}
[BenchmarkCategory("MapFromInMemoryTmxString")]
[Benchmark(Description = "TiledCSPlus")]
public TiledCSPlus.TiledMap LoadWithTiledCSPlusFromInMemoryString()
public TiledCSPlus.TiledMap LoadWithTiledCSPlusFromInMemoryTmxString()
{
using var memStream = new MemoryStream(Encoding.UTF8.GetBytes(_tmxContents));
return new TiledCSPlus.TiledMap(memStream);
}
[BenchmarkCategory("MapFromTmxFile")]
[Benchmark(Description = "TiledCSPlus")]
public TiledCSPlus.TiledMap LoadWithTiledCSPlusFromFile()
{
using var fileStream = System.IO.File.OpenRead(_tmxPath);
return new TiledCSPlus.TiledMap(fileStream);
}
}
public class Program
@ -84,6 +70,7 @@ namespace MyBenchmarks
public static void Main(string[] args)
{
var config = BenchmarkDotNet.Configs.DefaultConfig.Instance
.WithArtifactsPath(args[0])
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
.AddDiagnoser(BenchmarkDotNet.Diagnosers.MemoryDiagnoser.Default);
var summary = BenchmarkRunner.Run<MapLoading>(config);

View file

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
namespace DotTiled;
internal static partial class Helpers
{
internal static class Data
{
internal static uint[] ReadMemoryStreamAsInt32Array(Stream stream)
{
var finalValues = new List<uint>();
var int32Bytes = new byte[4];
while (stream.Read(int32Bytes, 0, 4) == 4)
{
var value = BitConverter.ToUInt32(int32Bytes, 0);
finalValues.Add(value);
}
return [.. finalValues];
}
internal static uint[] DecompressGZip(MemoryStream stream)
{
using var decompressedStream = new GZipStream(stream, CompressionMode.Decompress);
return ReadMemoryStreamAsInt32Array(decompressedStream);
}
internal static uint[] DecompressZLib(MemoryStream stream)
{
using var decompressedStream = new ZLibStream(stream, CompressionMode.Decompress);
return ReadMemoryStreamAsInt32Array(decompressedStream);
}
internal static uint[] ReadBytesAsInt32Array(byte[] bytes)
{
var intArray = new uint[bytes.Length / 4];
for (var i = 0; i < intArray.Length; i++)
{
intArray[i] = BitConverter.ToUInt32(bytes, i * 4);
}
return intArray;
}
internal static (uint[] GlobalTileIDs, FlippingFlags[] FlippingFlags) ReadAndClearFlippingFlagsFromGIDs(uint[] globalTileIDs)
{
var clearedGlobalTileIDs = new uint[globalTileIDs.Length];
var flippingFlags = new FlippingFlags[globalTileIDs.Length];
for (var i = 0; i < globalTileIDs.Length; i++)
{
var gid = globalTileIDs[i];
var flags = gid & 0xF0000000u;
flippingFlags[i] = (FlippingFlags)flags;
clearedGlobalTileIDs[i] = gid & 0x0FFFFFFFu;
}
return (clearedGlobalTileIDs, flippingFlags);
}
}
}

View file

@ -49,7 +49,7 @@ internal partial class Tmj
{
// Array of uint
var data = element.GetValueAsList<uint>(e => e.GetValueAs<uint>()).ToArray();
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(data);
var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(data);
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null };
}
else if (encoding == DataEncoding.Base64)
@ -58,75 +58,25 @@ internal partial class Tmj
if (compression == null)
{
var data = ReadBytesAsInt32Array(base64Data);
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(data);
var data = Helpers.Data.ReadBytesAsInt32Array(base64Data);
var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(data);
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null };
}
using var stream = new MemoryStream(base64Data);
var decompressed = compression switch
{
DataCompression.GZip => DecompressGZip(stream),
DataCompression.ZLib => DecompressZLib(stream),
DataCompression.GZip => Helpers.Data.DecompressGZip(stream),
DataCompression.ZLib => Helpers.Data.DecompressZLib(stream),
_ => throw new JsonException($"Unsupported compression '{compression}'.")
};
{
var (globalTileIDs, flippingFlags) = ReadAndClearFlippingFlagsFromGIDs(decompressed);
var (globalTileIDs, flippingFlags) = Helpers.Data.ReadAndClearFlippingFlagsFromGIDs(decompressed);
return new Data { Encoding = encoding, Compression = compression, GlobalTileIDs = globalTileIDs, FlippingFlags = flippingFlags, Chunks = null };
}
}
throw new JsonException($"Unsupported encoding '{encoding}'.");
}
internal static uint[] ReadMemoryStreamAsInt32Array(Stream stream)
{
var finalValues = new List<uint>();
var int32Bytes = new byte[4];
while (stream.Read(int32Bytes, 0, 4) == 4)
{
var value = BitConverter.ToUInt32(int32Bytes, 0);
finalValues.Add(value);
}
return finalValues.ToArray();
}
internal static uint[] DecompressGZip(MemoryStream stream)
{
using var decompressedStream = new GZipStream(stream, CompressionMode.Decompress);
return ReadMemoryStreamAsInt32Array(decompressedStream);
}
internal static uint[] DecompressZLib(MemoryStream stream)
{
using var decompressedStream = new ZLibStream(stream, CompressionMode.Decompress);
return ReadMemoryStreamAsInt32Array(decompressedStream);
}
internal static uint[] ReadBytesAsInt32Array(byte[] bytes)
{
var intArray = new uint[bytes.Length / 4];
for (var i = 0; i < intArray.Length; i++)
{
intArray[i] = BitConverter.ToUInt32(bytes, i * 4);
}
return intArray;
}
internal static (uint[] GlobalTileIDs, FlippingFlags[] FlippingFlags) ReadAndClearFlippingFlagsFromGIDs(uint[] globalTileIDs)
{
var clearedGlobalTileIDs = new uint[globalTileIDs.Length];
var flippingFlags = new FlippingFlags[globalTileIDs.Length];
for (var i = 0; i < globalTileIDs.Length; i++)
{
var gid = globalTileIDs[i];
var flags = gid & 0xF0000000u;
flippingFlags[i] = (FlippingFlags)flags;
clearedGlobalTileIDs[i] = gid & 0x0FFFFFFFu;
}
return (clearedGlobalTileIDs, flippingFlags);
}
}

View file

@ -59,7 +59,7 @@ internal partial class Tmj
"grid" => TileRenderSize.Grid,
_ => throw new JsonException($"Unknown tile render size '{s}'")
}, TileRenderSize.Tile);
var tiles = element.GetOptionalPropertyCustom<List<Tile>>("tiles", el => ReadTiles(el, customTypeDefinitions), []);
var tiles = element.GetOptionalPropertyCustom<List<Tile>>("tiles", el => ReadTiles(el, externalTemplateResolver, customTypeDefinitions), []);
var tileWidth = element.GetOptionalProperty<uint?>("tilewidth", null);
var transparentColor = element.GetOptionalPropertyParseable<Color?>("transparentcolor", s => Color.Parse(s, CultureInfo.InvariantCulture), null);
var type = element.GetOptionalProperty<string?>("type", null);
@ -159,10 +159,11 @@ internal partial class Tmj
internal static List<Tile> ReadTiles(
JsonElement element,
Func<string, Template> externalTemplateResolver,
IReadOnlyCollection<CustomTypeDefinition> customTypeDefinitions) =>
element.GetValueAsList<Tile>(e =>
{
//var animation = e.GetOptionalPropertyCustom<List<Frame>>("animation", ReadFrames, null);
var animation = e.GetOptionalPropertyCustom<List<Frame>>("animation", e => e.GetValueAsList<Frame>(ReadFrame), null);
var id = e.GetRequiredProperty<uint>("id");
var image = e.GetOptionalProperty<string?>("image", null);
var imageHeight = e.GetOptionalProperty<uint?>("imageheight", null);
@ -171,7 +172,7 @@ internal partial class Tmj
var y = e.GetOptionalProperty<uint>("y", 0);
var width = e.GetOptionalProperty<uint>("width", imageWidth ?? 0);
var height = e.GetOptionalProperty<uint>("height", imageHeight ?? 0);
//var objectGroup = e.GetOptionalPropertyCustom<ObjectLayer?>("objectgroup", ReadObjectLayer, null);
var objectGroup = e.GetOptionalPropertyCustom<ObjectLayer?>("objectgroup", e => ReadObjectLayer(e, externalTemplateResolver, customTypeDefinitions), null);
var probability = e.GetOptionalProperty<float>("probability", 1.0f);
var properties = e.GetOptionalPropertyCustom<Dictionary<string, IProperty>?>("properties", el => ReadProperties(el, customTypeDefinitions), null);
// var terrain, replaced by wangsets
@ -187,17 +188,29 @@ internal partial class Tmj
return new Tile
{
//Animation = animation,
Animation = animation,
ID = id,
Image = imageModel,
X = x,
Y = y,
Width = width,
Height = height,
//ObjectLayer = objectGroup,
ObjectLayer = objectGroup,
Probability = probability,
Properties = properties,
Type = type
};
});
internal static Frame ReadFrame(JsonElement element)
{
var duration = element.GetRequiredProperty<uint>("duration");
var tileID = element.GetRequiredProperty<uint>("tileid");
return new Frame
{
Duration = duration,
TileID = tileID
};
}
}

View file

@ -1,9 +1,10 @@
test:
dotnet test
BENCHMARK_SOURCES = DotTiled.Benchmark/Program.cs DotTiled.Benchmark/DotTiled.Benchmark.csproj
BENCHMARK_OUTPUTDIR = DotTiled.Benchmark/BenchmarkDotNet.Artifacts
.PHONY: benchmark
benchmark: DotTiled.Benchmark/BenchmarkDotNet.Artifacts/results/MyBenchmarks.MapLoading-report-github.md
benchmark: $(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md
BENCHMARK_SOURCES = DotTiled.Benchmark/Program.cs
DotTiled.Benchmark/BenchmarkDotNet.Artifacts/results/MyBenchmarks.MapLoading-report-github.md: $(BENCHMARK_SOURCES)
dotnet run --project DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release
$(BENCHMARK_OUTPUTDIR)/results/MyBenchmarks.MapLoading-report-github.md: $(BENCHMARK_SOURCES)
dotnet run --project DotTiled.Benchmark/DotTiled.Benchmark.csproj -c Release -- $(BENCHMARK_OUTPUTDIR)

View file

@ -60,15 +60,13 @@ BenchmarkDotNet v0.13.12, Windows 10 (10.0.19045.4651/22H2/2022Update)
[Host] : .NET 8.0.3 (8.0.324.11423), X64 RyuJIT AVX2
DefaultJob : .NET 8.0.3 (8.0.324.11423), X64 RyuJIT AVX2
```
| Method | Categories | Mean | Error | StdDev | Ratio | RatioSD | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------ |------------------------- |----------:|----------:|----------:|------:|--------:|-------:|-------:|----------:|------------:|
| DotTiled | MapFromInMemoryTmxString | 2.991 μs | 0.0266 μs | 0.0236 μs | 1.00 | 0.00 | 1.2817 | 0.0610 | 16.37 KB | 1.00 |
| TiledLib | MapFromInMemoryTmxString | 5.405 μs | 0.0466 μs | 0.0413 μs | 1.81 | 0.02 | 1.8158 | 0.1068 | 23.32 KB | 1.42 |
| TiledCSPlus | MapFromInMemoryTmxString | 6.354 μs | 0.0703 μs | 0.0587 μs | 2.12 | 0.03 | 2.5940 | 0.1831 | 33.23 KB | 2.03 |
| | | | | | | | | | | |
| DotTiled | MapFromTmxFile | 28.570 μs | 0.1216 μs | 0.1137 μs | 1.00 | 0.00 | 1.0376 | - | 13.88 KB | 1.00 |
| TiledCSPlus | MapFromTmxFile | 33.377 μs | 0.1086 μs | 0.1016 μs | 1.17 | 0.01 | 2.8076 | 0.1221 | 36.93 KB | 2.66 |
| TiledLib | MapFromTmxFile | 36.077 μs | 0.1900 μs | 0.1777 μs | 1.26 | 0.01 | 2.0752 | 0.1221 | 27.1 KB | 1.95 |
| Method | Categories | Mean | Ratio | Gen0 | Gen1 | Allocated | Alloc Ratio |
|------------ |------------------------- |---------:|------:|-------:|-------:|----------:|------------:|
| DotTiled | MapFromInMemoryTmjString | 4.292 μs | 1.00 | 0.4349 | - | 5.62 KB | 1.00 |
| | | | | | | | |
| DotTiled | MapFromInMemoryTmxString | 3.075 μs | 1.00 | 1.2817 | 0.0610 | 16.4 KB | 1.00 |
| TiledLib | MapFromInMemoryTmxString | 5.574 μs | 1.81 | 1.8005 | 0.0916 | 23.32 KB | 1.42 |
| TiledCSPlus | MapFromInMemoryTmxString | 6.546 μs | 2.13 | 2.5940 | 0.1831 | 33.16 KB | 2.02 |
</details>