Skip to content

Commit 55f14e8

Browse files
authored
Merge pull request #2004 from tyrielv/tyrielv/expand-prefetch-cache
Expand blob prefetch noop cache to N entries
2 parents 438ed24 + 0764762 commit 55f14e8

4 files changed

Lines changed: 311 additions & 57 deletions

File tree

GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs

Lines changed: 55 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
using System.Collections.Generic;
1010
using System.IO;
1111
using System.Linq;
12+
using System.Security.Cryptography;
13+
using System.Text;
1214
using System.Threading;
1315

1416
namespace GVFS.Common.Prefetch
@@ -32,7 +34,13 @@ public class BlobPrefetcher
3234
private const string AreaPath = nameof(BlobPrefetcher);
3335
private static string pathSeparatorString = Path.DirectorySeparatorChar.ToString();
3436

35-
private FileBasedDictionary<string, string> lastPrefetchArgs;
37+
public const string BlobPrefetchCacheFile = "BlobPrefetchCache.dat";
38+
public const string PrefetchCacheSizeConfigKey = GVFSConstants.GitConfig.GVFSPrefix + "prefetch-cache-size";
39+
public const int DefaultPrefetchCacheSize = 100;
40+
public const int MaxPrefetchCacheSize = 1000;
41+
42+
private FileBasedDictionary<string, string> prefetchCache;
43+
private int maxCacheSize;
3644

