From 6e0a9b9eae47461f40132251071583d65fa67306 Mon Sep 17 00:00:00 2001 From: Pavel Djundik Date: Wed, 14 Feb 2024 13:51:52 +0200 Subject: [PATCH] Reduce allocations in FindEntry by manually doing binary search --- ValvePak/ValvePak.Test/WriteTest.cs | 1 - ValvePak/ValvePak/Package.cs | 64 ++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 15 deletions(-) diff --git a/ValvePak/ValvePak.Test/WriteTest.cs b/ValvePak/ValvePak.Test/WriteTest.cs index 555ad05..b64ccb2 100644 --- a/ValvePak/ValvePak.Test/WriteTest.cs +++ b/ValvePak/ValvePak.Test/WriteTest.cs @@ -1,4 +1,3 @@ -using System; using System.IO; using System.Text; using NUnit.Framework; diff --git a/ValvePak/ValvePak/Package.cs b/ValvePak/ValvePak/Package.cs index 376e2d1..6744219 100644 --- a/ValvePak/ValvePak/Package.cs +++ b/ValvePak/ValvePak/Package.cs @@ -195,7 +195,7 @@ public PackageEntry FindEntry(string filePathStr) return default; } - // Remove the trailing slash + // Remove the trailing and leading slash directory = directory.Trim(DirectorySeparatorChar); // If the directory is empty after trimming, set it to a space to match Valve's behaviour @@ -204,29 +204,65 @@ public PackageEntry FindEntry(string filePathStr) directory = Space; } - /// Searches for a given file entry in the file list after it has been optimized with . - /// This also supports case insensitive search by using a different . - if (Comparer != null) + int hi = entriesForExtension.Count - 1; + + if (Comparer == null) { - var searchEntry = new PackageEntry + for (var i = 0; i <= hi; i++) // Don't use foreach { - DirectoryName = directory.ToString(), - FileName = fileName.ToString(), - TypeName = extension, - }; - - var index = entriesForExtension.BinarySearch(searchEntry, Comparer); + var entry = entriesForExtension[i]; + if (directory.SequenceEqual(entry.DirectoryName) && fileName.SequenceEqual(entry.FileName)) + { + return entry; + } + } - return index < 0 ? default : entriesForExtension[index]; + return default; } - for(var i = 0; i < entriesForExtension.Count; i++) // Don't use foreach + /// Searches for a given file entry in the file list after it has been optimized with . + /// This also supports case insensitive search by using a different . + /// + /// Manually implement binary search to avoid allocating new strings for file and directory names. + /// See for reference. + + int lo = 0; + + while (lo <= hi) { + var i = (int)(((uint)hi + (uint)lo) >> 1); var entry = entriesForExtension[i]; - if (directory.SequenceEqual(entry.DirectoryName) && fileName.SequenceEqual(entry.FileName)) + + /// This code must match + var comp = fileName.Length.CompareTo(entry.FileName.Length); + + if (comp == 0) + { + comp = directory.Length.CompareTo(entry.DirectoryName.Length); + + if (comp == 0) + { + comp = fileName.CompareTo(entry.FileName, Comparer.Comparison); + + if (comp == 0) + { + comp = directory.CompareTo(entry.DirectoryName, Comparer.Comparison); + } + } + } + + if (comp == 0) { return entry; } + else if (comp > 0) + { + lo = i + 1; + } + else + { + hi = i - 1; + } } return default;