Skip to content

Commit 9fbee1d

Browse files
authored
Stop UTF8 decoding/reencoding in Fortunes platform (#1806)
1 parent 2b7e6ad commit 9fbee1d

File tree

7 files changed

+77
-24
lines changed

7 files changed

+77
-24
lines changed

src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BenchmarkApplication.Fortunes.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
using System;
45
using System.Collections.Generic;
6+
using System.Diagnostics;
57
using System.IO.Pipelines;
68
using System.Text.Encodings.Web;
79
using System.Threading.Tasks;
@@ -43,6 +45,37 @@ private void OutputFortunes(PipeWriter pipeWriter, List<Fortune> model)
4345
// Date header
4446
writer.Write(DateHeader.HeaderBytes);
4547

48+
var bodyStart = writer.Buffered;
49+
// Body
50+
writer.Write(_fortunesTableStart);
51+
foreach (var item in model)
52+
{
53+
writer.Write(_fortunesRowStart);
54+
writer.WriteNumeric((uint)item.Id);
55+
writer.Write(_fortunesColumn);
56+
HtmlEncoder.EncodeUtf8(item.Message.AsSpan(), writer.Span, out var bytesConsumed, out var bytesWritten, isFinalBlock: true);
57+
Debug.Assert(bytesConsumed == item.Message.Length, "Not enough remaining space in the buffer");
58+
writer.Advance(bytesWritten);
59+
writer.Write(_fortunesRowEnd);
60+
}
61+
writer.Write(_fortunesTableEnd);
62+
lengthWriter.WriteNumeric((uint)(writer.Buffered - bodyStart));
63+
64+
writer.Commit();
65+
}
66+
67+
private void OutputFortunes(PipeWriter pipeWriter, List<FortuneDapper> model)
68+
{
69+
var writer = GetWriter(pipeWriter, sizeHint: 1600); // in reality it's 1361
70+
71+
writer.Write(_fortunesPreamble);
72+
73+
var lengthWriter = writer;
74+
writer.Write(_contentLengthGap);
75+
76+
// Date header
77+
writer.Write(DateHeader.HeaderBytes);
78+
4679
var bodyStart = writer.Buffered;
4780
// Body
4881
writer.Write(_fortunesTableStart);

src/BenchmarksApps/TechEmpower/PlatformBenchmarks/BufferWriter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public void Advance(int count)
4545
}
4646

4747
[MethodImpl(MethodImplOptions.AggressiveInlining)]
48-
public void Write(ReadOnlySpan<byte> source)
48+
public void Write(scoped ReadOnlySpan<byte> source)
4949
{
5050
if (_span.Length >= source.Length)
5151
{
@@ -78,7 +78,7 @@ private void EnsureMore(int count = 0)
7878
_span = _output.GetSpan(count);
7979
}
8080

81-
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
81+
private void WriteMultiBuffer(scoped ReadOnlySpan<byte> source)
8282
{
8383
while (source.Length > 0)
8484
{

src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/DapperDb.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,20 @@ public class DapperDb
1313
public DapperDb(AppSettings appSettings)
1414
=> _connectionString = appSettings.ConnectionString;
1515

16-
public async Task<List<Fortune>> LoadFortunesRows()
16+
public async Task<List<FortuneDapper>> LoadFortunesRows()
1717
{
18-
List<Fortune> result;
18+
List<FortuneDapper> result;
1919

2020
using (var db = new NpgsqlConnection(_connectionString))
2121
{
2222
// Note: don't need to open connection if only doing one thing; let dapper do it
23-
result = (await db.QueryAsync<Fortune>("SELECT id, message FROM fortune")).AsList();
23+
result = (await db.QueryAsync<FortuneDapper>("SELECT id, message FROM fortune")).AsList();
2424
}
2525

26-
result.Add(new Fortune(id: 0, message: "Additional fortune added at request time." ));
26+
result.Add(new FortuneDapper(id: 0, message: "Additional fortune added at request time." ));
2727
result.Sort();
2828

2929
return result;
3030
}
3131
}
32-
}
32+
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
// Copyright (c) .NET Foundation. All rights reserved.
2-
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

44
using System;
55

66
namespace PlatformBenchmarks
77
{
88
public readonly struct Fortune : IComparable<Fortune>, IComparable
99
{
10-
public Fortune(int id, string message)
10+
public Fortune(int id, byte[] message)
1111
{
1212
Id = id;
1313
Message = message;
1414
}
1515

1616
public int Id { get; }
1717

18-
public string Message { get; }
18+
public byte[] Message { get; }
1919

2020
public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used");
2121

2222
// Performance critical, using culture insensitive comparison
23-
public int CompareTo(Fortune other) => string.CompareOrdinal(Message, other.Message);
23+
public int CompareTo(Fortune other) => Message.AsSpan().SequenceCompareTo(other.Message.AsSpan());
2424
}
2525
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System;
5+
6+
namespace PlatformBenchmarks
7+
{
8+
public readonly struct FortuneDapper : IComparable<FortuneDapper>, IComparable
9+
{
10+
public FortuneDapper(int id, string message)
11+
{
12+
Id = id;
13+
Message = message;
14+
}
15+
16+
public int Id { get; }
17+
18+
public string Message { get; }
19+
20+
public int CompareTo(object obj) => throw new InvalidOperationException("The non-generic CompareTo should not be used");
21+
22+
// Performance critical, using culture insensitive comparison
23+
public int CompareTo(FortuneDapper other) => string.CompareOrdinal(Message, other.Message);
24+
}
25+
}

src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/FortuneEf.cs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,7 @@ public class FortuneEf : IComparable<FortuneEf>, IComparable
1717
[Required]
1818
public string Message { get; set; }
1919

20-
public int CompareTo(object obj)
21-
{
22-
return CompareTo((FortuneEf)obj);
23-
}
24-
25-
public int CompareTo(FortuneEf other)
26-
{
27-
return String.CompareOrdinal(Message, other.Message);
28-
}
20+
public int CompareTo(object obj) => CompareTo((FortuneEf)obj);
21+
public int CompareTo(FortuneEf other) => String.CompareOrdinal(Message, other.Message);
2922
}
30-
}
23+
}

src/BenchmarksApps/TechEmpower/PlatformBenchmarks/Data/RawDb.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -255,17 +255,19 @@ public async Task<List<Fortune>> LoadFortunesRows()
255255
result.Add(new Fortune
256256
(
257257
id: rdr.GetInt32(0),
258-
message: rdr.GetString(1)
258+
message: rdr.GetFieldValue<byte[]>(1)
259259
));
260260
}
261261
}
262262

263-
result.Add(new Fortune(id: 0, message: "Additional fortune added at request time." ));
263+
result.Add(new Fortune(id: 0, AdditionalFortune));
264264
result.Sort();
265265

266266
return result;
267267
}
268268

269+
private readonly byte[] AdditionalFortune = "Additional fortune added at request time."u8.ToArray();
270+
269271
private (NpgsqlCommand readCmd, NpgsqlParameter<int> idParameter) CreateReadCommand(NpgsqlConnection connection)
270272
{
271273
#if NET6_0_OR_GREATER

0 commit comments

Comments
 (0)