public final class DfsBlockCache
extends java.lang.Object
BlockBasedFile
in memory for
faster read access.
The DfsBlockCache serves as a Java based "buffer cache", loading segments of a BlockBasedFile into the JVM heap prior to use. As JGit often wants to do reads of only tiny slices of a file, the DfsBlockCache tries to smooth out these tiny reads into larger block-sized IO operations.
Whenever a cache miss occurs, loading is invoked by exactly one thread for
the given (DfsStreamKey,position)
key tuple. This is ensured by
an array of locks, with the tuple hashed to a lock instance.
Its too expensive during object access to be accurate with a least recently used (LRU) algorithm. Strictly ordering every read is a lot of overhead that typically doesn't yield a corresponding benefit to the application. This cache implements a clock replacement algorithm, giving each block one chance to have been accessed during a sweep of the cache to save itself from eviction.
Entities created by the cache are held under hard references, preventing the Java VM from clearing anything. Blocks are discarded by the replacement algorithm when adding a new block would cause the cache to exceed its configured maximum size.
The key tuple is passed through to methods as a pair of parameters rather than as a single Object, thus reducing the transient memory allocations of callers. It is more efficient to avoid the allocation, as we can't be 100% sure that a JIT would be able to stack-allocate a key tuple.
The internal hash table does not expand at runtime, instead it is fixed in size at cache creation time. The internal lock table used to gate load invocations is also fixed in size.
Modifier and Type | Class and Description |
---|---|
private static class |
DfsBlockCache.HashEntry |
(package private) static interface |
DfsBlockCache.ReadableChannelSupplier
Supplier for readable channel
|
(package private) static class |
DfsBlockCache.Ref<T> |
(package private) static interface |
DfsBlockCache.RefLoader<T> |
Modifier and Type | Field and Description |
---|---|
private int |
blockSize
Suggested block size to read from pack files in.
|
private int |
blockSizeShift
As
blockSize is a power of 2, bits to shift for a / blockSize. |
private static DfsBlockCache |
cache |
private DfsBlockCache.Ref |
clockHand
Current position of the clock.
|
private java.util.concurrent.locks.ReentrantLock |
clockLock
Protects the clock and its related data.
|
private java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> |
liveBytes
Number of bytes currently loaded in the cache, per pack file extension.
|
private java.util.concurrent.locks.ReentrantLock[] |
loadLocks
Locks to prevent concurrent loads for same (PackFile,position) block.
|
private long |
maxBytes
Maximum number of bytes the cache should hold.
|
private long |
maxStreamThroughCache
Pack files smaller than this size can be copied through the cache.
|
private java.util.concurrent.locks.ReentrantLock[] |
refLocks
A separate pool of locks to prevent concurrent loads for same index or bitmap from PackFile.
|
private java.util.function.Consumer<java.lang.Long> |
refLockWaitTime
A consumer of object reference lock wait time milliseconds.
|
private java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> |
statEvict
Number of blocks evicted due to cache being full, per pack file
extension.
|
private java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> |
statHit
Number of times a block was found in the cache, per pack file extension.
|
private java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> |
statMiss
Number of times a block was not found, and had to be loaded, per pack
file extension.
|
private java.util.concurrent.atomic.AtomicReferenceArray<DfsBlockCache.HashEntry> |
table
Hash bucket directory; entries are chained below.
|
private int |
tableSize
Number of entries in
table . |
Modifier | Constructor and Description |
---|---|
private |
DfsBlockCache(DfsBlockCacheConfig cfg) |
Modifier and Type | Method and Description |
---|---|
private void |
addToClock(DfsBlockCache.Ref ref,
int credit) |
private static DfsBlockCache.HashEntry |
clean(DfsBlockCache.HashEntry top) |
(package private) boolean |
contains(DfsStreamKey key,
long position) |
private void |
creditSpace(int credit,
DfsStreamKey key) |
(package private) <T> T |
get(DfsStreamKey key,
long position) |
(package private) int |
getBlockSize() |
long[] |
getCurrentSize()
Get total number of bytes in the cache, per pack file extension.
|
long[] |
getEvictions()
Get number of evictions performed due to cache being full, per pack file
extension.
|
long |
getFillPercentage()
Get 0..100, defining how full the cache is.
|
long[] |
getHitCount()
Get number of requests for items in the cache, per pack file extension.
|
long[] |
getHitRatio()
Get hit ratios
|
static DfsBlockCache |
getInstance()
Get the currently active DfsBlockCache.
|
long[] |
getMissCount()
Get number of requests for items not in the cache, per pack file
extension.
|
(package private) DfsBlock |
getOrLoad(BlockBasedFile file,
long position,
DfsReader ctx,
DfsBlockCache.ReadableChannelSupplier fileChannel)
Look up a cached object, creating and loading it if it doesn't exist.
|
(package private) <T> DfsBlockCache.Ref<T> |
getOrLoadRef(DfsStreamKey key,
DfsBlockCache.RefLoader<T> loader)
Look up a cached object, creating and loading it if it doesn't exist.
|
private static java.util.concurrent.atomic.AtomicLong |
getStat(java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> stats,
DfsStreamKey key) |
private static long[] |
getStatVals(java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> stat) |
long[] |
getTotalRequestCount()
Get total number of requests (hit + miss), per pack file extension.
|
boolean |
hasBlock0(DfsStreamKey key)
Quickly check if the cache contains block 0 of the given stream.
|
private int |
hash(int packHash,
long off) |
private java.util.concurrent.locks.ReentrantLock |
lockFor(DfsStreamKey key,
long position) |
private java.util.concurrent.locks.ReentrantLock |
lockForRef(DfsStreamKey key) |
private static java.util.concurrent.atomic.AtomicLong[] |
newCounters() |
(package private) void |
put(DfsBlock v) |
(package private) <T> DfsBlockCache.Ref<T> |
put(DfsStreamKey key,
long pos,
int size,
T v) |
(package private) <T> DfsBlockCache.Ref<T> |
putRef(DfsStreamKey key,
long size,
T v) |
static void |
reconfigure(DfsBlockCacheConfig cfg)
Modify the configuration of the window cache.
|
private void |
reserveSpace(int reserve,
DfsStreamKey key) |
private <T> T |
scan(DfsBlockCache.HashEntry n,
DfsStreamKey key,
long position) |
private <T> DfsBlockCache.Ref<T> |
scanRef(DfsBlockCache.HashEntry n,
DfsStreamKey key,
long position) |
(package private) boolean |
shouldCopyThroughCache(long length) |
private int |
slot(DfsStreamKey key,
long position) |
private static int |
tableSize(DfsBlockCacheConfig cfg) |
private static volatile DfsBlockCache cache
private final int tableSize
table
.private final java.util.concurrent.atomic.AtomicReferenceArray<DfsBlockCache.HashEntry> table
private final java.util.concurrent.locks.ReentrantLock[] loadLocks
DfsBlockCacheConfig.getConcurrencyLevel()
to
cap the overall concurrent block loads.private final java.util.concurrent.locks.ReentrantLock[] refLocks
private final long maxBytes
private final long maxStreamThroughCache
private final int blockSize
If a pack file does not have a native block size, this size will be used.
If a pack file has a native size, a whole multiple of the native size will be used until it matches this size.
The value for blockSize must be a power of 2.
private final int blockSizeShift
blockSize
is a power of 2, bits to shift for a / blockSize.private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> statHit
private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> statMiss
private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> statEvict
private final java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> liveBytes
private final java.util.concurrent.locks.ReentrantLock clockLock
private final java.util.function.Consumer<java.lang.Long> refLockWaitTime
private DfsBlockCache.Ref clockHand
private DfsBlockCache(DfsBlockCacheConfig cfg)
public static void reconfigure(DfsBlockCacheConfig cfg)
The new configuration is applied immediately, and the existing cache is cleared.
cfg
- the new window cache configuration.java.lang.IllegalArgumentException
- the cache configuration contains one or more invalid
settings, usually too low of a limit.public static DfsBlockCache getInstance()
boolean shouldCopyThroughCache(long length)
public long[] getCurrentSize()
public long getFillPercentage()
public long[] getHitCount()
public long[] getMissCount()
public long[] getTotalRequestCount()
public long[] getHitRatio()
public long[] getEvictions()
public boolean hasBlock0(DfsStreamKey key)
This can be useful for sophisticated pre-read algorithms to quickly determine if a file is likely already in cache, especially small reftables which may be smaller than a typical DFS block size.
key
- the file to check.private int hash(int packHash, long off)
int getBlockSize()
private static int tableSize(DfsBlockCacheConfig cfg)
DfsBlock getOrLoad(BlockBasedFile file, long position, DfsReader ctx, DfsBlockCache.ReadableChannelSupplier fileChannel) throws java.io.IOException
file
- the pack that "contains" the cached object.position
- offset within pack
of the object.ctx
- current thread's reader.fileChannel
- supplier for channel to read pack
.java.io.IOException
- the reference was not in the cache and could not be loaded.private void reserveSpace(int reserve, DfsStreamKey key)
private void creditSpace(int credit, DfsStreamKey key)
private void addToClock(DfsBlockCache.Ref ref, int credit)
void put(DfsBlock v)
<T> DfsBlockCache.Ref<T> getOrLoadRef(DfsStreamKey key, DfsBlockCache.RefLoader<T> loader) throws java.io.IOException
key
- the stream key of the pack.loader
- the function to load the reference.java.io.IOException
- the reference was not in the cache and could not be loaded.<T> DfsBlockCache.Ref<T> putRef(DfsStreamKey key, long size, T v)
<T> DfsBlockCache.Ref<T> put(DfsStreamKey key, long pos, int size, T v)
boolean contains(DfsStreamKey key, long position)
<T> T get(DfsStreamKey key, long position)
private <T> T scan(DfsBlockCache.HashEntry n, DfsStreamKey key, long position)
private <T> DfsBlockCache.Ref<T> scanRef(DfsBlockCache.HashEntry n, DfsStreamKey key, long position)
private int slot(DfsStreamKey key, long position)
private java.util.concurrent.locks.ReentrantLock lockFor(DfsStreamKey key, long position)
private java.util.concurrent.locks.ReentrantLock lockForRef(DfsStreamKey key)
private static java.util.concurrent.atomic.AtomicLong[] newCounters()
private static java.util.concurrent.atomic.AtomicLong getStat(java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> stats, DfsStreamKey key)
private static long[] getStatVals(java.util.concurrent.atomic.AtomicReference<java.util.concurrent.atomic.AtomicLong[]> stat)
private static DfsBlockCache.HashEntry clean(DfsBlockCache.HashEntry top)