Skip to content

Commit

Permalink
Reduce allocations in FindEntry by manually doing binary search
Browse files Browse the repository at this point in the history
  • Loading branch information
xPaw committed Feb 14, 2024
1 parent e8a863c commit 6e0a9b9
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 15 deletions.
1 change: 0 additions & 1 deletion ValvePak/ValvePak.Test/WriteTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System;
using System.IO;
using System.Text;
using NUnit.Framework;
Expand Down
64 changes: 50 additions & 14 deletions ValvePak/ValvePak/Package.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 <see cref="OptimizeEntriesForBinarySearch"/>.
/// This also supports case insensitive search by using a different <see cref="StringComparison"/>.
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 <see cref="OptimizeEntriesForBinarySearch"/>.
/// This also supports case insensitive search by using a different <see cref="StringComparison"/>.
///
/// Manually implement binary search to avoid allocating new strings for file and directory names.
/// See <see cref="MemoryExtensions.BinarySearch{T}(ReadOnlySpan{T}, IComparable{T})"/> 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 <see cref="CaseInsensitivePackageEntryComparer.Compare(PackageEntry, PackageEntry)"/>
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;
Expand Down

0 comments on commit 6e0a9b9

Please sign in to comment.