Upload FastSeekWpf/Core/SearchEngine.cs
Browse files
FastSeekWpf/Core/SearchEngine.cs
CHANGED
|
@@ -20,16 +20,8 @@ public class SearchResult
|
|
| 20 |
public ResultKind Kind { get; set; }
|
| 21 |
}
|
| 22 |
|
| 23 |
-
/// <summary>
|
| 24 |
-
/// Allocation-free search engine matching Rust behavior:
|
| 25 |
-
/// - Pre-cached lowercase names (no UTF8 decoding in hot loop)
|
| 26 |
-
/// - Span-based prefix/contains matching
|
| 27 |
-
/// - Stack-allocated candidate buffer
|
| 28 |
-
/// - Reusable StringBuilder for path building
|
| 29 |
-
/// </summary>
|
| 30 |
public static class SearchEngine
|
| 31 |
{
|
| 32 |
-
// Reusable StringBuilder for path building
|
| 33 |
[ThreadStatic]
|
| 34 |
private static StringBuilder? _pathBuilder;
|
| 35 |
|
|
@@ -96,9 +88,6 @@ public static class SearchEngine
|
|
| 96 |
return false;
|
| 97 |
}
|
| 98 |
|
| 99 |
-
/// <summary>
|
| 100 |
-
/// Checks if haystack span contains needle span (Ordinal comparison).
|
| 101 |
-
/// </summary>
|
| 102 |
private static bool SpanContains(ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle)
|
| 103 |
{
|
| 104 |
if (needle.IsEmpty) return true;
|
|
@@ -113,9 +102,6 @@ public static class SearchEngine
|
|
| 113 |
return false;
|
| 114 |
}
|
| 115 |
|
| 116 |
-
/// <summary>
|
| 117 |
-
/// Zero-allocation search using cached lowercase names and Span-based matching.
|
| 118 |
-
/// </summary>
|
| 119 |
public static List<SearchResult> Search(
|
| 120 |
IndexStore store,
|
| 121 |
string query,
|
|
@@ -126,10 +112,10 @@ public static class SearchEngine
|
|
| 126 |
if (string.IsNullOrWhiteSpace(query))
|
| 127 |
return new List<SearchResult>();
|
| 128 |
|
|
|
|
| 129 |
string q = caseSensitive ? query : query.ToLowerInvariant();
|
| 130 |
ReadOnlySpan<char> qSpan = q.AsSpan();
|
| 131 |
|
| 132 |
-
// Stack-allocated candidate array (avoid List allocations)
|
| 133 |
Span<(int idx, byte rank)> candidates = stackalloc (int, byte)[10000];
|
| 134 |
int candidateCount = 0;
|
| 135 |
|
|
@@ -152,7 +138,6 @@ public static class SearchEngine
|
|
| 152 |
}
|
| 153 |
}
|
| 154 |
|
| 155 |
-
// Sort candidates by rank
|
| 156 |
candidates[..candidateCount].Sort((a, b) => a.rank.CompareTo(b.rank));
|
| 157 |
|
| 158 |
int overshoot = Math.Max(limit * 5, 1000);
|
|
@@ -215,6 +200,9 @@ public static class SearchEngine
|
|
| 215 |
if (results.Count > limit)
|
| 216 |
results.RemoveRange(limit, results.Count - limit);
|
| 217 |
|
|
|
|
|
|
|
|
|
|
| 218 |
return results;
|
| 219 |
}
|
| 220 |
|
|
@@ -266,13 +254,9 @@ public static class SearchEngine
|
|
| 266 |
return results;
|
| 267 |
}
|
| 268 |
|
| 269 |
-
/// <summary>
|
| 270 |
-
/// Fast path building using reusable StringBuilder instead of List allocation.
|
| 271 |
-
/// </summary>
|
| 272 |
private static string BuildPathFast(IndexStore store, ulong fileRef, StringBuilder sb)
|
| 273 |
{
|
| 274 |
sb.Clear();
|
| 275 |
-
// string is a managed type, can't stackalloc it. Use small heap array.
|
| 276 |
string[] components = new string[64];
|
| 277 |
int compCount = 0;
|
| 278 |
ulong current = fileRef;
|
|
|
|
| 20 |
public ResultKind Kind { get; set; }
|
| 21 |
}
|
| 22 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
public static class SearchEngine
|
| 24 |
{
|
|
|
|
| 25 |
[ThreadStatic]
|
| 26 |
private static StringBuilder? _pathBuilder;
|
| 27 |
|
|
|
|
| 88 |
return false;
|
| 89 |
}
|
| 90 |
|
|
|
|
|
|
|
|
|
|
| 91 |
private static bool SpanContains(ReadOnlySpan<char> haystack, ReadOnlySpan<char> needle)
|
| 92 |
{
|
| 93 |
if (needle.IsEmpty) return true;
|
|
|
|
| 102 |
return false;
|
| 103 |
}
|
| 104 |
|
|
|
|
|
|
|
|
|
|
| 105 |
public static List<SearchResult> Search(
|
| 106 |
IndexStore store,
|
| 107 |
string query,
|
|
|
|
| 112 |
if (string.IsNullOrWhiteSpace(query))
|
| 113 |
return new List<SearchResult>();
|
| 114 |
|
| 115 |
+
var searchSw = System.Diagnostics.Stopwatch.StartNew();
|
| 116 |
string q = caseSensitive ? query : query.ToLowerInvariant();
|
| 117 |
ReadOnlySpan<char> qSpan = q.AsSpan();
|
| 118 |
|
|
|
|
| 119 |
Span<(int idx, byte rank)> candidates = stackalloc (int, byte)[10000];
|
| 120 |
int candidateCount = 0;
|
| 121 |
|
|
|
|
| 138 |
}
|
| 139 |
}
|
| 140 |
|
|
|
|
| 141 |
candidates[..candidateCount].Sort((a, b) => a.rank.CompareTo(b.rank));
|
| 142 |
|
| 143 |
int overshoot = Math.Max(limit * 5, 1000);
|
|
|
|
| 200 |
if (results.Count > limit)
|
| 201 |
results.RemoveRange(limit, results.Count - limit);
|
| 202 |
|
| 203 |
+
searchSw.Stop();
|
| 204 |
+
Logger.Log($"Search '{query}': {candidateCount} candidates, {results.Count} results, {searchSw.ElapsedMilliseconds}ms (index={store.Count:N0})");
|
| 205 |
+
|
| 206 |
return results;
|
| 207 |
}
|
| 208 |
|
|
|
|
| 254 |
return results;
|
| 255 |
}
|
| 256 |
|
|
|
|
|
|
|
|
|
|
| 257 |
private static string BuildPathFast(IndexStore store, ulong fileRef, StringBuilder sb)
|
| 258 |
{
|
| 259 |
sb.Clear();
|
|
|
|
| 260 |
string[] components = new string[64];
|
| 261 |
int compCount = 0;
|
| 262 |
ulong current = fileRef;
|