| 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