saul / demofile-net

Blazing fast cross-platform demo parser library for Counter-Strike 2, written in C#.
MIT License
76 stars 7 forks source link

NativeAOT support #25

Closed in0finite closed 6 months ago

in0finite commented 7 months ago

It would be great if NativeAOT compilation would be supported by the library.

This would give the benefits of faster execution, and possibility to build native library (DLL) which can then be linked by any other program (regardless of programming language).

Right now, I get this error for most of demos:

Unhandled Exception: Google.Protobuf.InvalidProtocolBufferException: Protocol message contained an invalid tag (zero).
   at Google.Protobuf.ParsingPrimitives.ParseTag(ReadOnlySpan`1&, ParserInternalState&) + 0x133
   at CNETMsg_Tick.pb::Google.Protobuf.IBufferMessage.InternalMergeFrom(ParseContext&) + 0x1f
   at Google.Protobuf.MessageExtensions.MergeFrom(IMessage, ReadOnlySpan`1, Boolean, ExtensionRegistry) + 0xdb
   at Google.Protobuf.MessageParser`1.ParseFrom(ReadOnlySpan`1) + 0x2f
   at DemoFile.PacketEvents.ParseNetMessage(Int32, ReadOnlySpan`1) + 0xd3
   at DemoFile.DemoParser.OnDemoPacket(CDemoPacket msg) + 0x328
   at DemoFile.DemoEvents.ReadDemoCommand(EDemoCommands, ReadOnlySpan`1) + 0x1cf
   at DemoFile.DemoParser.<Start>d__46.MoveNext() + 0x824
--- End of stack trace from previous location ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x20
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0xb6
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task) + 0x42
   at Program.<Main>d__0.MoveNext() + 0x2b6
--- End of stack trace from previous location ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw() + 0x20
   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task) + 0xb6
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task) + 0x42
   at Program.<Main>(String[]) + 0x20
   at DemoFile.Example.Basic!<BaseAddress>+0x44a35b

It could be a bug in Google.Protobuf library, but I did not investigate further, for now.

saul commented 6 months ago

This is fixed in v0.5.1, thanks for reporting

in0finite commented 6 months ago

nice job 👍

saul commented 6 months ago

It's worth noting, from my benchmarks on an M1 MacBook Pro/.NET 8, Native AOT is slower than JIT when parsing a whole demo. I'd be interested to know if you see differently.

in0finite commented 6 months ago

I did some measurements. For each runtime, I made a separate publish, then ran it from command line a couple of times (I didn't use BenchmarkDotNet).

I'm using win11 x64. All runs use navi-javelins-vs-9-pandas-fearless-m1-mirage.dem.

Runtime Average
.NET 7 3.8 s
.NET 8 4.1 s
.NET 7 AOT 2.2 s
.NET 8 AOT 1.95 s
.NET 8 AOT with \Speed<\/OptimizationPreference> 1.9s

It seems as AOT is twice as fast ? Am i missing something ? Also, you mentioned in Readme that you can parse 1 hour of gameplay in 1.5 sec, which doesn't seem possible for me.

saul commented 6 months ago

You'll be measuring app startup time there too, which Benchmark.Net specifically excludes. Also process startup time on Windows is very slow compared to Unix-based OSes.

Suggest using Benchmark.Net (with the DemoFile.Benchmark project) to benchmark. Otherwise you're also testing your OS, antivirus and disk read speeds.

in0finite commented 6 months ago

Sorry, I forgot to mention how I measure time. This is the running process:

public static async Task Main(string[] args)
{
    var path = args.SingleOrDefault() ?? throw new Exception("Expected a single argument: <path to .dem>");

    var stream = File.OpenRead(path);

    var sw = Stopwatch.StartNew();

    var demo = new DemoParser();

    await demo.Start(stream);

    var elapsedMs = sw.ElapsedMilliseconds;
    Console.WriteLine($"\nFinished!, elapsed {elapsedMs} ms, tick {demo.CurrentDemoTick.Value}");
}

As you can see, I measure only the processing time, no events, no file stream opening, etc.

in0finite commented 6 months ago

Ok, it turns out I was measuring only the 1st run :D

So probably .NET runtime needs to warmup. After warmup, they run for ~1.6s. As for AOT, it runs for ~1.7s, so yeah it's slightly slower.