Skip to content

Commit 803c069

Browse files
authoredMay 10, 2024
Add Least Frequently Used eviction strategy (#328)
* Add Least Frequently Used eviction strategy ## Summary Currently PINCache only offers LRU (least recently used) as an eviction strategy. However, there are some special workloads where LFU (least frequently used) could offer better performance. This PR introduces LFU alongside the existing LRU eviction strategy. The default is still LRU. There is also some minor renaming to the `trimToSizeByDateAsync`, `trimToSizeByDate`, `trimToCostByDate`, and `trimToCostByDateAsync` methods, since those now follow the explicit eviction strategy. Old methods remain and work as expected, but are marked deprecated. ## Testing Added some unit tests for both memory and disk caches to verify objects are evicted based on access count when LFU is selected. Ran tests on iOS, tvOS, macOS. * Add deprecated messages to clarify what should be used instead * DRY up a couple of constants * Remove doc for parameter that doesn't exist * Nudge github actions
1 parent 9f3977e commit 803c069

8 files changed

+443
-44
lines changed
 

‎Source/PINCache.h

+27-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,33 @@ PIN_SUBCLASSING_RESTRICTED
147147
deserializer:(nullable PINDiskCacheDeserializerBlock)deserializer
148148
keyEncoder:(nullable PINDiskCacheKeyEncoderBlock)keyEncoder
149149
keyDecoder:(nullable PINDiskCacheKeyDecoderBlock)keyDecoder
150-
ttlCache:(BOOL)ttlCache NS_DESIGNATED_INITIALIZER;
150+
ttlCache:(BOOL)ttlCache;
151+
152+
/**
153+
Multiple instances with the same name are *not* allowed and can *not* safely
154+
access the same data on disk. Also used to create the <diskCache>.
155+
Initializer allows you to override default NSKeyedArchiver/NSKeyedUnarchiver serialization for <diskCache>.
156+
You must provide both serializer and deserializer, or opt-out to default implementation providing nil values.
157+
158+
@see name
159+
@param name The name of the cache.
160+
@param rootPath The path of the cache on disk.
161+
@param serializer A block used to serialize object before writing to disk. If nil provided, default NSKeyedArchiver serialized will be used.
162+
@param deserializer A block used to deserialize object read from disk. If nil provided, default NSKeyedUnarchiver serialized will be used.
163+
@param keyEncoder A block used to encode key(filename). If nil provided, default url encoder will be used
164+
@param keyDecoder A block used to decode key(filename). If nil provided, default url decoder will be used
165+
@param ttlCache Whether or not the cache should behave as a TTL cache.
166+
@param evictionStrategy How the cache decide to evict objects when over cost.
167+
@result A new cache with the specified name.
168+
*/
169+
- (instancetype)initWithName:(nonnull NSString *)name
170+
rootPath:(nonnull NSString *)rootPath
171+
serializer:(nullable PINDiskCacheSerializerBlock)serializer
172+
deserializer:(nullable PINDiskCacheDeserializerBlock)deserializer
173+
keyEncoder:(nullable PINDiskCacheKeyEncoderBlock)keyEncoder
174+
keyDecoder:(nullable PINDiskCacheKeyDecoderBlock)keyDecoder
175+
ttlCache:(BOOL)ttlCache
176+
evictionStrategy:(PINCacheEvictionStrategy)evictionStrategy NS_DESIGNATED_INITIALIZER;
151177

152178
@end
153179

‎Source/PINCache.m

+17-2
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,25 @@ - (instancetype)initWithName:(NSString *)name
4848
return [self initWithName:name rootPath:rootPath serializer:serializer deserializer:deserializer keyEncoder:keyEncoder keyDecoder:keyDecoder ttlCache:NO];
4949
}
5050

51+
- (instancetype)initWithName:(nonnull NSString *)name
52+
rootPath:(nonnull NSString *)rootPath
53+
serializer:(nullable PINDiskCacheSerializerBlock)serializer
54+
deserializer:(nullable PINDiskCacheDeserializerBlock)deserializer
55+
keyEncoder:(nullable PINDiskCacheKeyEncoderBlock)keyEncoder
56+
keyDecoder:(nullable PINDiskCacheKeyDecoderBlock)keyDecoder
57+
ttlCache:(BOOL)ttlCache
58+
{
59+
return [self initWithName:name rootPath:rootPath serializer:serializer deserializer:deserializer keyEncoder:keyEncoder keyDecoder:keyDecoder ttlCache:ttlCache evictionStrategy:PINCacheEvictionStrategyLeastRecentlyUsed];
60+
}
61+
5162
- (instancetype)initWithName:(NSString *)name
5263
rootPath:(NSString *)rootPath
5364
serializer:(PINDiskCacheSerializerBlock)serializer
5465
deserializer:(PINDiskCacheDeserializerBlock)deserializer
5566
keyEncoder:(PINDiskCacheKeyEncoderBlock)keyEncoder
5667
keyDecoder:(PINDiskCacheKeyDecoderBlock)keyDecoder
5768
ttlCache:(BOOL)ttlCache
69+
evictionStrategy:(PINCacheEvictionStrategy)evictionStrategy
5870
{
5971
if (!name)
6072
return nil;
@@ -72,8 +84,11 @@ - (instancetype)initWithName:(NSString *)name
7284
keyEncoder:keyEncoder
7385
keyDecoder:keyDecoder
7486
operationQueue:_operationQueue
75-
ttlCache:ttlCache];
76-
_memoryCache = [[PINMemoryCache alloc] initWithName:_name operationQueue:_operationQueue ttlCache:ttlCache];
87+
ttlCache:ttlCache
88+
byteLimit:PINDiskCacheDefaultByteLimit
89+
ageLimit:PINDiskCacheDefaultAgeLimit
90+
evictionStrategy:evictionStrategy];
91+
_memoryCache = [[PINMemoryCache alloc] initWithName:_name operationQueue:_operationQueue ttlCache:ttlCache evictionStrategy:evictionStrategy];
7792
}
7893
return self;
7994
}

‎Source/PINCaching.h

+5
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ typedef void (^PINCacheObjectEnumerationBlock)(__kindof id<PINCaching> cache, NS
3434
*/
3535
typedef void (^PINCacheObjectContainmentBlock)(BOOL containsObject);
3636

37+
typedef NS_ENUM(NSInteger, PINCacheEvictionStrategy) {
38+
PINCacheEvictionStrategyLeastRecentlyUsed,
39+
PINCacheEvictionStrategyLeastFrequentlyUsed,
40+
};
41+
3742
@protocol PINCaching <NSObject>
3843

3944
#pragma mark - Core

‎Source/PINDiskCache.h

+45-6
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ extern NSErrorUserInfoKey const PINDiskCacheErrorReadFailureCodeKey;
1818
extern NSErrorUserInfoKey const PINDiskCacheErrorWriteFailureCodeKey;
1919
extern NSString * const PINDiskCachePrefix;
2020

21+
extern NSUInteger PINDiskCacheDefaultByteLimit;
22+
extern NSTimeInterval PINDiskCacheDefaultAgeLimit;
23+
2124
typedef NS_ENUM(NSInteger, PINDiskCacheError) {
2225
PINDiskCacheErrorReadFailure = -1000,
2326
PINDiskCacheErrorWriteFailure = -1001,
@@ -168,6 +171,11 @@ PIN_SUBCLASSING_RESTRICTED
168171
*/
169172
@property (assign) NSTimeInterval ageLimit;
170173

174+
/**
175+
The eviction strategy when trimming the cache.
176+
*/
177+
@property (atomic, assign) PINCacheEvictionStrategy evictionStrategy;
178+
171179
/**
172180
The writing protection option used when writing a file on disk. This value is used every time an object is set.
173181
NSDataWritingAtomic and NSDataWritingWithoutOverwriting are ignored if set
@@ -336,6 +344,33 @@ PIN_SUBCLASSING_RESTRICTED
336344
operationQueue:(nonnull PINOperationQueue *)operationQueue
337345
ttlCache:(BOOL)ttlCache;
338346

347+
/**
348+
@see name
349+
@param name The name of the cache.
350+
@param prefix The prefix for the cache name. Defaults to com.pinterest.PINDiskCache
351+
@param rootPath The path of the cache.
352+
@param serializer A block used to serialize object. If nil provided, default NSKeyedArchiver serialized will be used.
353+
@param deserializer A block used to deserialize object. If nil provided, default NSKeyedUnarchiver serialized will be used.
354+
@param keyEncoder A block used to encode key(filename). If nil provided, default url encoder will be used
355+
@param keyDecoder A block used to decode key(filename). If nil provided, default url decoder will be used
356+
@param operationQueue A PINOperationQueue to run asynchronous operations
357+
@param ttlCache Whether or not the cache should behave as a TTL cache.
358+
@param byteLimit The maximum number of bytes allowed on disk. Defaults to 50MB.
359+
@param ageLimit The maximum number of seconds an object is allowed to exist in the cache. Defaults to 30 days.
360+
@result A new cache with the specified name.
361+
*/
362+
- (instancetype)initWithName:(nonnull NSString *)name
363+
prefix:(nonnull NSString *)prefix
364+
rootPath:(nonnull NSString *)rootPath
365+
serializer:(nullable PINDiskCacheSerializerBlock)serializer
366+
deserializer:(nullable PINDiskCacheDeserializerBlock)deserializer
367+
keyEncoder:(nullable PINDiskCacheKeyEncoderBlock)keyEncoder
368+
keyDecoder:(nullable PINDiskCacheKeyDecoderBlock)keyDecoder
369+
operationQueue:(nonnull PINOperationQueue *)operationQueue
370+
ttlCache:(BOOL)ttlCache
371+
byteLimit:(NSUInteger)byteLimit
372+
ageLimit:(NSTimeInterval)ageLimit;
373+
339374
/**
340375
The designated initializer allowing you to override default NSKeyedArchiver/NSKeyedUnarchiver serialization.
341376
@@ -351,6 +386,7 @@ PIN_SUBCLASSING_RESTRICTED
351386
@param ttlCache Whether or not the cache should behave as a TTL cache.
352387
@param byteLimit The maximum number of bytes allowed on disk. Defaults to 50MB.
353388
@param ageLimit The maximum number of seconds an object is allowed to exist in the cache. Defaults to 30 days.
389+
@param evictionStrategy How the cache decides to evict objects
354390
@result A new cache with the specified name.
355391
*/
356392
- (instancetype)initWithName:(nonnull NSString *)name
@@ -363,7 +399,8 @@ PIN_SUBCLASSING_RESTRICTED
363399
operationQueue:(nonnull PINOperationQueue *)operationQueue
364400
ttlCache:(BOOL)ttlCache
365401
byteLimit:(NSUInteger)byteLimit
366-
ageLimit:(NSTimeInterval)ageLimit NS_DESIGNATED_INITIALIZER;
402+
ageLimit:(NSTimeInterval)ageLimit
403+
evictionStrategy:(PINCacheEvictionStrategy)evictionStrategy NS_DESIGNATED_INITIALIZER;
367404

368405
#pragma mark - Asynchronous Methods
369406
/// @name Asynchronous Methods
@@ -471,7 +508,7 @@ PIN_SUBCLASSING_RESTRICTED
471508
- (void)trimToSizeAsync:(NSUInteger)byteCount completion:(nullable PINCacheBlock)block;
472509

473510
/**
474-
Removes objects from the cache, ordered by date (least recently used first), until the cache is equal to or smaller
511+
Removes objects from the cache, using the eviction strategy, until the cache is equal to or smaller
475512
than the specified byteCount. This method returns immediately and executes the passed block as soon as the cache has
476513
been trimmed.
477514
@@ -480,7 +517,7 @@ PIN_SUBCLASSING_RESTRICTED
480517
481518
@note This will not remove objects that have been added via one of the @c -setObject:forKey:withAgeLimit methods.
482519
*/
483-
- (void)trimToSizeByDateAsync:(NSUInteger)byteCount completion:(nullable PINCacheBlock)block;
520+
- (void)trimToSizeByEvictionStrategyAsync:(NSUInteger)byteCount completion:(nullable PINCacheBlock)block;
484521

485522
/**
486523
Loops through all objects in the cache (reads and writes are suspended during the enumeration). Data is not actually
@@ -564,15 +601,15 @@ PIN_SUBCLASSING_RESTRICTED
564601
- (void)trimToSize:(NSUInteger)byteCount;
565602

566603
/**
567-
Removes objects from the cache, ordered by date (least recently used first), until the cache is equal to or
604+
Removes objects from the cache, using the defined evictionStrategy, until the cache is equal to or
568605
smaller than the specified byteCount. This method blocks the calling thread until the cache has been trimmed.
569606
570-
@see trimToSizeByDateAsync:
607+
@see trimToSizeByEvictionStrategyAsync:
571608
@param byteCount The cache will be trimmed equal to or smaller than this size.
572609
573610
@note This will not remove objects that have been added via one of the @c -setObject:forKey:withAgeLimit methods.
574611
*/
575-
- (void)trimToSizeByDate:(NSUInteger)byteCount;
612+
- (void)trimToSizeByEvictionStrategy:(NSUInteger)byteCount;
576613

577614
/**
578615
Loops through all objects in the cache (reads and writes are suspended during the enumeration). Data is not actually
@@ -616,6 +653,8 @@ typedef void (^PINDiskCacheBlock)(PINDiskCache *cache);
616653
- (void)removeAllObjects:(nullable PINDiskCacheBlock)block __attribute__((deprecated));
617654
- (void)enumerateObjectsWithBlock:(PINDiskCacheFileURLBlock)block completionBlock:(nullable PINDiskCacheBlock)completionBlock __attribute__((deprecated));
618655
- (void)setTtlCache:(BOOL)ttlCache DEPRECATED_MSG_ATTRIBUTE("ttlCache is no longer a settable property and must now be set via initializer.");
656+
- (void)trimToSizeByDate:(NSUInteger)byteCount DEPRECATED_MSG_ATTRIBUTE("Use trimToSizeByEvictionStrategy: instead");
657+
- (void)trimToSizeByDateAsync:(NSUInteger)byteCount completion:(nullable PINCacheBlock)block DEPRECATED_MSG_ATTRIBUTE("Use trimToSizeByEvictionStrategyAsync:completion: instead");
619658
@end
620659

621660
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)
Please sign in to comment.