You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 10:56:06