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

Improved compatibility of Format String #30

Merged
merged 6 commits into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

<ItemGroup>
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" Condition="$(TargetFramework) != 'netstandard3.1'" />
<PackageReference Include="ZString" Version="2.1.3" />
<PackageReference Include="ZString" Version="2.2.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
</ItemGroup>

Expand Down
20 changes: 16 additions & 4 deletions sandbox/BenchmarkVsReleasedVersion/BuiltinTypesBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,30 @@ public string FormatN()
_byte, _dt, _dto, _decimal, _double, _guid, _short, _float, _ts, _uint, _ulong, _null, _string, _bool, _enum, _char);
}

[BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
public object CreatePreparedFormat_()
[BenchmarkCategory("CreateUtf16PreparedFormat"), Benchmark(Baseline = true)]
public object CreateUtf16PreparedFormat_()
{
return new PF16(_format);
}

[BenchmarkCategory("CreatePreparedFormat"), Benchmark]
public object CreatePreparedFormatN()
[BenchmarkCategory("CreateUtf16PreparedFormat"), Benchmark]
public object CreateUtf16PreparedFormatN()
{
return new NPF16(_format);
}

[BenchmarkCategory("CreateUtf8PreparedFormat"), Benchmark(Baseline = true)]
public object CreateUtf8PreparedFormat_()
{
return new PF8(_format);
}

[BenchmarkCategory("CreateUtf8PreparedFormat"), Benchmark]
public object CreateUtf8PreparedFormatN()
{
return new NPF8(_format);
}

[BenchmarkCategory("Utf16PreparedFormat"), Benchmark(Baseline = true)]
public string Utf16PreparedFormat_()
{
Expand Down
20 changes: 16 additions & 4 deletions sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,30 @@ public string FormatN()
return NZString.Format(FormatString, x, y);
}

[BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
public object CreatePreparedFormat_()
[BenchmarkCategory("CreateUtf16PreparedFormat"), Benchmark(Baseline = true)]
public object CreateUtf16PreparedFormat_()
{
return new Utf16PreparedFormat<int, int>(FormatString);
}

[BenchmarkCategory("CreatePreparedFormat"), Benchmark]
public object CreatePreparedFormatN()
[BenchmarkCategory("CreateUtf16PreparedFormat"), Benchmark]
public object CreateUtf16PreparedFormatN()
{
return new NewZString::Cysharp.Text.Utf16PreparedFormat<int, int>(FormatString);
}

[BenchmarkCategory("CreateUtf8PreparedFormat"), Benchmark(Baseline = true)]
public object CreateUtf8PreparedFormat_()
{
return new Utf8PreparedFormat<int, int>(FormatString);
}

[BenchmarkCategory("CreateUtf8PreparedFormat"), Benchmark]
public object CreateUtf8PreparedFormatN()
{
return new NewZString::Cysharp.Text.Utf8PreparedFormat<int, int>(FormatString);
}

[BenchmarkCategory("Utf16PreparedFormat"), Benchmark(Baseline = true)]
public string Utf16PreparedFormat_()
{
Expand Down
10 changes: 9 additions & 1 deletion sandbox/PerfBenchmark/Benchmarks/FormatBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,18 @@ public class FormatBenchmark
int y;
string format;
StringBuilder stringBuilder;
Utf16PreparedFormat<int, int> preparedFormat;

public FormatBenchmark()
{
x = int.Parse("100");
y = int.Parse("200");
format = "x:{0}, y:{1}";
stringBuilder = new StringBuilder();
preparedFormat = new Utf16PreparedFormat<int,int>(format);
}

[Benchmark]
[Benchmark(Baseline = true)]
public string StringFormat()
{
return string.Format(format, x, y);
Expand All @@ -35,6 +37,12 @@ public string ZStringFormat()
return ZString.Format(format, x, y);
}

[Benchmark]
public string ZStringPreparedFormat()
{
return preparedFormat.Format(x, y);
}

[Benchmark]
public string StringFormatterFormat()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public SixObjectConcatBenchmark()
z = int.Parse("555");
}

[Benchmark]
[Benchmark(Baseline = true)]
public string StringPlus()
{
return "x:" + x + " y:" + y + " z:" + z;
Expand Down
5 changes: 5 additions & 0 deletions src/ZString.Unity/Assets/Scripts/ZString/ExceptionUtil.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,10 @@ internal static void ThrowFormatException()
{
throw new FormatException("Index (zero based) must be greater than or equal to zero and less than the size of the argument list.");
}

internal static void ThrowFormatError()
{
throw new FormatException("Input string was not in a correct format.");
}
}
}
172 changes: 172 additions & 0 deletions src/ZString.Unity/Assets/Scripts/ZString/FormatHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using System;
using System.Text;
using System.Buffers;
using System.Runtime.CompilerServices;

namespace Cysharp.Text
{
internal static partial class Utf16FormatHelper
{
const char sp = (char)' ';

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FormatTo<TBufferWriter, T>(ref TBufferWriter sb, T arg, int width, ReadOnlySpan<char> format, string argName)
where TBufferWriter : IBufferWriter<char>
{
if (width <= 0) // leftJustify
{
var span = sb.GetSpan(0);
if (!Utf16ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, span, out var argWritten, format))
{
sb.Advance(0);
span = sb.GetSpan(Math.Max(span.Length + 1, argWritten));
if (!Utf16ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, span, out argWritten, format))
{
ExceptionUtil.ThrowArgumentException(argName);
}
}
sb.Advance(argWritten);

width *= -1;
int padding = width - argWritten;
if (width > 0 && padding > 0)
{
var paddingSpan = sb.GetSpan(padding);
paddingSpan.Fill(sp);
sb.Advance(padding);
}
}
else
{
FormatToRightJustify(ref sb, arg, width, format, argName);
}
}

private static void FormatToRightJustify<TBufferWriter, T>(ref TBufferWriter sb, T arg, int width, ReadOnlySpan<char> format, string argName)
where TBufferWriter : IBufferWriter<char>
{
if (typeof(T) == typeof(string))
{
var s = Unsafe.As<string>(arg);
int padding = width - s.Length;
if (padding > 0)
{
var paddingSpan = sb.GetSpan(padding);
paddingSpan.Fill(sp);
sb.Advance(padding);
}

var span = sb.GetSpan(s.Length);
s.AsSpan().CopyTo(span);
sb.Advance(s.Length);
}
else
{
Span<char> s = stackalloc char[typeof(T).IsValueType ? Unsafe.SizeOf<T>() * 8 : 1024];

if (!Utf16ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, s, out var charsWritten, format))
{
s = stackalloc char[s.Length * 2];
if (!Utf16ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, s, out charsWritten, format))
{
ExceptionUtil.ThrowArgumentException(argName);
}
}

int padding = width - charsWritten;
if (padding > 0)
{
var paddingSpan = sb.GetSpan(padding);
paddingSpan.Fill(sp);
sb.Advance(padding);
}

var span = sb.GetSpan(charsWritten);
s.CopyTo(span);
sb.Advance(charsWritten);
}
}
}

