Revert "Ring Buffer: Replace virtual buffers"

This reverts commit 476c88973b.
This commit is contained in:
Christopher Snowhill 2022-02-01 18:45:12 -08:00
parent 641f6390c5
commit 78e960a9e4

View file

@ -17,44 +17,16 @@
#import "VirtualRingBuffer.h" #import "VirtualRingBuffer.h"
#import <Foundation/Foundation.h> #include <mach/mach.h>
#import <stdlib.h> #include <mach/mach_error.h>
#import <mm_malloc.h>
#import "Logging.h" #import "Logging.h"
@interface block_chunk : NSObject {
void * blockPointer;
void * theBlock;
size_t blockSize;
}
@property void * blockPointer;
@property void * theBlock;
@property size_t blockSize;
@end
@implementation block_chunk
@synthesize blockPointer;
@synthesize theBlock;
@synthesize blockSize;
@end
@interface VirtualBufferHolder : NSObject {
NSMutableArray * blocks;
NSMutableArray * blocksUsed;
NSMutableDictionary * blockRefCounts;
}
+ (VirtualBufferHolder *) sharedInstance;
- (void *) allocateBlock:(size_t)size;
- (void) freeBlock:(void *)block;
@end
@implementation VirtualRingBuffer @implementation VirtualRingBuffer
static void *allocateVirtualBuffer(UInt32 bufferLength);
static void deallocateVirtualBuffer(void *buffer, UInt32 bufferLength);
- (id)initWithLength:(UInt32)length - (id)initWithLength:(UInt32)length
{ {
@ -64,7 +36,7 @@
// We need to allocate entire VM pages, so round the specified length up to the next page if necessary. // We need to allocate entire VM pages, so round the specified length up to the next page if necessary.
bufferLength = (UInt32) round_page(length); bufferLength = (UInt32) round_page(length);
buffer = [[VirtualBufferHolder sharedInstance] allocateBlock:bufferLength]; buffer = allocateVirtualBuffer(bufferLength);
if (!buffer) if (!buffer)
{ {
self = nil; self = nil;
@ -83,7 +55,7 @@
- (void)dealloc - (void)dealloc
{ {
if (buffer) if (buffer)
[[VirtualBufferHolder sharedInstance] freeBlock:buffer]; deallocateVirtualBuffer(buffer, bufferLength);
} }
- (void)empty - (void)empty
@ -207,106 +179,106 @@
@end @end
@implementation VirtualBufferHolder
static VirtualBufferHolder * g_instance = nil; void *allocateVirtualBuffer(UInt32 bufferLength)
+ (VirtualBufferHolder *) sharedInstance
{ {
@synchronized (g_instance) { kern_return_t error;
if (!g_instance) { vm_address_t originalAddress = (vm_address_t)NULL;
g_instance = [[VirtualBufferHolder alloc] init]; vm_address_t realAddress = (vm_address_t)NULL;
} mach_port_t memoryEntry;
return g_instance; vm_size_t memoryEntryLength;
} vm_address_t virtualAddress = (vm_address_t)NULL;
// We want to find where we can get 2 * bufferLength bytes of contiguous address space.
// So let's just allocate that space, remember its address, and deallocate it.
// (This doesn't actually have to touch all of that memory so it's not terribly expensive.)
error = vm_allocate(mach_task_self(), &originalAddress, 2 * bufferLength, TRUE);
if (error) {
#if DEBUG
mach_error("vm_allocate initial chunk", error);
#endif
return NULL;
} }
- (id) init { error = vm_deallocate(mach_task_self(), originalAddress, 2 * bufferLength);
self = [super init]; if (error) {
#if DEBUG
if (self) { mach_error("vm_deallocate initial chunk", error);
blocks = [[NSMutableArray alloc] init]; #endif
blocksUsed = [[NSMutableArray alloc] init]; return NULL;
blockRefCounts = [[NSMutableDictionary alloc] init];
} }
return self; // Then allocate a "real" block of memory at the same address, but with the normal bufferLength.
realAddress = originalAddress;
error = vm_allocate(mach_task_self(), &realAddress, bufferLength, FALSE);
if (error) {
#if DEBUG
mach_error("vm_allocate real chunk", error);
#endif
return NULL;
}
if (realAddress != originalAddress) {
DLog(@"allocateVirtualBuffer: vm_allocate 2nd time didn't return same address (%p vs %p)", (void *) originalAddress, (void *) realAddress);
goto errorReturn;
} }
- (void *)allocateBlock:(size_t)size { // Then make a memory entry for the area we just allocated.
@synchronized(blocks) { memoryEntryLength = bufferLength;
tryagain: error = mach_make_memory_entry(mach_task_self(), &memoryEntryLength, realAddress, VM_PROT_READ | VM_PROT_WRITE, &memoryEntry, (vm_address_t)NULL);
for (block_chunk * chunk in blocks) { if (error) {
if (chunk.blockSize == size) { #if DEBUG
[blocksUsed addObject:chunk]; mach_error("mach_make_memory_entry", error);
[blocks removeObject:chunk]; #endif
NSInteger refCount = [[blockRefCounts objectForKey:[NSNumber numberWithLongLong:(uintptr_t)chunk.theBlock]] integerValue]; goto errorReturn;
[blockRefCounts setObject:[NSNumber numberWithInteger:refCount + 1] forKey:[NSNumber numberWithLongLong:(uintptr_t)chunk.theBlock]];
return chunk.blockPointer;
} }
if (!memoryEntry) {
DLog(@"mach_make_memory_entry: returned memoryEntry of NULL");
goto errorReturn;
} }
if (![blocks count]) { if (memoryEntryLength != bufferLength) {
void * theBlock = _mm_malloc(32 * 1024 * 1024, 1024); DLog(@"mach_make_memory_entry: size changed (from %0x to %0lx)", bufferLength, memoryEntryLength);
if (!theBlock) return NULL; goto errorReturn;
@synchronized (blocks) {
block_chunk * chunk = [[block_chunk alloc] init];
chunk.theBlock = theBlock;
chunk.blockPointer = theBlock;
chunk.blockSize = 4 * 1024 * 1024;
[blocks addObject:chunk];
chunk = [[block_chunk alloc] init];
chunk.theBlock = theBlock;
chunk.blockPointer = theBlock + 4 * 1024 * 1024;
chunk.blockSize = 4 * 1024 * 1024;
[blocks addObject:chunk];
for (size_t i = 8 * 1024 * 1024; i < 32 * 1024 * 1024; i += 1024 * 1024) {
chunk = [[block_chunk alloc] init];
chunk.theBlock = theBlock;
chunk.blockPointer = theBlock + i;
chunk.blockSize = 1024 * 1024;
[blocks addObject:chunk];
} }
// And map the area immediately after the first block, with length bufferLength, to that memory entry.
virtualAddress = realAddress + bufferLength;
error = vm_map(mach_task_self(), &virtualAddress, bufferLength, 0, FALSE, memoryEntry, 0, FALSE, VM_PROT_READ | VM_PROT_WRITE, VM_PROT_READ | VM_PROT_WRITE, VM_INHERIT_DEFAULT);
if (error) {
#if DEBUG
mach_error("vm_map", error);
#endif
// TODO Retry from the beginning, instead of failing completely. There is a tiny (but > 0) probability that someone
// will allocate this space out from under us.
virtualAddress = (vm_address_t)NULL;
goto errorReturn;
} }
goto tryagain; if (virtualAddress != realAddress + bufferLength) {
} DLog(@"vm_map: didn't return correct address (%p vs %p)", (void *) realAddress + bufferLength, (void *) virtualAddress);
goto errorReturn;
} }
// Success!
return (void *)realAddress;
errorReturn:
if (realAddress)
vm_deallocate(mach_task_self(), realAddress, bufferLength);
if (virtualAddress)
vm_deallocate(mach_task_self(), virtualAddress, bufferLength);
return NULL; return NULL;
} }
- (void) freeBlock:(void *)block { void deallocateVirtualBuffer(void *buffer, UInt32 bufferLength)
@synchronized(blocks) { {
for (block_chunk * chunk in blocksUsed) { kern_return_t error;
if (chunk.blockPointer == block) {
[blocks addObject:chunk];
[blocksUsed removeObject:chunk];
NSInteger refCount = [[blockRefCounts objectForKey:[NSNumber numberWithLongLong:(uintptr_t)chunk.theBlock]] integerValue];
if (refCount <= 1) {
[blockRefCounts removeObjectForKey:[NSNumber numberWithLongLong:(uintptr_t)chunk.theBlock]];
NSArray * blocksCopy = [blocks copy];
for (block_chunk * removeChunk in blocksCopy) {
if (removeChunk.theBlock == chunk.theBlock) {
[blocks removeObject:removeChunk];
}
}
_mm_free(chunk.theBlock);
}
else {
[blockRefCounts setObject:[NSNumber numberWithInteger:refCount - 1] forKey:[NSNumber numberWithLongLong:(uintptr_t)chunk.theBlock]];
}
return;
}
}
}
}
@end // We can conveniently deallocate both the vm_allocated memory and
// the vm_mapped region at the same time.
error = vm_deallocate(mach_task_self(), (vm_address_t)buffer, bufferLength * 2);
if (error) {
#if DEBUG
mach_error("vm_deallocate in dealloc", error);
#endif
}
}