Skip to content

Commit a8a8f73

Browse files
committed
GPU Metal: allocate the memory pools from anonymous memory
The full system test publishes its time frames seconds apart, and after a few idle seconds the first command buffer of a time frame waits until the driver has made the memory pools resident again: in a Metal System Trace its driver processing takes about 165 ms, with "Wire 5.59 GiB System Memory" for the 6 GB device pool. That is the ~160 ms by which a time frame in the full system test was slower than the same time frame in the standalone benchmark, which never idles; with 2 s between the time frames the two take the same time. The driver does this faster for a buffer around anonymous memory than for one Metal allocates: 77 ms instead of 125 ms for 6 GB after 8 s idle on an M1 Max, in a standalone test. So wrap mmap'ed memory with newBufferWithBytesNoCopy for the device and host pools. On an M1 Max, interleaved over 4 rounds, the warm time frames of the TPC-only reconstruction of the 10-collision Pb-Pb full system test take 955 ms instead of 1003 (median of the chain's wall time, 8 s between the time frames), with the same 1,355,432 clusters. The 30-collision Pb-Pb benchmark is unchanged (970 against 967 ms per event over 3 rounds), and its clusters are byte-identical to the CPU's, as on sim5pp.
1 parent cabb816 commit a8a8f73

1 file changed

Lines changed: 28 additions & 2 deletions

File tree

‎GPU/GPUTracking/Base/metal/GPUReconstructionMetal.mm‎

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <mach-o/dyld.h>
2424
#include <mach-o/getsect.h>
2525
#include <mach-o/ldsyms.h> // _mh_execute_header
26+
#include <sys/mman.h>
27+
#include <unistd.h>
2628

2729
#define GPUErrorReturn(...) \
2830
{ \
@@ -50,6 +52,30 @@ uint32_t metalGPUCoreCount()
5052
}
5153
return n;
5254
}
55+
56+
// A shared buffer around anonymous memory rather than one Metal allocates. After
57+
// the GPU has been idle for a few seconds, the first command buffer waits until
58+
// the driver has made the memory pools resident again, which for 6 GB takes about
59+
// 75 ms instead of 125 ms on an M1 Max, and the full system test idles between
60+
// all its time frames.
61+
id<MTLBuffer> metalNewSharedBuffer(id<MTLDevice> device, size_t size)
62+
{
63+
const size_t length = (size + getpagesize() - 1) & ~(size_t)(getpagesize() - 1);
64+
void* p = mmap(nullptr, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
65+
if (p == MAP_FAILED) {
66+
return nil;
67+
}
68+
id<MTLBuffer> buffer = [device newBufferWithBytesNoCopy:p
69+
length:length
70+
options:MTLResourceStorageModeShared | MTLResourceHazardTrackingModeUntracked
71+
deallocator:^(void* ptr, NSUInteger l) {
72+
munmap(ptr, l);
73+
}];
74+
if (buffer == nil) {
75+
munmap(p, length);
76+
}
77+
return buffer;
78+
}
5379
} // namespace
5480

5581
#include "utils/qGetLdBinarySymbols.h"
@@ -116,7 +142,7 @@ uint32_t metalGPUCoreCount()
116142
}
117143

118144
// We only support internal GPUs, so the ownership of the memory is always shared
119-
mInternals->mem_gpu = [mInternals->device newBufferWithLength:mDeviceMemorySize options:MTLResourceStorageModeShared | MTLResourceHazardTrackingModeUntracked];
145+
mInternals->mem_gpu = metalNewSharedBuffer(mInternals->device, mDeviceMemorySize);
120146
if (mInternals->mem_gpu == nil) {
121147
GPUErrorReturn("Metal Memory Allocation Error");
122148
}
@@ -143,7 +169,7 @@ uint32_t metalGPUCoreCount()
143169
mInternals->streamHasWork[i] = false;
144170
}
145171

146-
mInternals->mem_host = [mInternals->device newBufferWithLength:mHostMemorySize options:MTLResourceStorageModeShared | MTLResourceHazardTrackingModeUntracked];
172+
mInternals->mem_host = metalNewSharedBuffer(mInternals->device, mHostMemorySize);
147173
if (mInternals->mem_host == nil) {
148174
GPUErrorReturn("Error allocating pinned host memory");
149175
}

0 commit comments

Comments
 (0)