From 1b1b94b85ac1d2508b6dea91008433bd2ade45b4 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Fri, 8 Jan 2010 17:02:24 +0000 Subject: [PATCH] Entry cache: * Moved hash computations out of the critical sections. * Replaced the LRU entry queue by an array of entry "generations", each containing a sparse array of entries of that generation. Whenever a generation is full, we clear the oldest generation and continue with that one. The main advantage of this algorithm is that entry cache's mutex could be replaced by an r/w lock, that most of the time only has to be read locked in Lookup(). This does dramatically decrease contention of that lock. The total -j8 Haiku image build speedup is marginal, but the kernel time drops about 7% (now being smaller than the real time). git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@34950 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/system/kernel/fs/EntryCache.cpp | 237 ++++++++++++++++++++++++++++ src/system/kernel/fs/EntryCache.h | 146 ++++++----------- src/system/kernel/fs/Jamfile | 1 + 3 files changed, 283 insertions(+), 101 deletions(-) create mode 100644 src/system/kernel/fs/EntryCache.cpp diff --git a/src/system/kernel/fs/EntryCache.cpp b/src/system/kernel/fs/EntryCache.cpp new file mode 100644 index 0000000000..8d288facda --- /dev/null +++ b/src/system/kernel/fs/EntryCache.cpp @@ -0,0 +1,237 @@ +/* + * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de. + * Distributed under the terms of the MIT License. + */ + + +#include "EntryCache.h" + +#include + + +static const int32 kEntriesPerGeneration = 1024; + +static const int32 kEntryNotInArray = -1; +static const int32 kEntryRemoved = -2; + + +// #pragma mark - EntryCacheGeneration + + +EntryCacheGeneration::EntryCacheGeneration() + : + next_index(0), + entries(NULL) +{ +} + + +EntryCacheGeneration::~EntryCacheGeneration() +{ + delete[] entries; +} + + +status_t +EntryCacheGeneration::Init() +{ + entries = new(std::nothrow) EntryCacheEntry*[kEntriesPerGeneration]; + if (entries == NULL) + return B_NO_MEMORY; + + memset(entries, 0, sizeof(EntryCacheEntry*) * kEntriesPerGeneration); + + return B_OK; +} + + +// #pragma mark - EntryCache + + +EntryCache::EntryCache() + : + fCurrentGeneration(0) +{ + rw_lock_init(&fLock, "entry cache"); + + new(&fEntries) EntryTable; +} + + +EntryCache::~EntryCache() +{ + // delete entries + EntryCacheEntry* entry = fEntries.Clear(true); + while (entry != NULL) { + EntryCacheEntry* next = entry->hash_link; + free(entry); + entry = next; + } + + rw_lock_destroy(&fLock); +} + + +status_t +EntryCache::Init() +{ + status_t error = fEntries.Init(); + if (error != B_OK) + return error; + + for (int32 i = 0; i < kGenerationCount; i++) { + error = fGenerations[i].Init(); + if (error != B_OK) + return error; + } + + return B_OK; +} + + +status_t +EntryCache::Add(ino_t dirID, const char* name, ino_t nodeID) +{ + EntryCacheKey key(dirID, name); + + WriteLocker _(fLock); + + EntryCacheEntry* entry = fEntries.Lookup(key); + if (entry != NULL) { + entry->node_id = nodeID; + if (entry->generation != fCurrentGeneration) { + if (entry->index >= 0) { + fGenerations[entry->generation].entries[entry->index] = NULL; + _AddEntryToCurrentGeneration(entry); + } + } + return B_OK; + } + + entry = (EntryCacheEntry*)malloc(sizeof(EntryCacheEntry) + strlen(name)); + if (entry == NULL) + return B_NO_MEMORY; + + entry->node_id = nodeID; + entry->dir_id = dirID; + entry->generation = fCurrentGeneration; + entry->index = kEntryNotInArray; + strcpy(entry->name, name); + + fEntries.Insert(entry); + + _AddEntryToCurrentGeneration(entry); + + return B_OK; +} + + +status_t +EntryCache::Remove(ino_t dirID, const char* name) +{ + EntryCacheKey key(dirID, name); + + WriteLocker writeLocker(fLock); + + EntryCacheEntry* entry = fEntries.Lookup(key); + if (entry == NULL) + return B_ENTRY_NOT_FOUND; + + fEntries.Remove(entry); + + if (entry->index >= 0) { + // remove the entry from its generation and delete it + fGenerations[entry->generation].entries[entry->index] = NULL; + free(entry); + } else { + // We can't free it, since another thread is about to try to move it + // to another generation. We mark it removed and the other thread will + // take care of deleting it. + entry->index = kEntryRemoved; + } + + return B_OK; +} + + +bool +EntryCache::Lookup(ino_t dirID, const char* name, ino_t& _nodeID) +{ + EntryCacheKey key(dirID, name); + + ReadLocker readLocker(fLock); + + EntryCacheEntry* entry = fEntries.Lookup(key); + if (entry == NULL) + return false; + + int32 oldGeneration = atomic_set(&entry->generation, fCurrentGeneration); + if (oldGeneration == fCurrentGeneration || entry->index < 0) { + // The entry is already in the current generation or is being moved to + // it by another thread. + _nodeID = entry->node_id; + return true; + } + + // remove from old generation array + fGenerations[oldGeneration].entries[entry->index] = NULL; + entry->index = kEntryNotInArray; + + // add to the current generation + int32 index = atomic_add(&fGenerations[oldGeneration].next_index, 1); + if (index < kEntriesPerGeneration) { + fGenerations[fCurrentGeneration].entries[index] = entry; + entry->index = index; + _nodeID = entry->node_id; + return true; + } + + // The current generation is full, so we probably need to clear the oldest + // one to make room. We need the write lock for that. + readLocker.Unlock(); + WriteLocker writeLocker(fLock); + + if (entry->index == kEntryRemoved) { + // the entry has been removed in the meantime + free(entry); + return false; + } + + _AddEntryToCurrentGeneration(entry); + + _nodeID = entry->node_id; + return true; +} + + +void +EntryCache::_AddEntryToCurrentGeneration(EntryCacheEntry* entry) +{ + // the generation might not be full yet + int32 index = fGenerations[fCurrentGeneration].next_index++; + if (index < kEntriesPerGeneration) { + fGenerations[fCurrentGeneration].entries[index] = entry; + entry->generation = fCurrentGeneration; + entry->index = index; + return; + } + + // we have to clear the oldest generation + int32 newGeneration = (fCurrentGeneration + 1) % kGenerationCount; + for (int32 i = 0; i < kEntriesPerGeneration; i++) { + EntryCacheEntry* otherEntry = fGenerations[newGeneration].entries[i]; + if (otherEntry == NULL) + continue; + + fGenerations[newGeneration].entries[i] = NULL; + fEntries.Remove(otherEntry); + free(otherEntry); + } + + // set the new generation and add the entry + fCurrentGeneration = newGeneration; + fGenerations[newGeneration].next_index = 1; + fGenerations[newGeneration].entries[0] = entry; + entry->generation = newGeneration; + entry->index = 0; +} diff --git a/src/system/kernel/fs/EntryCache.h b/src/system/kernel/fs/EntryCache.h index 0af38eceaa..12726b1582 100644 --- a/src/system/kernel/fs/EntryCache.h +++ b/src/system/kernel/fs/EntryCache.h @@ -1,5 +1,5 @@ /* - * Copyright 2008-2009, Ingo Weinhold, ingo_weinhold@gmx.de. + * Copyright 2008-2010, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #ifndef ENTRY_CACHE_H @@ -9,32 +9,45 @@ #include #include -#include #include #include -const static uint32 kMaxEntryCacheEntryCount = 8192; - // Maximum number of entries per entry cache. It's a hard limit ATM. - struct EntryCacheKey { EntryCacheKey(ino_t dirID, const char* name) : dir_id(dirID), name(name) { + hash = (uint32)dir_id ^ (uint32)(dir_id >> 32) ^ hash_hash_string(name); + // We cache the hash value, so we can easily compute it before + // holding any locks. } ino_t dir_id; const char* name; + size_t hash; }; -struct EntryCacheEntry : DoublyLinkedListLinkImpl { - EntryCacheEntry* hash_link; - ino_t node_id; - ino_t dir_id; - char name[1]; +struct EntryCacheEntry { + EntryCacheEntry* hash_link; + ino_t node_id; + ino_t dir_id; + vint32 generation; + vint32 index; + char name[1]; +}; + + +struct EntryCacheGeneration { + vint32 next_index; + EntryCacheEntry** entries; + + EntryCacheGeneration(); + ~EntryCacheGeneration(); + + status_t Init(); }; @@ -44,8 +57,7 @@ struct EntryCacheHashDefinition { uint32 HashKey(const EntryCacheKey& key) const { - return (uint32)key.dir_id ^ (uint32)(key.dir_id >> 32) - ^ hash_hash_string(key.name); + return key.hash; } size_t Hash(const EntryCacheEntry* value) const @@ -69,102 +81,34 @@ struct EntryCacheHashDefinition { class EntryCache { public: - EntryCache() - { - mutex_init(&fLock, "entry cache"); + EntryCache(); + ~EntryCache(); - new(&fEntries) EntryTable; - new(&fUsedEntries) EntryList; - fEntryCount = 0; - } + status_t Init(); - ~EntryCache() - { - while (EntryCacheEntry* entry = fUsedEntries.Head()) - _Remove(entry); + status_t Add(ino_t dirID, const char* name, + ino_t nodeID); - mutex_destroy(&fLock); - } + status_t Remove(ino_t dirID, const char* name); - status_t Init() - { - return fEntries.Init(); - } - - status_t Add(ino_t dirID, const char* name, ino_t nodeID) - { - MutexLocker _(fLock); - - EntryCacheEntry* entry = fEntries.Lookup(EntryCacheKey(dirID, name)); - if (entry != NULL) { - entry->node_id = nodeID; - return B_OK; - } - - if (fEntryCount >= kMaxEntryCacheEntryCount) - _Remove(fUsedEntries.Head()); - - entry = (EntryCacheEntry*)malloc(sizeof(EntryCacheEntry) - + strlen(name)); - if (entry == NULL) - return B_NO_MEMORY; - - entry->node_id = nodeID; - entry->dir_id = dirID; - strcpy(entry->name, name); - - fEntries.Insert(entry); - fUsedEntries.Add(entry); - fEntryCount++; - - return B_OK; - } - - status_t Remove(ino_t dirID, const char* name) - { - MutexLocker _(fLock); - - EntryCacheEntry* entry = fEntries.Lookup(EntryCacheKey(dirID, name)); - if (entry == NULL) - return B_ENTRY_NOT_FOUND; - - _Remove(entry); - - return B_OK; - } - - bool Lookup(ino_t dirID, const char* name, ino_t& nodeID) - { - MutexLocker _(fLock); - - EntryCacheEntry* entry = fEntries.Lookup(EntryCacheKey(dirID, name)); - if (entry == NULL) - return false; - - // requeue at the end - fUsedEntries.Remove(entry); - fUsedEntries.Add(entry); - - nodeID = entry->node_id; - return true; - } - - void _Remove(EntryCacheEntry* entry) - { - fEntries.Remove(entry); - fUsedEntries.Remove(entry); - free(entry); - fEntryCount--; - } + bool Lookup(ino_t dirID, const char* name, + ino_t& nodeID); private: - typedef BOpenHashTable EntryTable; - typedef DoublyLinkedList EntryList; + static const int32 kGenerationCount = 8; - mutex fLock; - EntryTable fEntries; - EntryList fUsedEntries; // LRU queue (LRU entry at the head) - uint32 fEntryCount; + typedef BOpenHashTable EntryTable; + typedef DoublyLinkedList EntryList; + +private: + void _AddEntryToCurrentGeneration( + EntryCacheEntry* entry); + +private: + rw_lock fLock; + EntryTable fEntries; + EntryCacheGeneration fGenerations[kGenerationCount]; + int32 fCurrentGeneration; }; diff --git a/src/system/kernel/fs/Jamfile b/src/system/kernel/fs/Jamfile index 8bef928694..d2d0f84ab7 100644 --- a/src/system/kernel/fs/Jamfile +++ b/src/system/kernel/fs/Jamfile @@ -8,6 +8,7 @@ UsePrivateHeaders net shared storage ; UseHeaders [ FDirName $(SUBDIR) $(DOTDOT) device_manager ] ; KernelMergeObject kernel_fs.o : + EntryCache.cpp fd.cpp fifo.cpp KPath.cpp