MTLBuffer的deallocator块未调用致mmap内存泄漏,如何解决?
问题:MTLBuffer的deallocator未触发导致mmap内存泄漏
我在main函数中编写了如下代码(已确保CONTENT_SIZE为页对齐):
if (!(Queue = [Device = MTLCreateSystemDefaultDevice() newCommandQueue])) // Set global variables return EXIT_FAILURE; { const id<MTLBuffer> data = [Device newBufferWithBytesNoCopy:({ void *const map = ({ NSFileHandle *const file = [NSFileHandle fileHandleForReadingAtPath:[NSBundle.mainBundle pathForResource:@"Content" ofType:nil]]; if (!file) return EXIT_FAILURE; mmap(NULL, CONTENT_SIZE, PROT_READ, MAP_SHARED, file.fileDescriptor, 0); }); if (map == MAP_FAILED) return EXIT_FAILURE; map; }) length:CONTENT_SIZE options:MTLResourceStorageModeShared deallocator:^(void *const ptr, const NSUInteger len){ munmap(ptr, len); // Never called }]; if (!data) return EXIT_FAILURE; const id<MTLCommandBuffer> buffer = [Queue commandBuffer]; const id<MTLBlitCommandEncoder> encoder = [buffer blitCommandEncoder]; if (!encoder) return EXIT_FAILURE; // Below, the contents of buffer are copied to MTLTexture MTLTextureDescriptor *const descriptor = [MTLTextureDescriptor new]; descriptor.width = descriptor.height = 32; descriptor.mipmapLevelCount = 6; descriptor.textureType = MTLTextureType2DArray; descriptor.storageMode = MTLStorageModePrivate; const enum MTLPixelFormat format[] = {MTLPixelFormatR8Unorm, MTLPixelFormatRG8Unorm, MTLPixelFormatRGBA8Unorm}; const NSUInteger len[] = {TEX_LEN_1, TEX_LEN_2, TEX_LEN_4}; for (NSUInteger i = 3, off = 0; i--;) { descriptor.pixelFormat = format[i]; const NSUInteger l = descriptor.arrayLength = len[i]; const id<MTLTexture> texture = [Device newTextureWithDescriptor:descriptor]; if (!texture) return EXIT_FAILURE; const NSUInteger br = 32<<i, bi = 1024<<i; for (NSUInteger j = 0; j < l; off += bi) [encoder copyFromBuffer:data sourceOffset:off sourceBytesPerRow:br sourceBytesPerImage:bi sourceSize:(const MTLSize){32, 32, 1} toTexture:texture destinationSlice:j++ destinationLevel:0 destinationOrigin:(const MTLOrigin){0}]; [encoder generateMipmapsForTexture:BlockTexture[i] = texture]; // BlockTexture is a global variable } [encoder endEncoding]; [buffer commit]; }
发现传入deallocator参数的block从未被调用,即便buffer的引用已超出作用域、数据已复制到纹理不再需要,mmap分配的内存也未释放,存在内存泄漏。请问如何确保mmap内存能在buffer无需时被正确执行munmap释放?
解决方案
核心原因
Metal的命令提交是异步执行的:调用[buffer commit]只是把命令提交到GPU队列,后台GPU还在处理拷贝和mipmap生成操作,此时data这个MTLBuffer仍被GPU命令持有引用,不会因为超出代码作用域就立即释放,自然也不会触发deallocator。
具体解决方法
方法一:同步等待命令执行完成
在提交命令后,添加等待代码确保GPU完成所有操作,此时data的引用才会被释放:
[encoder endEncoding]; [buffer commit]; [buffer waitUntilCompleted]; // 阻塞当前线程,直到GPU完成所有命令
当waitUntilCompleted返回时,GPU已经不再需要访问data的内容,data的引用计数会降至0,触发deallocator执行munmap。
方法二:异步回调处理(非阻塞)
如果不想阻塞主线程,可以给命令缓冲区添加完成回调,在GPU操作结束后自动触发内存清理逻辑:
__weak typeof(data) weakData = data; [buffer addCompletedHandler:^(id<MTLCommandBuffer> completedBuffer) { // 回调触发时,GPU已完成操作,weakData会被自动释放,进而触发deallocator }]; [encoder endEncoding]; [buffer commit];
注意这里用__weak引用data,避免循环引用导致内存无法释放。
额外优化点
代码中NSFileHandle未显式关闭,虽然block结束后会自动释放,但建议显式关闭文件描述符,避免潜在泄漏:
NSFileHandle *const file = [NSFileHandle fileHandleForReadingAtPath:[NSBundle.mainBundle pathForResource:@"Content" ofType:nil]]; if (!file) return EXIT_FAILURE; void *map = mmap(NULL, CONTENT_SIZE, PROT_READ, MAP_SHARED, file.fileDescriptor, 0); [file closeFile]; // 显式关闭文件,不影响已mmap的内存访问
内容的提问来源于stack exchange,提问作者CPlus
相关产品推荐
相关产品推荐

