Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Odin Serializer to the benchmark (round 2) #1248

Merged
merged 7 commits into from
May 29, 2021

Conversation

JesseTG
Copy link
Contributor

@JesseTG JesseTG commented May 27, 2021

Here's a second attempt at this PR. The difference is:

  • The exception I cited in that original post has been resolved. It still occurs, but now it's logged instead of being fatal. @TorVestergaard can provide more information. (Thanks for all the help!)
  • The numbers I gave here were in fact incorrect because Odin was not configured properly. Since Odin is intended for Unity, its default settings are unusual as far as .NET serializers go. The benchmark now has Odin configured to serialize everything given to it (as opposed to whatever fits Unity's rules).

Here are the specs of my machine:

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.19042
Intel Core i7-4810MQ CPU 2.80GHz (Haswell), 1 CPU, 8 logical and 4 physical cores
.NET Core SDK=3.1.301
  [Host]   : .NET Core 3.1.14 (CoreCLR 4.700.21.16201, CoreFX 4.700.21.16208), X64 RyuJIT
  ShortRun : .NET Core 3.1.14 (CoreCLR 4.700.21.16201, CoreFX 4.700.21.16208), X64 RyuJIT

Job=ShortRun  Jit=RyuJit  Platform=X64  
Runtime=.NET Core 3.1  IterationCount=1  LaunchCount=1  
WarmupCount=1  

These are the results of the entire benchmark suite on my machine, including Odin:

Method Serializer Mean Error DataSize Gen 0 Gen 1 Gen 2 Allocated
AnswerDeserialize BinaryFormatter_ 60.331 us NA - 10.9863 - - 34568 B
AnswerDeserialize BsonNet 38.832 us NA - 3.2349 - - 10248 B
AnswerDeserialize Ceras_ 5.592 us NA - 0.5722 - - 1816 B
AnswerDeserialize DataContract_ 42.584 us NA - 5.4321 - - 17072 B
AnswerDeserialize FsPickler_ 7.489 us NA - 1.2512 - - 3936 B
AnswerDeserialize Hyperion_ 6.698 us NA - 1.3428 - - 4216 B
AnswerDeserialize Jil_ 12.630 us NA - 1.9073 - - 6016 B
AnswerDeserialize JsonNet 40.545 us NA - 3.2959 - - 10360 B
AnswerDeserialize MessagePack_v1 3.034 us NA - 0.5760 - - 1816 B
AnswerDeserialize MessagePack_v2 4.270 us NA - 0.5722 - - 1816 B
AnswerDeserialize MessagePackLz4_v1 3.329 us NA - 0.5760 - - 1816 B
AnswerDeserialize MessagePackLz4_v2 5.405 us NA - 0.5722 - - 1816 B
AnswerDeserialize MsgPack_v1_str_lz4 4.703 us NA - 0.5722 - - 1816 B
AnswerDeserialize MsgPack_v1_string 4.290 us NA - 0.5722 - - 1816 B
AnswerDeserialize MsgPack_v2_opt 5.693 us NA - 0.5722 - - 1816 B
AnswerDeserialize MsgPack_v2_str_lz4 8.206 us NA - 0.5646 - - 1816 B
AnswerDeserialize MsgPack_v2_string 6.949 us NA - 0.5722 - - 1816 B
AnswerDeserialize MsgPackCli 10.959 us NA - 0.9155 - - 2880 B
AnswerDeserialize Odin_ 54.379 us NA - 3.9673 - - 12504 B
AnswerDeserialize ProtobufNet 6.714 us NA - 1.2894 - - 4064 B
AnswerDeserialize SpanJson_ 7.852 us NA - 0.5798 - - 1864 B
AnswerDeserialize SystemTextJson 18.620 us NA - 0.7019 - - 2208 B
AnswerDeserialize Utf8Json_ 12.255 us NA - 0.5798 - - 1864 B
AnswerSerialize BinaryFormatter_ 80.461 us NA 3.64 KB 14.5264 - - 45645 B
AnswerSerialize BsonNet 37.215 us NA 1.38 KB 6.8359 - - 21600 B
AnswerSerialize Ceras_ 5.001 us NA 476.00 B 1.4954 - - 4704 B
AnswerSerialize DataContract_ 23.219 us NA 2.74 KB 4.9133 - - 15424 B
AnswerSerialize FsPickler_ 15.498 us NA 793.00 B 2.1973 - - 6952 B
AnswerSerialize Hyperion_ 11.979 us NA 1015.00 B 1.5259 - - 4824 B
AnswerSerialize Jil_ 9.372 us NA 1.79 KB 3.4027 - - 10688 B
AnswerSerialize JsonNet 25.300 us NA 1.76 KB 3.9978 - - 12576 B
AnswerSerialize MessagePack_v1 3.866 us NA 440.00 B 0.1450 - - 464 B
AnswerSerialize MessagePack_v2 4.135 us NA 440.00 B 0.1450 - - 472 B
AnswerSerialize MessagePackLz4_v1 4.744 us NA 440.00 B 0.1450 - - 472 B
AnswerSerialize MessagePackLz4_v2 5.533 us NA 441.00 B 0.1450 - - 472 B
AnswerSerialize MsgPack_v1_str_lz4 7.129 us NA 852.00 B 0.2747 - - 880 B
AnswerSerialize MsgPack_v1_string 4.790 us NA 1.21 KB 0.3967 - - 1264 B
AnswerSerialize MsgPack_v2_opt 3.976 us NA 434.00 B 0.1450 - - 464 B
AnswerSerialize MsgPack_v2_str_lz4 8.208 us NA 853.00 B 0.2747 - - 872 B
AnswerSerialize MsgPack_v2_string 5.144 us NA 1.21 KB 0.3967 - - 1256 B
AnswerSerialize MsgPackCli 7.544 us NA 489.00 B 1.2054 - - 3792 B
AnswerSerialize Odin_ 28.503 us NA 7.44 KB 3.2654 - - 10328 B
AnswerSerialize ProtobufNet 3.292 us NA 499.00 B 0.4234 - - 1336 B
AnswerSerialize SpanJson_ 5.286 us NA 1.83 KB 0.5951 - - 1888 B
AnswerSerialize SystemTextJson 10.048 us NA 1.76 KB 0.7324 - - 2336 B
AnswerSerialize Utf8Json_ 7.832 us NA 1.83 KB 0.5951 - - 1888 B

Here are the full CSV and log files:
Benchmark.ShortRun_AllSerializerBenchmark_BytesInOut-report.csv
Benchmark.ShortRun_AllSerializerBenchmark_BytesInOut-20210527-121852.log

JesseTG added 5 commits May 20, 2021 17:53

Verified

This commit was signed with the committer’s verified signature.
vszakats Viktor Szakats

Verified

This commit was signed with the committer’s verified signature.
vszakats Viktor Szakats

Verified

This commit was signed with the committer’s verified signature.
vszakats Viktor Szakats
- Benchmark results should now be correct

Verified

This commit was signed with the committer’s verified signature.
vszakats Viktor Szakats

Verified

This commit was signed with the committer’s verified signature.
vszakats Viktor Szakats
- Fix the issue where an exception stopped the entire benchmark suite
- The exception still occurs but outside of Odin's code; it is now logged
@TorVestergaard
Copy link

The numbers are back to being surprising in the opposite direction - not sure how the benchmark is really set up, but those numbers are considerably worse than I'd expect given our own benchmarks and performance/GC measurements. Regardless, the exception issue itself is resolved.

To provide some clarity, the issue was that during Odin Serializer's first-time initialization, it scans all loaded assemblies for extension points such as custom formatters - in this case, it was checking for the presence of an attribute that Odin uses to indicate that it has emitted a given assembly to support an AOT build.

However, checking for any attribute via Assembly.IsDefined can cause the runtime to resolve all attributes declared on that assembly, and it seems one of the loaded assemblies had an attribute on it that depended on the System.Runtime.InteropServices.PInvoke assembly, which could be located. Therefore, Odin checking all assemblies for its own attribute causes an error to occur for a totally unrelated attribute on a loaded assembly. Odin itself has no dependency on System.Runtime.InteropServices.PInvoke.

JesseTG added 2 commits May 29, 2021 14:28

Verified

This commit was signed with the committer’s verified signature.
vszakats Viktor Szakats
@TorVestergaard
Copy link

A SerializationContext and DeserializationContext must be disposed after use, or it will retain its old state, in which case you will get strange results like this. The same context can be disposed multiple times, though. However, I doubt this is what is causing the bad performance we're seeing - the cache claim is very lightweight and should not contribute materially to the serialization or deserialization time. I would suggest the initial using cached context pattern, as that is the "standard" way of claiming and using a context for an individual serialization call.

Odin Serializer on .NET Core is a very new thing and it was just "ported" - perhaps everything else was simply slow on the old Unity runtimes and there the serializer was being compared to antique, slow versions of all other libraries or was just optimized for the performance characteristics for the APIs of those specific runtimes, or something like that.

To clarify my confusion, in our initial benchmarks Odin compared very well against the likes of protobuf-net, and Odin's binary format target has since become ~5-20x faster (depending on circumstance), so seeing these numbers was a bit of a shock after all that optimization work I put in :D

Regardless, it looks like it is at least working now, so these numbers are something I will have to look into later.

@AArnott AArnott merged commit 83f7cb6 into MessagePack-CSharp:master May 29, 2021
@JesseTG JesseTG deleted the cg/compare-to-odin branch May 29, 2021 21:02
@JesseTG
Copy link
Contributor Author

JesseTG commented May 29, 2021

Thanks for merging! What do you want to do about the data on the chart in the README?

@JesseTG
Copy link
Contributor Author

JesseTG commented May 29, 2021

A SerializationContext and DeserializationContext must be disposed after use, or it will retain its old state, in which case you will get strange results like this. The same context can be disposed multiple times, though. However, I doubt this is what is causing the bad performance we're seeing - the cache claim is very lightweight and should not contribute materially to the serialization or deserialization time. I would suggest the initial using cached context pattern, as that is the "standard" way of claiming and using a context for an individual serialization call.

Odin Serializer on .NET Core is a very new thing and it was just "ported" - perhaps everything else was simply slow on the old Unity runtimes and there the serializer was being compared to antique, slow versions of all other libraries or was just optimized for the performance characteristics for the APIs of those specific runtimes, or something like that.

To clarify my confusion, in our initial benchmarks Odin compared very well against the likes of protobuf-net, and Odin's binary format target has since become ~5-20x faster (depending on circumstance), so seeing these numbers was a bit of a shock after all that optimization work I put in :D

Regardless, it looks like it is at least working now, so these numbers are something I will have to look into later.

I guess it's time to update Odin's benchmark suite and add MessagePack-CSharp to it so we can compare them on a level playing field, then?

@AArnott
Copy link
Collaborator

AArnott commented May 29, 2021

What do you want to do about the data on the chart in the README?

I'm working on that now.

@TorVestergaard
Copy link

Yes, we're currently swamped by other tasks but we should get our original benchmarks updated when there's space for it and add libraries like MessagePack-CSharp to the list. I wouldn't be surprised if we've simply fallen behind the times, though, and everything else has gotten much faster while we looked away. It'll be a while, though - updating the serializer benchmarks is not really on our internal roadmap as a priority at the moment, so either I do it in my free time or it'll have to wait for now or until someone else deigns to make a PR for it.

Odin Serializer's core design certainly isn't very suited for the kinds of crazy speeds MessagePack can achieve with the way it's written. For that, a total rewrite of our serializer would be needed. Reading the source here, I'm very impressed - really great job! :D Many things have been done that I've long wanted to do, like the AutomataDictionary for member string resolution to avoid the extra GC and dictionary lookup overhead and the use of "new" .NET stuff like Span and Memory that we just don't have access to yet in Odin given the range of Unity versions we support (we use a fixed byte[] buffer and dump it through a Stream in chunks, which these days... ouch!).

Perhaps one day I'll have time to give our serializer the total revisit it deserves.

@JesseTG
Copy link
Contributor Author

JesseTG commented May 29, 2021

Yes, we're currently swamped by other tasks but we should get our original benchmarks updated when there's space for it and add libraries like MessagePack-CSharp to the list. I wouldn't be surprised if we've simply fallen behind the times, though, and everything else has gotten much faster while we looked away. It'll be a while, though - updating the serializer benchmarks is not really on our internal roadmap as a priority at the moment, so either I do it in my free time or it'll have to wait for now or until someone else deigns to make a PR for it.

Odin Serializer's core design certainly isn't very suited for the kinds of crazy speeds MessagePack can achieve with the way it's written. For that, a total rewrite of our serializer would be needed. Reading the source here, I'm very impressed - really great job! :D Many things have been done that I've long wanted to do, like the AutomataDictionary for member string resolution to avoid the extra GC and dictionary lookup overhead and the use of "new" .NET stuff like Span and Memory that we just don't have access to yet in Odin given the range of Unity versions we support (we use a fixed byte[] buffer and dump it through a Stream in chunks, which these days... ouch!).

Perhaps one day I'll have time to give our serializer the total revisit it deserves.

Is your benchmark suite open source?

@TorVestergaard
Copy link

No, it was something we put together back in the day in a pretty helter-skelter fashion and we don't even have the benchmarking code any more, as it was lost. It was a very simple, naive benchmark with some warmup cycles for each test and then calculating an average over a massive number of iterations. Certainly nothing as rigorous as BenchmarkDotNet, which is why I'm inclined to trust these newer numbers more than our old benchmarks.

Honestly, the pedigree of our benchmarks have long bothered me (and they're very old as well), but there keeps being other stuff to do instead with our core Odin Inspector product that uses the serializer. The serializer has not really been a focus of ours for some time, as it's been working well enough for our purposes and hasn't been cause for issue, while we've been rushing to keep up with Unity's rapid pace on other fronts. Getting them updated would certainly be very welcome, but like I said, it's not really been at the top of our minds.

@JesseTG
Copy link
Contributor Author

JesseTG commented May 29, 2021

No, it was something we put together back in the day in a pretty helter-skelter fashion and we don't even have the benchmarking code any more, as it was lost. It was a very simple, naive benchmark with some warmup cycles for each test and then calculating an average over a massive number of iterations. Certainly nothing as rigorous as BenchmarkDotNet, which is why I'm inclined to trust these newer numbers more than our old benchmarks.

Honestly, the pedigree of our benchmarks have long bothered me (and they're very old as well), but there keeps being other stuff to do instead with our core Odin Inspector product that uses the serializer. The serializer has not really been a focus of ours for some time, as it's been working well enough for our purposes and hasn't been cause for issue, while we've been rushing to keep up with Unity's rapid pace on other fronts. Getting them updated would certainly be very welcome, but like I said, it's not really been at the top of our minds.

Understood, thank you for the info.

@AArnott
Copy link
Collaborator

AArnott commented May 29, 2021

@JesseTG See #1253

@JesseTG
Copy link
Contributor Author

JesseTG commented May 29, 2021

@JesseTG See #1253

I saw, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants