⛏️ index : haiku.git

author Augustin Cavalier <waddlesplash@gmail.com> 2022-11-30 20:27:17.0 -05:00:00
committer waddlesplash <waddlesplash@gmail.com> 2022-12-01 1:28:54.0 +00:00:00
commit
41b1ec3e58bbb358095d219fd51ce5c4ffc0b6db [patch]
tree
8c4da8c24591ed4a232b1a274829a45030bb21a1
parent
fb7aa63d1891d0904a764309d1042209353c1c0d
download
41b1ec3e58bbb358095d219fd51ce5c4ffc0b6db.tar.gz

ramfs: Switch from an embedded to a separately allocated small buffer.

This saves 16 bytes in the class vs. the old size, but more importantly
allows us to avoid allocating VMCaches and wasting an entire page for
any attribute larger than 32 bytes; instead, attributes can be up to
1024 bytes before we allocate a full page for them.

Unfortunately small files cannot take advantage of this optimization
right now as the cache is always used for them. I added a TODO about
this.
(cherry picked from commit 396e3dfb9ceaabd07a9a9e3e84b35efae85e1831)

Change-Id: I6c939565dd577cb86aeb7b518a219da01cb313a1
Reviewed-on: https://review.haiku-os.org/c/haiku/+/5849
Reviewed-by: waddlesplash <waddlesplash@gmail.com>

Diff

 src/add-ons/kernel/file_systems/ramfs/DataContainer.cpp | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------
 src/add-ons/kernel/file_systems/ramfs/DataContainer.h   | 25 +++----------------------
 2 files changed, 57 insertions(+), 36 deletions(-)

diff --git a/src/add-ons/kernel/file_systems/ramfs/DataContainer.cpp b/src/add-ons/kernel/file_systems/ramfs/DataContainer.cpp
index bf224a1..3dcc839 100644
--- a/src/add-ons/kernel/file_systems/ramfs/DataContainer.cpp
+++ b/src/add-ons/kernel/file_systems/ramfs/DataContainer.cpp
@@ -7,6 +7,7 @@

#include <AutoDeleter.h>
#include <util/AutoLock.h>
#include <util/BitUtils.h>

#include <vm/VMCache.h>
#include <vm/vm_page.h>
@@ -16,11 +17,37 @@
#include "Misc.h"
#include "Volume.h"


// Initial size of the DataContainer's small buffer. If it contains data up to
// this size, nothing is allocated, but the small buffer is used instead.
// 16 bytes are for free, since they are shared with the block list.
// (actually even more, since the list has an initial size).
// I ran a test analyzing what sizes the attributes in my system have:
//     size   percentage   bytes used in average
//   <=   0         0.00                   93.45
//   <=   4        25.46                   75.48
//   <=   8        30.54                   73.02
//   <=  16        52.98                   60.37
//   <=  32        80.19                   51.74
//   <=  64        94.38                   70.54
//   <= 126        96.90                  128.23
//
// For average memory usage it is assumed, that attributes larger than 126
// bytes have size 127, that the list has an initial capacity of 10 entries
// (40 bytes), that the block reference consumes 4 bytes and the block header
// 12 bytes. The optimal length is actually 35, with 51.05 bytes per
// attribute, but I conservatively rounded to 32.
static const off_t kMinimumSmallBufferSize = 32;
static const off_t kMaximumSmallBufferSize = (B_PAGE_SIZE / 4);


// constructor
DataContainer::DataContainer(Volume *volume)
	: fVolume(volume),
	  fSize(0),
	  fCache(NULL)
	  fCache(NULL),
	  fSmallBuffer(NULL),
	  fSmallBufferSize(0)
{
}

@@ -31,6 +58,10 @@
		fCache->Lock();
		fCache->ReleaseRefAndUnlock();
		fCache = NULL;
	}
	if (fSmallBuffer != NULL) {
		free(fSmallBuffer);
		fSmallBuffer = NULL;
	}
}

@@ -45,6 +76,8 @@
VMCache*
DataContainer::GetCache()
{
	// TODO: Because we always get the cache for files on creation vs. on demand,
	// this means files (no matter how small) always use cache mode at present.
	if (!_IsCacheMode())
		_SwitchToCacheMode();
	return fCache;
@@ -57,20 +90,16 @@
//	PRINT("DataContainer::Resize(%Ld), fSize: %Ld\n", newSize, fSize);

	status_t error = B_OK;
	if (newSize < fSize) {
		// shrink
		if (_IsCacheMode()) {
	if (_RequiresCacheMode(newSize)) {
		if (newSize < fSize) {
			// shrink
			// resize the VMCache, which will automatically free pages
			AutoLocker<VMCache> _(fCache);
			error = fCache->Resize(newSize, VM_PRIORITY_SYSTEM);
			if (error != B_OK)
				return error;
		} else {
			// small buffer mode: just set the new size (done below)
		}
	} else if (newSize > fSize) {
		// grow
		if (_RequiresCacheMode(newSize)) {
			// grow
			if (!_IsCacheMode())
				error = _SwitchToCacheMode();
			if (error != B_OK)
@@ -81,10 +110,17 @@

			// pages will be added as they are written to; so nothing else
			// needs to be done here.
		} else {
			// no need to switch to cache mode: just set the new size
			// (done below)
		}
	} else if (fSmallBufferSize < newSize
			|| (fSmallBufferSize - newSize) > (kMaximumSmallBufferSize / 2)) {
		const size_t newBufferSize = max_c(next_power_of_2(newSize),
			kMinimumSmallBufferSize);
		void* newBuffer = realloc(fSmallBuffer, newBufferSize);
		if (newBuffer == NULL)
			return B_NO_MEMORY;

		fSmallBufferSize = newBufferSize;
		fSmallBuffer = (uint8*)newBuffer;
	}

	fSize = newSize;
@@ -142,7 +178,7 @@
		return error;

	if (!_IsCacheMode()) {
		// in non-cache mode, we just copy the data directly
		// in non-cache mode, use the "small buffer"
		memcpy(fSmallBuffer + offset, buffer, size);
		if (bytesWritten != NULL)
			*bytesWritten = size;
@@ -173,7 +209,7 @@
{
	// we cannot back out of cache mode after entering it,
	// as there may be other consumers of our VMCache
	return _IsCacheMode() || (size > kSmallDataContainerSize);
	return _IsCacheMode() || (size > kMaximumSmallBufferSize);
}

// _IsCacheMode
@@ -212,6 +248,10 @@

	if (fSize != 0)
		error = _DoCacheIO(0, fSmallBuffer, fSize, NULL, true);

	free(fSmallBuffer);
	fSmallBuffer = NULL;
	fSmallBufferSize = 0;

	return error;
}
diff --git a/src/add-ons/kernel/file_systems/ramfs/DataContainer.h b/src/add-ons/kernel/file_systems/ramfs/DataContainer.h
index 053623a..4eef20b 100644
--- a/src/add-ons/kernel/file_systems/ramfs/DataContainer.h
+++ b/src/add-ons/kernel/file_systems/ramfs/DataContainer.h
@@ -13,27 +13,6 @@
class AllocationInfo;
class Volume;

// Size of the DataContainer's small buffer. If it contains data up to this
// size, no blocks are allocated, but the small buffer is used instead.
// 16 bytes are for free, since they are shared with the block list.
// (actually even more, since the list has an initial size).
// I ran a test analyzing what sizes the attributes in my system have:
//     size   percentage   bytes used in average
//   <=   0         0.00                   93.45
//   <=   4        25.46                   75.48
//   <=   8        30.54                   73.02
//   <=  16        52.98                   60.37
//   <=  32        80.19                   51.74
//   <=  64        94.38                   70.54
//   <= 126        96.90                  128.23
//
// For average memory usage it is assumed, that attributes larger than 126
// bytes have size 127, that the list has an initial capacity of 10 entries
// (40 bytes), that the block reference consumes 4 bytes and the block header
// 12 bytes. The optimal length is actually 35, with 51.05 bytes per
// attribute, but I conservatively rounded to 32.
static const size_t kSmallDataContainerSize = 32;

class DataContainer {
public:
	DataContainer(Volume *volume);
@@ -71,7 +50,9 @@
	Volume				*fVolume;
	off_t				fSize;
	VMCache*			fCache;
	uint8				fSmallBuffer[kSmallDataContainerSize];

	uint8*				fSmallBuffer;
	off_t				fSmallBufferSize;
};

#endif	// DATA_CONTAINER_H