From b91d06bf3ede4b9afa5a74070a4f8ca95d16f629 Mon Sep 17 00:00:00 2001 From: Peter Amstutz Date: Mon, 1 Apr 2024 15:58:06 -0400 Subject: [PATCH] 21639: Improve critical path of read() from cache * Don't use tobytes(), it makes a copy, and it should be be zero-copy. * Prefetching adds a lot of overhead. Don't do it. * Don't use a list comprehension to calculate cache size Arvados-DCO-1.1-Signed-off-by: Peter Amstutz --- sdk/python/arvados/arvfile.py | 19 ++++++++++++------- sdk/python/arvados/keep.py | 13 +++++++++++-- services/fuse/arvados_fuse/command.py | 3 ++- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/sdk/python/arvados/arvfile.py b/sdk/python/arvados/arvfile.py index 4b95835aac..0cc7d25a33 100644 --- a/sdk/python/arvados/arvfile.py +++ b/sdk/python/arvados/arvfile.py @@ -1060,7 +1060,8 @@ class ArvadosFile(object): if size == 0 or offset >= self.size(): return b'' readsegs = locators_and_ranges(self._segments, offset, size) - prefetch = locators_and_ranges(self._segments, offset + size, config.KEEP_BLOCK_SIZE * self.parent._my_block_manager()._keep.num_prefetch_threads, limit=32) + if self.parent._my_block_manager()._keep.num_prefetch_threads > 0: + prefetch = locators_and_ranges(self._segments, offset + size, config.KEEP_BLOCK_SIZE * self.parent._my_block_manager()._keep.num_prefetch_threads, limit=32) locs = set() data = [] @@ -1068,17 +1069,21 @@ class ArvadosFile(object): block = self.parent._my_block_manager().get_block_contents(lr.locator, num_retries=num_retries, cache_only=(bool(data) and not exact)) if block: blockview = memoryview(block) - data.append(blockview[lr.segment_offset:lr.segment_offset+lr.segment_size].tobytes()) + data.append(blockview[lr.segment_offset:lr.segment_offset+lr.segment_size]) locs.add(lr.locator) else: break - for lr in prefetch: - if lr.locator not in locs: - self.parent._my_block_manager().block_prefetch(lr.locator) - locs.add(lr.locator) + if self.parent._my_block_manager()._keep.num_prefetch_threads > 0: + for lr in prefetch: + if lr.locator not in locs: + self.parent._my_block_manager().block_prefetch(lr.locator) + locs.add(lr.locator) - return b''.join(data) + if len(data) == 1: + return data[0] + else: + return b''.join(data) @must_be_writable @synchronized diff --git a/sdk/python/arvados/keep.py b/sdk/python/arvados/keep.py index 1d0fc5f159..6b34a1f933 100644 --- a/sdk/python/arvados/keep.py +++ b/sdk/python/arvados/keep.py @@ -271,8 +271,14 @@ class KeepBlockCache(object): # Try and make sure the contents of the cache do not exceed # the supplied maximums. + sm = 0 + for slot in self._cache.values(): + sm += slot.size() + + if sm <= cache_max and len(self._cache) <= max_slots: + return + _evict_candidates = collections.deque(self._cache.values()) - sm = sum([slot.size() for slot in _evict_candidates]) while len(_evict_candidates) > 0 and (sm > cache_max or len(self._cache) > max_slots): slot = _evict_candidates.popleft() if not slot.ready.is_set(): @@ -926,7 +932,10 @@ class KeepClient(object): self.misses_counter = Counter() self._storage_classes_unsupported_warning = False self._default_classes = [] - self.num_prefetch_threads = num_prefetch_threads or 2 + if num_prefetch_threads is not None: + self.num_prefetch_threads = num_prefetch_threads + else: + self.num_prefetch_threads = 2 self._prefetch_queue = None self._prefetch_threads = None diff --git a/services/fuse/arvados_fuse/command.py b/services/fuse/arvados_fuse/command.py index 719ec7ee95..29ace2e52e 100644 --- a/services/fuse/arvados_fuse/command.py +++ b/services/fuse/arvados_fuse/command.py @@ -487,7 +487,8 @@ class Mount(object): # layer actually ends up being slower. # Experimentally, capping 7 threads seems to # be a sweet spot. - prefetch_threads = min(max((block_cache.cache_max // (64 * 1024 * 1024)) - 1, 1), 7) + #prefetch_threads = min(max((block_cache.cache_max // (64 * 1024 * 1024)) - 1, 1), 7) + prefetch_threads = 0 self.api = arvados.safeapi.ThreadSafeApiCache( apiconfig=arvados.config.settings(), -- 2.30.2