Skip to content

Commit 5cb589c

Browse files
Merge pull request #188 from PhyxionNL/patch-1
Avoid most allocations in GetExtensionFromUri
2 parents f73155d + 9e0c1cb commit 5cb589c

File tree

1 file changed

+37
-30
lines changed

1 file changed

+37
-30
lines changed

src/ImageSharp.Web/FormatUtilities.cs

Lines changed: 37 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,71 +19,78 @@ namespace SixLabors.ImageSharp.Web
1919
/// </summary>
2020
public sealed class FormatUtilities
2121
{
22-
private readonly IImageFormat[] imageFormats;
23-
private readonly Dictionary<IImageFormat, string[]> fileExtensions = new Dictionary<IImageFormat, string[]>();
24-
private readonly Dictionary<string, string> fileExtension = new Dictionary<string, string>();
22+
private readonly List<string> extensions = new();
23+
private readonly Dictionary<string, string> extensionsByMimeType = new();
2524

2625
/// <summary>
27-
/// Initializes a new instance of the <see cref="FormatUtilities"/> class.
26+
/// Initializes a new instance of the <see cref="FormatUtilities" /> class.
2827
/// </summary>
2928
/// <param name="options">The middleware options.</param>
3029
public FormatUtilities(IOptions<ImageSharpMiddlewareOptions> options)
3130
{
3231
Guard.NotNull(options, nameof(options));
3332

34-
// The formats contained in the configuration are used a lot in hash generation
35-
// so we need them to be enumerated to remove allocations and allow indexing.
36-
this.imageFormats = options.Value.Configuration.ImageFormats.ToArray();
37-
for (int i = 0; i < this.imageFormats.Length; i++)
33+
foreach (IImageFormat imageFormat in options.Value.Configuration.ImageFormats)
3834
{
39-
string[] extensions = this.imageFormats[i].FileExtensions.ToArray();
40-
this.fileExtensions[this.imageFormats[i]] = extensions;
41-
this.fileExtension[this.imageFormats[i].DefaultMimeType] = extensions[0];
35+
string[] extensions = imageFormat.FileExtensions.ToArray();
36+
37+
foreach (string extension in extensions)
38+
{
39+
this.extensions.Add(extension);
40+
}
41+
42+
this.extensionsByMimeType[imageFormat.DefaultMimeType] = extensions[0];
4243
}
4344
}
4445

4546
/// <summary>
4647
/// Gets the file extension for the given image uri.
4748
/// </summary>
4849
/// <param name="uri">The full request uri.</param>
49-
/// <returns>The <see cref="string"/>.</returns>
50+
/// <returns>The <see cref="string" />.</returns>
5051
[MethodImpl(MethodImplOptions.AggressiveInlining)]
5152
public string GetExtensionFromUri(string uri)
5253
{
53-
// TODO: Investigate using span to reduce allocations here.
54-
string[] parts = uri.Split('?');
55-
if (parts.Length > 1 && QueryHelpers.ParseQuery(parts[1]).TryGetValue(FormatWebProcessor.Format, out StringValues ext))
54+
int query = uri.IndexOf('?');
55+
ReadOnlySpan<char> path;
56+
57+
if (query > -1)
58+
{
59+
if (uri.Contains(FormatWebProcessor.Format, StringComparison.OrdinalIgnoreCase) && QueryHelpers.ParseQuery(uri.Substring(query)).TryGetValue(FormatWebProcessor.Format, out StringValues ext))
60+
{
61+
return ext;
62+
}
63+
64+
path = uri.AsSpan(0, query);
65+
}
66+
else
5667
{
57-
return ext;
68+
path = uri;
5869
}
5970

60-
string path = parts[0];
61-
string extension = null;
62-
int index = 0;
63-
for (int i = 0; i < this.imageFormats.Length; i++)
71+
int extensionIndex;
72+
if ((extensionIndex = path.LastIndexOf('.')) != -1)
6473
{
65-
for (int j = 0; j < this.fileExtensions[this.imageFormats[i]].Length; j++)
74+
ReadOnlySpan<char> pathExtension = path.Slice(extensionIndex + 1);
75+
76+
foreach (string extension in this.extensions)
6677
{
67-
int li = path.LastIndexOf($".{this.fileExtensions[this.imageFormats[i]][j]}", StringComparison.OrdinalIgnoreCase);
68-
if (li < index)
78+
if (pathExtension.Equals(extension, StringComparison.OrdinalIgnoreCase))
6979
{
70-
continue;
80+
return extension;
7181
}
72-
73-
index = li;
74-
extension = this.fileExtensions[this.imageFormats[i]][j];
7582
}
7683
}
7784

78-
return extension;
85+
return null;
7986
}
8087

8188
/// <summary>
8289
/// Gets the correct extension for the given content type (mime-type).
8390
/// </summary>
8491
/// <param name="contentType">The content type (mime-type).</param>
85-
/// <returns>The <see cref="string"/>.</returns>
92+
/// <returns>The <see cref="string" />.</returns>
8693
[MethodImpl(MethodImplOptions.AggressiveInlining)]
87-
public string GetExtensionFromContentType(string contentType) => this.fileExtension[contentType];
94+
public string GetExtensionFromContentType(string contentType) => this.extensionsByMimeType[contentType];
8895
}
8996
}

0 commit comments

Comments
 (0)