From 28f434c3f3415181901a803985d05b19b0d18107 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Sat, 17 Aug 2024 21:30:26 -0400 Subject: [PATCH] Remove O(N) List.RemoveAt from RegexCache.Add The cache maintains a list of all regexes in the cache, which it uses to be able to randomly access members as part of evicting when the cache is full and an item needs to be replaced. When evicting, it randomly samples a subset of the items, and then removes whichever is found to have the oldest timestamp. That RemoveAt call is O(N), as elements need to be shifted down. But the order of the list doesn't actually matter, so we can make that instead be O(1) by just moving the last element into the removal slot (overwriting the one to be removed) and then shrinking the size of the list. --- .../src/System/Text/RegularExpressions/Regex.Cache.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.Cache.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.Cache.cs index c8759484567728..35dddc889d85f6 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.Cache.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.Cache.cs @@ -248,9 +248,12 @@ private static void Add(Key key, Regex regex) } } - // Remove the key found to have the smallest access stamp. + // Remove the key found to have the smallest access stamp. List ordering isn't important, so rather than + // just removing the element at minListIndex, which would result in an O(N) shift down, we copy the last + // element to minListIndex, and then remove the last. (If minListIndex is the last, this is a no-op.) s_cacheDictionary.TryRemove(s_cacheList[minListIndex].Key, out _); - s_cacheList.RemoveAt(minListIndex); + s_cacheList[minListIndex] = s_cacheList[^1]; + s_cacheList.RemoveAt(s_cacheList.Count - 1); } // Finally add the regex.