fix: Optimizes Point2D by implementing ISpanFormattable (#1203)

Point2D implements a basic TryFormat function. Only a single format is supported: "(X, Y)" where X and Y are base 10 integers.

Uses recent improvements in string interpolation to write the characters to the destination without first allocating memory for boxed arguments or intermediate strings.

This is a partial solution to issue #1067. A similar solution well be implemented in other classes in Geometry if it looks promising.

Benchmarks
---
d6f8dabf3c/Program.cs

Before change:
```
BenchmarkDotNet=v0.13.2, OS=ubuntu 22.04
Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.402
  [Host]     : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2


|                           Method |      Mean |    Error |   StdDev |   Gen0 | Allocated |
|--------------------------------- |----------:|---------:|---------:|-------:|----------:|
|                     CallToString |  44.27 ns | 0.068 ns | 0.063 ns | 0.0063 |      40 B |
|               InterpolatedString | 110.64 ns | 0.316 ns | 0.295 ns | 0.0126 |      80 B |
| InterpolatedStringMultiplePoints | 211.36 ns | 0.349 ns | 0.326 ns | 0.0293 |     184 B |
```

After change:
```
BenchmarkDotNet=v0.13.2, OS=ubuntu 22.04
Intel Core i7-9700K CPU 3.60GHz (Coffee Lake), 1 CPU, 8 logical and 8 physical cores
.NET SDK=6.0.402
  [Host]     : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2
  DefaultJob : .NET 6.0.10 (6.0.1022.47605), X64 RyuJIT AVX2

|                           Method |      Mean |    Error |   StdDev |   Gen0 | Allocated |
|--------------------------------- |----------:|---------:|---------:|-------:|----------:|
|                     CallToString |  55.13 ns | 0.826 ns | 0.772 ns | 0.0063 |      40 B |
|               InterpolatedString |  55.73 ns | 0.954 ns | 0.892 ns | 0.0063 |      40 B |
| InterpolatedStringMultiplePoints | 105.42 ns | 0.151 ns | 0.134 ns | 0.0101 |      64 B |
```
This commit is contained in:
Harley Holt 2022-10-25 12:08:35 -07:00 • committed by GitHub
parent 5797c85d75
commit be19d9aae6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 3 deletions

View file

@ -0,0 +1,17 @@
using System;
using Xunit;
namespace Server.Tests
{
public class Point2DTests
{
[Fact]
public void TestToString()
{
Assert.Equal("(0, 0)", new Point2D(0, 0).ToString());
Assert.Equal("(1, 1)", new Point2D(1, 1).ToString());
Assert.Equal($"({Int32.MaxValue}, {Int32.MaxValue})", new Point2D(Int32.MaxValue, Int32.MaxValue).ToString());
Assert.Equal($"({Int32.MinValue}, {Int32.MinValue})", new Point2D(Int32.MinValue, Int32.MinValue).ToString());
}
}
}

View file

@ -20,7 +20,7 @@ namespace Server;
[Parsable]
public struct Point2D
: IPoint2D, IComparable<Point2D>, IComparable<IPoint2D>, IEquatable<object>, IEquatable<Point2D>,
IEquatable<IPoint2D>
IEquatable<IPoint2D>, ISpanFormattable
{
internal int m_X;
internal int m_Y;
@ -51,8 +51,6 @@ public struct Point2D
{
}
public override string ToString() => $"({m_X}, {m_Y})";
public static Point2D Parse(string value)
{
var start = value.IndexOfOrdinal('(');
@ -112,4 +110,26 @@ public struct Point2D
var xComparison = m_X.CompareTo(other.X);
return xComparison != 0 ? xComparison : m_Y.CompareTo(other.Y);
}
public bool TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider provider)
{
return destination.TryWrite(provider, $"({m_X}, {m_Y})", out charsWritten);
}
public override string ToString() {
// Maximum number of characters that are needed to represent this:
// 4 characters for (, )
// Up to 11 characters to represent each integer
Span<char> span = stackalloc char[4+11*2];
this.TryFormat(span, out var charsWritten, null, null);
return span.Slice(0, charsWritten).ToString();
}
public string ToString(string format, IFormatProvider formatProvider)
{
// format and formatProvider are not doing anything right now, so use the
// default ToString implementation.
return ToString();
}
}