public void Test_TRexSpatialMemoryCacheTests_EvictedItemsRemoval() { const int _originX = 123; const int _originY = 456; const int _size = 1000; // Create the cache with enough elements to hold one per context without eviction using (var cache = new TRexSpatialMemoryCache(1, 1000000, 0.5)) { // Make the number of contexts requested, and a separate item to be placed in each one var context = cache.LocateOrCreateContext(Guid.Empty, GridDataType.Height, "fingerprint"); var item1 = new TRexSpatialMemoryCacheContextTests_Element { SizeInBytes = _size, CacheOriginX = _originX, CacheOriginY = _originY }; var item2 = new TRexSpatialMemoryCacheContextTests_Element { SizeInBytes = _size, CacheOriginX = _originX + SubGridTreeConsts.SubGridTreeDimension, CacheOriginY = _originY + SubGridTreeConsts.SubGridTreeDimension }; cache.Add(context, item1, context.InvalidationVersion).Should().Be(CacheContextAdditionResult.Added); cache.Add(context, item2, context.InvalidationVersion).Should().Be(CacheContextAdditionResult.Added); Assert.True(context.TokenCount == 1, "Token count not one after addition of second element forcing eviction of first"); Assert.True(cache.Get(context, item1.CacheOriginX, item1.CacheOriginY) == null, "Able to request item1 after it should have been evicted for item2"); Assert.True(cache.Get(context, item2.CacheOriginX, item2.CacheOriginY) != null, "Unable to request item2 after it should have replaced item1"); } }
public void Test_TRexSpatialMemoryCacheTests_ElementTimeBasedExpiry() { using (var cache = new TRexSpatialMemoryCache(10, 1000000, 0.5)) { // Create a context with an expiry time of one second var context = cache.LocateOrCreateContext(Guid.Empty, GridDataType.All, "fingerprint", new TimeSpan(0, 0, 0, 0, 500)); // Add an item, verify it is there, wait for a seconds then attempt to get the // item. Verify the result is null and that the item is no longer present in the cache cache.Add(context, new TRexSpatialMemoryCacheContextTests_Element { CacheOriginX = 1000, CacheOriginY = 1000, SizeInBytes = 1000 }, context.InvalidationVersion); Assert.True(context.TokenCount == 1, "Failed to add new item to context"); System.Threading.Thread.Sleep(1000); // Allow the item to expire Assert.Null(cache.Get(context, 1000, 1000)); Assert.True(context.TokenCount == 0, "Element not retired on Get() after expiry date"); } }
public void Test_TRexSpatialMemoryCacheTests_AddAndRetrieveItem_OneContext() { const int _originX = 123; const int _originY = 456; const int _size = 1000; using (var cache = new TRexSpatialMemoryCache(100, 1000000, 0.5)) { var context = cache.LocateOrCreateContext(Guid.Empty, GridDataType.Height, "fingerprint"); cache.Add(context, new TRexSpatialMemoryCacheContextTests_Element { SizeInBytes = _size, CacheOriginX = _originX, CacheOriginY = _originY, }, context.InvalidationVersion).Should().Be(CacheContextAdditionResult.Added); Assert.True(context.TokenCount == 1, "Context token count not one after adding single item"); var gotItem = cache.Get(context, _originX, _originY); Assert.True(gotItem != null, "Failed to retrieve added entry"); Assert.Equal(_originX, gotItem.CacheOriginX); Assert.Equal(_originY, gotItem.CacheOriginY); Assert.Equal(_size, gotItem.IndicativeSizeInBytes()); } }
public void Test_TRexSpatialMemoryCacheTests_AddAndRetrieveManyItems_OneContext() { const int _originX = 0; const int _originY = 0; const int _size = 10; using (var cache = new TRexSpatialMemoryCache(100000, 1000000, 0.5)) { var context = cache.LocateOrCreateContext(Guid.Empty, GridDataType.Height, "fingerprint"); for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) { Assert.True(cache.Add(context, new TRexSpatialMemoryCacheContextTests_Element() { SizeInBytes = _size, CacheOriginX = (int)(_originX + i * SubGridTreeConsts.SubGridTreeDimension), CacheOriginY = (int)(_originY + j * SubGridTreeConsts.SubGridTreeDimension) }, context.InvalidationVersion) == CacheContextAdditionResult.Added, $"Failed to add item to cache at {i}:{j}"); } } Assert.True(context.TokenCount == 10000, $"Context token count not 10000 after adding 10000 items, it is {context.TokenCount} instead"); for (int i = 0; i < 100; i++) { for (int j = 0; j < 100; j++) { int x = (int)(_originX + i * SubGridTreeConsts.SubGridTreeDimension); int y = (int)(_originY + j * SubGridTreeConsts.SubGridTreeDimension); var gotItem = cache.Get(context, x, y); Assert.True(gotItem != null, "Failed to retrieve added entry"); Assert.Equal(x, gotItem.CacheOriginX); Assert.Equal(y, gotItem.CacheOriginY); Assert.Equal(_size, gotItem.IndicativeSizeInBytes()); } } } }
public void Test_TRexSpatialMemoryCacheTests_Get() { using (var cache = new TRexSpatialMemoryCache(10, 1000000, 0.5)) { // Create a context with an expiry time of one second var context = cache.LocateOrCreateContext(Guid.Empty, GridDataType.Height, "fingerprint"); cache.Add(context, new TRexSpatialMemoryCacheContextTests_Element { CacheOriginX = 1000, CacheOriginY = 1000, SizeInBytes = 1000 }, context.InvalidationVersion).Should().Be(CacheContextAdditionResult.Added); Assert.True(context.TokenCount == 1, "Failed to add new item to context"); var item = cache.Get(context, 1000, 1000); Assert.NotNull(item); Assert.True(item.CacheOriginX == 1000 && item.CacheOriginY == 1000); } }
public void Test_TRexSpatialMemoryCacheTests_AddAndRetrieveItem_ManyContexts(int numContexts) { const int _originX = 123; const int _originY = 456; const int _size = 10; // Create the cache with enough elements to hold one per context without eviction using (var cache = new TRexSpatialMemoryCache(numContexts, 1000000, 0.5)) { // Make the number ofg contexts requested, and a separate item to be placed in each one var contexts = Enumerable.Range(0, numContexts).Select(x => cache.LocateOrCreateContext(Guid.Empty, GridDataType.Height, $"fingerprint:{x}")).ToArray(); var items = Enumerable.Range(0, numContexts).Select(x => new TRexSpatialMemoryCacheContextTests_Element { SizeInBytes = _size, CacheOriginX = (int)(_originX + x * SubGridTreeConsts.SubGridTreeDimension), CacheOriginY = (int)(_originY + x * SubGridTreeConsts.SubGridTreeDimension) }).ToArray(); for (int i = 0; i < numContexts; i++) { cache.Add(contexts[i], items[i], contexts[i].InvalidationVersion).Should().Be(CacheContextAdditionResult.Added); Assert.True(contexts[i].TokenCount == 1, "Context token count not one after adding single item"); } for (int i = 0; i < numContexts; i++) { var gotItem = cache.Get(contexts[i], items[i].CacheOriginX, items[i].CacheOriginY); Assert.True(gotItem != null, "Failed to retrieve added entry"); Assert.True(ReferenceEquals(items[i], gotItem), $"Got item not same as elements in items array at index {i}"); Assert.Equal(items[i].CacheOriginX, gotItem.CacheOriginX); Assert.Equal(items[i].CacheOriginY, gotItem.CacheOriginY); Assert.Equal(items[i].IndicativeSizeInBytes(), gotItem.IndicativeSizeInBytes()); } } }
public void CacheContext_Tenancy_IsUpheld_OnConcurrentRequests_WithLRUEvictionOnSmallCacheSize() { using var cache = new TRexSpatialMemoryCache(100, 1000000, 0.1); // Create a context with default invalidation sensitivity, add some data to it // and validate that a change bitmask causes appropriate invalidation in concurrent operations var contextHeight = cache.LocateOrCreateContext(Guid.Empty, GridDataType.Height, "fingerprintHeight"); var contextPassCount = cache.LocateOrCreateContext(Guid.Empty, GridDataType.PassCount, "fingerprintPasscount"); var contexts = new[] { contextHeight, contextPassCount }; var itemsHeight = new TRexSpatialMemoryCacheContextTests_Element[100, 100]; var itemsPassCount = new TRexSpatialMemoryCacheContextTests_Element[100, 100]; var items = new[] { itemsHeight, itemsPassCount }; // Create the bitmask ISubGridTreeBitMask mask = new SubGridTreeSubGridExistenceBitMask(); for (var i = 0; i < 100; i++) { for (var j = 0; j < 100; j++) { mask[(i * SubGridTreeConsts.SubGridTreeDimension) >> SubGridTreeConsts.SubGridIndexBitsPerLevel, (j * SubGridTreeConsts.SubGridTreeDimension) >> SubGridTreeConsts.SubGridIndexBitsPerLevel] = true; items.ForEach((x, index) => x[i, j] = new TRexSpatialMemoryCacheContextTests_Element { Context = contexts[index], CacheOriginX = i * SubGridTreeConsts.SubGridTreeDimension, CacheOriginY = j * SubGridTreeConsts.SubGridTreeDimension, SizeInBytes = 1 }); } } // var additionComplete = false; // var invalidationComplete = false; // Progressively add elements in the cache forcing elements to be removed to accomodate them given the cache's small size var additionTask = Task.Run(() => { for (var loopCount = 0; loopCount < 100; loopCount++) { for (var i = 0; i < 100; i++) { for (var j = 0; j < 100; j++) { for (var contextIndex = 0; contextIndex < contexts.Length; contextIndex++) { TRexSpatialMemoryCacheContextTests_Element elem; if ((elem = (TRexSpatialMemoryCacheContextTests_Element)cache.Get(contexts[contextIndex], i, j)) != null) { elem.Context.Should().Be(contexts[contextIndex]); } else { cache.Add(contexts[contextIndex], items[contextIndex][i, j], contexts[contextIndex].InvalidationVersion).Should().Be(CacheContextAdditionResult.Added); } } } } } // additionComplete = true; }); /* * var invalidationTask = Task.Run(() => * { * for (var loopCount = 0; loopCount < 100; loopCount++) * { * for (var i = 99; i >= 0; i--) * { * for (var j = 99; j >= 0; j--) * { * foreach (var context in contexts) * { * // Empty contexts are ignored * if (context.TokenCount > 0) * { * context.InvalidateSubGrid(i * SubGridTreeConsts.SubGridTreeDimension, j * SubGridTreeConsts.SubGridTreeDimension, out var subGridPresentForInvalidation); * } * } * } * } * } * * invalidationComplete = true; * }); */ /* var numDesignInvalidations = 0; * var designUpdateTask = Task.Run(async () => * { * while (!additionComplete || !invalidationComplete) * { * // Mimic design invalidation by periodically invalidating all sub grids * await Task.Delay(10); * contexts[numDesignInvalidations % contexts.Length].InvalidateAllSubGrids(); * * numDesignInvalidations++; * } * }); * */ Task.WaitAll(new[] { additionTask /*, invalidationTask, designUpdateTask*/ }); // numDesignInvalidations.Should().BeGreaterThan(0); }
public void CacheContext_Pressure_MRULRUManagement() { using var cache = new TRexSpatialMemoryCache(100, 1000000, 0.1); // Create a context with default invalidation sensitivity, add some data to it // and validate that a change bitmask causes appropriate invalidation in concurrent operations var contextHeight = cache.LocateOrCreateContext(Guid.Empty, GridDataType.Height, "fingerprintHeight"); var contextPassCount = cache.LocateOrCreateContext(Guid.Empty, GridDataType.PassCount, "fingerprintPasscount"); var contexts = new[] { contextHeight, contextPassCount }; var itemsHeight = new TRexSpatialMemoryCacheContextTests_Element[100, 100]; var itemsPassCount = new TRexSpatialMemoryCacheContextTests_Element[100, 100]; var items = new[] { itemsHeight, itemsPassCount }; for (var i = 0; i < 100; i++) { for (var j = 0; j < 100; j++) { items.ForEach((x, index) => x[i, j] = new TRexSpatialMemoryCacheContextTests_Element { Context = contexts[index], CacheOriginX = i * SubGridTreeConsts.SubGridTreeDimension, CacheOriginY = j * SubGridTreeConsts.SubGridTreeDimension, SizeInBytes = 1 }); } } // Progressively add elements in the cache forcing elements to be removed to accomodate them given the cache's small size // Check the retrieved element is also in the expected cache var additionTask = Task.Run(() => { for (var loopCount = 0; loopCount < 100; loopCount++) { for (var i = 0; i < 100; i++) { for (var j = 0; j < 100; j++) { for (var contextIndex = 0; contextIndex < contexts.Length; contextIndex++) { TRexSpatialMemoryCacheContextTests_Element elem; if ((elem = (TRexSpatialMemoryCacheContextTests_Element)cache.Get(contexts[contextIndex], i, j)) != null) { elem.Context.Should().Be(contexts[contextIndex]); } else { cache.Add(contexts[contextIndex], items[contextIndex][i, j], contexts[contextIndex].InvalidationVersion).Should().Be(CacheContextAdditionResult.Added); } } } } } }); Task.WaitAll(new[] { additionTask }); }
public void Test_TRexSpatialMemoryCacheTests_ConcurrentAccessWithDesignAndProductionDataIngestInvalidation() { using var cache = new TRexSpatialMemoryCache(20000, 1000000, 0.5); // Create a context with default invalidation sensitivity, add some data to it // and validate that a change bitmask causes appropriate invalidation in concurrent operations var contextHeight = cache.LocateOrCreateContext(Guid.Empty, GridDataType.Height, "fingerprintHeight"); var contextPassCount = cache.LocateOrCreateContext(Guid.Empty, GridDataType.PassCount, "fingerprintPasscount"); var contexts = new[] { contextHeight, contextPassCount }; var itemsHeight = new TRexSpatialMemoryCacheContextTests_Element[100, 100]; var itemsPassCount = new TRexSpatialMemoryCacheContextTests_Element[100, 100]; var items = new[] { itemsHeight, itemsPassCount }; // Create the bitmask ISubGridTreeBitMask mask = new SubGridTreeSubGridExistenceBitMask(); for (var i = 0; i < 100; i++) { for (var j = 0; j < 100; j++) { mask[(i * SubGridTreeConsts.SubGridTreeDimension) >> SubGridTreeConsts.SubGridIndexBitsPerLevel, (j * SubGridTreeConsts.SubGridTreeDimension) >> SubGridTreeConsts.SubGridIndexBitsPerLevel] = true; items.ForEach(x => x[i, j] = new TRexSpatialMemoryCacheContextTests_Element { CacheOriginX = i * SubGridTreeConsts.SubGridTreeDimension, CacheOriginY = j * SubGridTreeConsts.SubGridTreeDimension, SizeInBytes = 1 }); } } var additionComplete = false; var invalidationComplete = false; // Progressively add and invalidate the elements in the cache in separate tasks var additionTask = Task.Run(() => { for (var loopCount = 0; loopCount < 100; loopCount++) { for (var i = 0; i < 100; i++) { for (var j = 0; j < 100; j++) { for (var contextIndex = 0; contextIndex < contexts.Length; contextIndex++) { if (cache.Get(contexts[contextIndex], i, j) == null) { cache.Add(contexts[contextIndex], items[contextIndex][i, j], contexts[contextIndex].InvalidationVersion); } } } } } additionComplete = true; }); var invalidationTask = Task.Run(() => { for (var loopCount = 0; loopCount < 100; loopCount++) { for (var i = 99; i >= 0; i--) { for (var j = 99; j >= 0; j--) { foreach (var context in contexts) { // Empty contexts are ignored if (context.TokenCount > 0) { context.InvalidateSubGrid(i * SubGridTreeConsts.SubGridTreeDimension, j * SubGridTreeConsts.SubGridTreeDimension, out var subGridPresentForInvalidation); } } } } } invalidationComplete = true; }); var numDesignInvalidations = 0; var designUpdateTask = Task.Run(async() => { while (!additionComplete || !invalidationComplete) { // Mimic design invalidation by periodically invalidating all sub grids await Task.Delay(10); contexts[numDesignInvalidations % contexts.Length].InvalidateAllSubGrids(); numDesignInvalidations++; } }); Task.WaitAll(new[] { additionTask, invalidationTask, designUpdateTask }); numDesignInvalidations.Should().BeGreaterThan(0); }