-
Notifications
You must be signed in to change notification settings - Fork 20
Optimize redirect collection #166
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -19,14 +19,9 @@ public class CustomRedirectCollection : IEnumerable<CustomRedirect> | |
| private readonly IEnumerable<INotFoundHandler> _providers = new List<INotFoundHandler>(); | ||
|
|
||
| /// <summary> | ||
| /// Hashtable for quick lookup of old urls | ||
| /// URLs sorted Z-A. | ||
| /// </summary> | ||
| private readonly Dictionary<string, CustomRedirect> _quickLookupTable = new(StringComparer.OrdinalIgnoreCase); | ||
|
|
||
| /// <summary> | ||
| /// Cache of URLs sorted ZA for look up of partially matched URLs | ||
| /// </summary> | ||
| private KeyValuePair<string, CustomRedirect>[] _redirectsZACache; | ||
| private readonly SortedDictionary<string, CustomRedirect> _redirectsZA = new(new ReverseStringComparer()); | ||
|
|
||
| public CustomRedirectCollection() | ||
| { | ||
|
|
@@ -39,7 +34,7 @@ public CustomRedirectCollection(IEnumerable<INotFoundHandler> providers) | |
|
|
||
| public IEnumerator<CustomRedirect> GetEnumerator() | ||
| { | ||
| return _quickLookupTable.Values.GetEnumerator(); | ||
| return _redirectsZA.Values.GetEnumerator(); | ||
| } | ||
|
|
||
| IEnumerator IEnumerable.GetEnumerator() | ||
|
|
@@ -79,34 +74,19 @@ public CustomRedirect Find(Uri urlNotFound) | |
| public void Add(CustomRedirect customRedirect) | ||
| { | ||
| var oldUrl = HttpUtility.UrlDecode(customRedirect.OldUrl); | ||
| if (_quickLookupTable.ContainsKey(oldUrl)) | ||
| { | ||
| _quickLookupTable[oldUrl] = customRedirect; | ||
| return; | ||
| } | ||
|
|
||
| // Add to quick look up table too | ||
| _quickLookupTable.Add(oldUrl, customRedirect); | ||
|
|
||
| // clean cache | ||
| _redirectsZACache = null; | ||
| _redirectsZA[oldUrl] = customRedirect; | ||
| } | ||
|
|
||
| private CustomRedirect FindInternal(string url) | ||
| { | ||
| url = HttpUtility.UrlDecode(url) ?? string.Empty; | ||
|
Comment on lines
80
to
82
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
| if (_quickLookupTable.TryGetValue(url, out var redirect)) | ||
| if (_redirectsZA.TryGetValue(url, out var redirect)) | ||
| { | ||
| return redirect; | ||
| } | ||
|
|
||
| // working with local copy to avoid multi-threading issues | ||
| var redirectsZA = _redirectsZACache; | ||
| if (redirectsZA == null) | ||
| { | ||
| redirectsZA = _quickLookupTable.OrderByDescending(x => x.Key, StringComparer.OrdinalIgnoreCase).ToArray(); | ||
| _redirectsZACache = redirectsZA; | ||
| } | ||
| var redirectsZA = _redirectsZA.ToArray(); | ||
|
Comment on lines
88
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While creating a local copy of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This array used to be calculated and cached. With the proposed change, the array is re-created on every call to FindInternal, likely increasing memory usage and garbage collections. FindInternal is expected to be called multiple times during public Find method. |
||
|
|
||
| var path = url.AsPathSpan(); | ||
| var query = url.AsQuerySpan(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // Copyright (c) Geta Digital. All rights reserved. | ||
| // Licensed under Apache-2.0. See the LICENSE file in the project root for more information | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
|
|
||
| namespace Geta.NotFoundHandler.Core.Redirects; | ||
|
|
||
| /// <summary> | ||
| /// A comparer that sorts strings in reverse order. | ||
| /// </summary> | ||
| public class ReverseStringComparer : IComparer<string> | ||
| { | ||
| private readonly IComparer<string> _baseComparer; | ||
|
|
||
| public ReverseStringComparer() : this(StringComparer.OrdinalIgnoreCase) { } | ||
|
|
||
| public ReverseStringComparer(IComparer<string> baseComparer) | ||
| { | ||
| _baseComparer = baseComparer ?? throw new ArgumentNullException(nameof(baseComparer)); | ||
| } | ||
|
|
||
| public int Compare(string x, string y) | ||
| { | ||
| return _baseComparer.Compare(y, x); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switching from Dictionary (O(1) lookup) to SortedDictionary (O(log n)) might actually hurt performance, especially since:
https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?view=net-9.0
https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.sorteddictionary-2?view=net-9.0