3745
public BlobPrefetcher(
3846
ITracer tracer,
@@ -42,7 +50,7 @@ public BlobPrefetcher(
4250
int searchThreadCount,
4351
int downloadThreadCount,
4452
int indexThreadCount)
45-
: this(tracer, enlistment, objectRequestor, null, null, null, chunkSize, searchThreadCount, downloadThreadCount, indexThreadCount)
53+
: this(tracer, enlistment, objectRequestor, null, null, null, DefaultPrefetchCacheSize, chunkSize, searchThreadCount, downloadThreadCount, indexThreadCount)
4654
{
4755
}
4856

@@ -52,7 +60,8 @@ public BlobPrefetcher(
5260
GitObjectsHttpRequestor objectRequestor,
5361
List<string> fileList,
5462
List<string> folderList,
55-
FileBasedDictionary<string, string> lastPrefetchArgs,
63+
FileBasedDictionary<string, string> prefetchCache,
64+
int maxCacheSize,
5665
int chunkSize,
5766
int searchThreadCount,
5867
int downloadThreadCount,
@@ -70,7 +79,8 @@ public BlobPrefetcher(
7079
this.FileList = fileList ?? new List<string>();
7180
this.FolderList = folderList ?? new List<string>();
7281

73-
this.lastPrefetchArgs = lastPrefetchArgs;
82+
this.prefetchCache = prefetchCache;
83+
this.maxCacheSize = maxCacheSize;
7484

7585
// We never want to update config settings for a GVFSEnlistment
7686
this.SkipConfigUpdate = enlistment is GVFSEnlistment;
@@ -127,39 +137,26 @@ public static bool TryLoadFileList(Enlistment enlistment, string filesInput, str
127137

128138
public static bool IsNoopPrefetch(
129139
ITracer tracer,
130-
FileBasedDictionary<string, string> lastPrefetchArgs,
140+
FileBasedDictionary<string, string> prefetchCache,
131141
string commitId,
132142
List<string> files,
133143
List<string> folders,
134144
bool hydrateFilesAfterDownload)
135145
{
136-
if (lastPrefetchArgs != null &&
137-
lastPrefetchArgs.TryGetValue(PrefetchArgs.CommitId, out string lastCommitId) &&
138-
lastPrefetchArgs.TryGetValue(PrefetchArgs.Files, out string lastFilesString) &&
139-
lastPrefetchArgs.TryGetValue(PrefetchArgs.Folders, out string lastFoldersString) &&
140-
lastPrefetchArgs.TryGetValue(PrefetchArgs.Hydrate, out string lastHydrateString))
146+
if (prefetchCache != null)
141147
{
142-
string newFilesString = GVFSJsonOptions.Serialize(files);
143-
string newFoldersString = GVFSJsonOptions.Serialize(folders);
144-
bool isNoop =
145-
commitId == lastCommitId &&
146-
hydrateFilesAfterDownload.ToString() == lastHydrateString &&
147-
newFilesString == lastFilesString &&
148-
newFoldersString == lastFoldersString;
148+
string cacheKey = ComputeCacheKey(files, folders, hydrateFilesAfterDownload);
149+
bool hasEntry = prefetchCache.TryGetValue(cacheKey, out string cachedCommitId);
150+
bool isNoop = hasEntry && commitId == cachedCommitId;
149151

150152
tracer.RelatedEvent(
151153
EventLevel.Informational,
152154
"BlobPrefetcher.IsNoopPrefetch",
153155
new EventMetadata
154156
{
155-
{ "Last" + PrefetchArgs.CommitId, lastCommitId },
156-
{ "Last" + PrefetchArgs.Files, lastFilesString },
157-
{ "Last" + PrefetchArgs.Folders, lastFoldersString },
158-
{ "Last" + PrefetchArgs.Hydrate, lastHydrateString },
159-
{ "New" + PrefetchArgs.CommitId, commitId },
160-
{ "New" + PrefetchArgs.Files, newFilesString },
161-
{ "New" + PrefetchArgs.Folders, newFoldersString },
162-
{ "New" + PrefetchArgs.Hydrate, hydrateFilesAfterDownload.ToString() },
157+
{ "CacheKey", cacheKey },
158+
{ "CachedCommitId", cachedCommitId ?? "(none)" },
159+
{ "NewCommitId", commitId },
163160
{ "Result", isNoop },
164161
});
165162

@@ -583,33 +580,50 @@ private bool IsSymbolicRef(string targetCommitish)
583580

584581
private void SavePrefetchArgs(string targetCommit, bool hydrate)
585582
{
586-
if (this.lastPrefetchArgs != null)
583+
if (this.prefetchCache != null && this.maxCacheSize > 0)
587584
{
588-
this.lastPrefetchArgs.SetValuesAndFlush(
589-
new[]
585+
string cacheKey = ComputeCacheKey(this.FileList, this.FolderList, hydrate);
586+
587+
Dictionary<string, string> allEntries = this.prefetchCache.GetAllKeysAndValues();
588+
if (allEntries.Count >= this.maxCacheSize && !allEntries.ContainsKey(cacheKey))
589+
{
590+
// Evict one arbitrary entry to make room
591+
using (Dictionary<string, string>.Enumerator enumerator = allEntries.GetEnumerator())
590592
{
591-
new KeyValuePair<string, string>(PrefetchArgs.CommitId, targetCommit),
592-
new KeyValuePair<string, string>(PrefetchArgs.Files, GVFSJsonOptions.Serialize(this.FileList)),
593-
new KeyValuePair<string, string>(PrefetchArgs.Folders, GVFSJsonOptions.Serialize(this.FolderList)),
594-
new KeyValuePair<string, string>(PrefetchArgs.Hydrate, hydrate.ToString()),
595-
});
593+
if (enumerator.MoveNext())
594+
{
595+
this.prefetchCache.RemoveAndFlush(enumerator.Current.Key);
596+
}
597+
}
598+
}
599+
600+
this.prefetchCache.SetValueAndFlush(cacheKey, targetCommit);
596601
}
597602
}
598603

604+
internal static string ComputeCacheKey(List<string> files, List<string> folders, bool hydrate)
605+
{
606+
List<string> sortedFiles = new List<string>(files);
607+
sortedFiles.Sort(StringComparer.Ordinal);
608+
609+
List<string> sortedFolders = new List<string>(folders);
610+
sortedFolders.Sort(StringComparer.Ordinal);
611+
612+
string compositeInput = string.Join("\n",
613+
GVFSJsonOptions.Serialize(sortedFiles),
614+
GVFSJsonOptions.Serialize(sortedFolders),
615+
hydrate.ToString());
616+
617+
byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(compositeInput));
618+
return Convert.ToHexString(hashBytes);
619+
}
620+
599621
public class FetchException : Exception
600622
{
601623
public FetchException(string format, params object[] args)
602624
: base(string.Format(format, args))
603625
{
604626
}
605627
}
606-
607-
private static class PrefetchArgs
608-
{
609-
public const string CommitId = "CommitId";
610-
public const string Files = "Files";
611-
public const string Folders = "Folders";
612-
public const string Hydrate = "Hydrate";
613-
}
614628
}
615629
}

GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchVerbTests.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ public PrefetchVerbTests()
3737
this.fileSystem = new SystemIORunner();
3838
}
3939

40+
[SetUp]
41+
public void DeletePrefetchCache()
42+
{
43+
string cachePath = Path.Combine(this.Enlistment.DotGVFSRoot, "BlobPrefetchCache.dat");
44+
if (File.Exists(cachePath))
45+
{
46+
File.Delete(cachePath);
47+
}
48+
}
49+
4050
[TestCase, Order(1)]
4151
public void PrefetchAllMustBeExplicit()
4252
{

0 commit comments

Comments
 (0)