internal static partial class Utf8FormatHelper
{
const byte sp = (byte)' ';

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void FormatTo<TBufferWriter, T>(ref TBufferWriter sb, T arg, int width, StandardFormat format, string argName)
where TBufferWriter : IBufferWriter<byte>
{
if (width <= 0) // leftJustify
{
var span = sb.GetSpan(0);
if (!Utf8ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, span, out var argWritten, format))
{
sb.Advance(0);
span = sb.GetSpan(Math.Max(span.Length + 1, argWritten));
if (!Utf8ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, span, out argWritten, format))
{
ExceptionUtil.ThrowArgumentException(argName);
}
}
sb.Advance(argWritten);

width *= -1;
int padding = width - argWritten;
if (width > 0 && padding > 0)
{
var paddingSpan = sb.GetSpan(padding);
paddingSpan.Fill(sp);
sb.Advance(padding);
}
}
else
{
FormatToRightJustify(ref sb, arg, width, format, argName);
}
}

private static void FormatToRightJustify<TBufferWriter, T>(ref TBufferWriter sb, T arg, int width, StandardFormat format, string argName)
where TBufferWriter : IBufferWriter<byte>
{
if (typeof(T) == typeof(string))
{
var s = Unsafe.As<string>(arg);
int padding = width - s.Length;
if (padding > 0)
{
var paddingSpan = sb.GetSpan(padding);
paddingSpan.Fill(sp);
sb.Advance(padding);
}

ZString.AppendChars(ref sb, s.AsSpan());
}
else
{
Span<byte> s = stackalloc byte[typeof(T).IsValueType ? Unsafe.SizeOf<T>() * 8 : 1024];

if (!Utf8ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, s, out var charsWritten, format))
{
s = stackalloc byte[s.Length * 2];
if (!Utf8ValueStringBuilder.FormatterCache<T>.TryFormatDelegate(arg, s, out charsWritten, format))
{
ExceptionUtil.ThrowArgumentException(argName);
}
}

int padding = width - charsWritten;
if (padding > 0)
{
var paddingSpan = sb.GetSpan(padding);
paddingSpan.Fill(sp);
sb.Advance(padding);
}

var span = sb.GetSpan(charsWritten);
s.CopyTo(span);
sb.Advance(charsWritten);
}
}
}

}
Loading