From f58435e43189a1c0281a01305301a0d97ee5e82c Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Mon, 4 Sep 2023 12:17:33 -0400 Subject: [PATCH] Optimized ImapTokenCache.GetOrAdd() by fast-pathing charset conversion --- MailKit/Net/Imap/ImapTokenCache.cs | 35 +++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/MailKit/Net/Imap/ImapTokenCache.cs b/MailKit/Net/Imap/ImapTokenCache.cs index 1150650b31..b6da1c6457 100644 --- a/MailKit/Net/Imap/ImapTokenCache.cs +++ b/MailKit/Net/Imap/ImapTokenCache.cs @@ -27,6 +27,8 @@ using System; using System.Text; using System.Collections.Generic; +using System.Buffers; +using System.Diagnostics; namespace MailKit.Net.Imap { @@ -59,7 +61,7 @@ public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder) { lock (cache) { // lookupKey is a pre-allocated key used for lookups - lookupKey.Init (decoders, chars, type, builder.GetBuffer (), builder.Length); + lookupKey.Init (decoders, chars, type, builder.GetBuffer (), builder.Length, out var decoder, out int charsNeeded); if (cache.TryGetValue (lookupKey, out var node)) { // move the node to the head of the list @@ -70,7 +72,27 @@ public ImapToken AddOrGet (ImapTokenType type, ByteArrayBuilder builder) return node.Value.Token; } - var token = new ImapToken (type, builder.ToString ()); + string value; + + if (charsNeeded <= chars.Length) { + // If the number of needed chars is <= the length of our temp buffer, then it should all be contained. + value = new string (chars, 0, charsNeeded); + } else { + var buffer = ArrayPool.Shared.Rent (charsNeeded); + try { + // Note: This conversion should go flawlessly, so we'll just Debug.Assert() our expectations. + decoder.Convert (builder.GetBuffer (), 0, builder.Length, buffer, 0, buffer.Length, true, out var bytesUsed, out var charsUsed, out var completed); + Debug.Assert (bytesUsed == builder.Length); + Debug.Assert (charsUsed == charsNeeded); + Debug.Assert (completed); + value = new string (buffer, 0, charsUsed); + } finally { + ArrayPool.Shared.Return (buffer); + decoder.Reset (); + } + } + + var token = new ImapToken (type, value); if (cache.Count >= capacity) { // remove the least recently used token @@ -112,7 +134,7 @@ public ImapTokenKey (ImapTokenType type, string key) Init (type, key); } - public void Init (Decoder[] decoders, char[] chars, ImapTokenType type, byte[] key, int length) + public void Init (Decoder[] decoders, char[] chars, ImapTokenType type, byte[] key, int length, out Decoder correctDecoder, out int charsNeeded) { this.type = type; this.byteArrayKey = key; @@ -122,13 +144,19 @@ public void Init (Decoder[] decoders, char[] chars, ImapTokenType type, byte[] k var hash = new HashCode (); hash.Add ((int) type); + correctDecoder = null; + charsNeeded = 0; + foreach (var decoder in decoders) { bool completed; int index = 0; + correctDecoder = decoder; + do { try { decoder.Convert (key, index, length - index, chars, 0, chars.Length, true, out var bytesUsed, out var charsUsed, out completed); + charsNeeded += charsUsed; index += bytesUsed; for (int i = 0; i < charsUsed; i++) @@ -138,6 +166,7 @@ public void Init (Decoder[] decoders, char[] chars, ImapTokenType type, byte[] k hash = new HashCode (); hash.Add ((int) type); completed = false; + charsNeeded = 0; break; } } while (!completed);