* Moved the unused vnode management to a new file. Well the few variables
used for it that is. * The main cause for the heavy contention of the unused vnodes mutex was that relatively few vnodes are actually used for a longer time. Mainly those are the volume roots, mmap()ed files, and the files opened by programs. A good deal of nodes -- particularly directories -- are just referenced for a very short time, e.g. to resolve a path to a contained entry. This caused those nodes to be added to and removed from the unused vnodes list very frequently, thus resulting in a high contention of the mutex guarding it. To address the problem I've introduced an approximation of a set of "hot" vnodes, i.e. vnodes that have recently been marked unused. They are stored in an array that by means of an r/w locker and atomic operations can most of the time be accessed concurrently. Whenever it gets full, it is flushed to the actual unused vnodes list. * dec_vnode_ref_count(): No longer check the unused vnode count every time. The called new vnode_unused() does only from time to time and returns when the caller is expected to free some of the unused vnodes. As a side effect this also fixes a bug I previously introduced: The unused vnode to be freed was marked busy without being locked first. The -j8 Haiku image test build shows that the changes reduce the contention of the unused vnode list mutex to virtually zero without introducing any significant contention of the new r/w lock. The VMCache lock contention also seems to be decreased somewhat, which is probably not that surprising considering that the page writer acquires/releases vnode references with the cache lock held. The "pages" lock takes over even more contention, now causing more than 100000 waits per second. The total build time reduction is about 4.5%. Kernel time drops more than 10%. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@34866 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
#include "EntryCache.h"
|
||||
#include "fifo.h"
|
||||
#include "IORequest.h"
|
||||
#include "unused_vnodes.h"
|
||||
#include "Vnode.h"
|
||||
#include "../cache/vnode_store.h"
|
||||
|
||||
@@ -104,13 +105,6 @@
|
||||
#endif
|
||||
|
||||
|
||||
const static uint32 kMaxUnusedVnodes = 8192;
|
||||
// This is the maximum number of unused vnodes that the system
|
||||
// will keep around (weak limit, if there is enough memory left,
|
||||
// they won't get flushed even when hitting that limit).
|
||||
// It may be chosen with respect to the available memory or enhanced
|
||||
// by some timestamp/frequency heurism.
|
||||
|
||||
const static size_t kMaxPathLength = 65536;
|
||||
// The absolute maximum path length (for getcwd() - this is not depending
|
||||
// on PATH_MAX
|
||||
@@ -263,17 +257,9 @@ static rw_lock sVnodeLock = RW_LOCK_INITIALIZER("vfs_vnode_lock");
|
||||
*/
|
||||
static mutex sIOContextRootLock = MUTEX_INITIALIZER("io_context::root lock");
|
||||
|
||||
/*! \brief Guards sUnusedVnodeList and sUnusedVnodes.
|
||||
|
||||
Innermost lock. Must not be held when acquiring any other lock.
|
||||
*/
|
||||
static mutex sUnusedVnodesLock = MUTEX_INITIALIZER("unused vnodes");
|
||||
|
||||
|
||||
#define VNODE_HASH_TABLE_SIZE 1024
|
||||
static hash_table* sVnodeTable;
|
||||
static list sUnusedVnodeList;
|
||||
static uint32 sUnusedVnodes = 0;
|
||||
static struct vnode* sRoot;
|
||||
|
||||
#define MOUNTS_HASH_TABLE_SIZE 16
|
||||
@@ -286,6 +272,8 @@ mode_t __gUmask = 022;
|
||||
|
||||
/* function declarations */
|
||||
|
||||
static void free_unused_vnodes();
|
||||
|
||||
// file descriptor operation prototypes
|
||||
static status_t file_read(struct file_descriptor* descriptor, off_t pos,
|
||||
void* buffer, size_t* _bytes);
|
||||
@@ -1053,33 +1041,24 @@ dec_vnode_ref_count(struct vnode* vnode, bool alwaysFree, bool reenter)
|
||||
panic("dec_vnode_ref_count: called on busy vnode %p\n", vnode);
|
||||
|
||||
bool freeNode = false;
|
||||
bool freeUnusedNodes = false;
|
||||
|
||||
// Just insert the vnode into an unused list if we don't need
|
||||
// to delete it
|
||||
if (vnode->IsRemoved() || alwaysFree) {
|
||||
vnode_to_be_freed(vnode);
|
||||
vnode->SetBusy(true);
|
||||
freeNode = true;
|
||||
} else {
|
||||
MutexLocker unusedVnodesLocker(sUnusedVnodesLock);
|
||||
list_add_item(&sUnusedVnodeList, vnode);
|
||||
if (++sUnusedVnodes > kMaxUnusedVnodes
|
||||
&& low_resource_state(
|
||||
B_KERNEL_RESOURCE_PAGES | B_KERNEL_RESOURCE_MEMORY)
|
||||
!= B_NO_LOW_RESOURCE) {
|
||||
// there are too many unused vnodes so we free the oldest one
|
||||
// TODO: evaluate this mechanism
|
||||
vnode = (struct vnode*)list_remove_head_item(&sUnusedVnodeList);
|
||||
vnode->SetBusy(true);
|
||||
freeNode = true;
|
||||
sUnusedVnodes--;
|
||||
}
|
||||
}
|
||||
} else
|
||||
freeUnusedNodes = vnode_unused(vnode);
|
||||
|
||||
nodeLocker.Unlock();
|
||||
locker.Unlock();
|
||||
|
||||
if (freeNode)
|
||||
free_vnode(vnode, reenter);
|
||||
else if (freeUnusedNodes)
|
||||
free_unused_vnodes();
|
||||
|
||||
return B_OK;
|
||||
}
|
||||
@@ -1178,9 +1157,7 @@ restart:
|
||||
if (vnode) {
|
||||
if (vnode->ref_count == 0) {
|
||||
// this vnode has been unused before
|
||||
MutexLocker unusedVnodesLocker(sUnusedVnodesLock);
|
||||
list_remove_item(&sUnusedVnodeList, vnode);
|
||||
sUnusedVnodes--;
|
||||
vnode_used(vnode);
|
||||
}
|
||||
inc_vnode_ref_count(vnode);
|
||||
|
||||
@@ -1269,9 +1246,16 @@ put_vnode(struct vnode* vnode)
|
||||
|
||||
|
||||
static void
|
||||
vnode_low_resource_handler(void* /*data*/, uint32 resources, int32 level)
|
||||
free_unused_vnodes(int32 level)
|
||||
{
|
||||
TRACE(("vnode_low_resource_handler(level = %ld)\n", level));
|
||||
unused_vnodes_check_started();
|
||||
|
||||
if (level == B_NO_LOW_RESOURCE) {
|
||||
unused_vnodes_check_done();
|
||||
return;
|
||||
}
|
||||
|
||||
flush_hot_vnodes();
|
||||
|
||||
// determine how many nodes to free
|
||||
uint32 count = 1;
|
||||
@@ -1279,8 +1263,6 @@ vnode_low_resource_handler(void* /*data*/, uint32 resources, int32 level)
|
||||
MutexLocker unusedVnodesLocker(sUnusedVnodesLock);
|
||||
|
||||
switch (level) {
|
||||
case B_NO_LOW_RESOURCE:
|
||||
return;
|
||||
case B_LOW_RESOURCE_NOTE:
|
||||
count = sUnusedVnodes / 100;
|
||||
break;
|
||||
@@ -1336,6 +1318,25 @@ vnode_low_resource_handler(void* /*data*/, uint32 resources, int32 level)
|
||||
dec_vnode_ref_count(vnode, true, false);
|
||||
// this should free the vnode when it's still unused
|
||||
}
|
||||
|
||||
unused_vnodes_check_done();
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
free_unused_vnodes()
|
||||
{
|
||||
free_unused_vnodes(
|
||||
low_resource_state(B_KERNEL_RESOURCE_PAGES | B_KERNEL_RESOURCE_MEMORY));
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
vnode_low_resource_handler(void* /*data*/, uint32 resources, int32 level)
|
||||
{
|
||||
TRACE(("vnode_low_resource_handler(level = %ld)\n", level));
|
||||
|
||||
free_unused_vnodes(level);
|
||||
}
|
||||
|
||||
|
||||
@@ -7292,9 +7293,7 @@ fs_unmount(char* path, dev_t mountID, uint32 flags, bool kernel)
|
||||
|
||||
if (vnode->ref_count == 0) {
|
||||
// this vnode has been unused before
|
||||
MutexLocker unusedVnodesLocker(sUnusedVnodesLock);
|
||||
list_remove_item(&sUnusedVnodeList, vnode);
|
||||
sUnusedVnodes--;
|
||||
vnode_used(vnode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7402,9 +7401,7 @@ fs_sync(dev_t device)
|
||||
|
||||
if (vnode->ref_count == 0) {
|
||||
// this vnode has been unused before
|
||||
MutexLocker unusedVnodesLocker(sUnusedVnodesLock);
|
||||
list_remove_item(&sUnusedVnodeList, vnode);
|
||||
sUnusedVnodes--;
|
||||
vnode_used(vnode);
|
||||
}
|
||||
inc_vnode_ref_count(vnode);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user