@@ -167,6 +167,10 @@ uint32_t metalGPUCoreCount()
167167 mInternals ->streamOrderValue [i] = 0 ;
168168 mInternals ->streamFence [i] = [mInternals->device newFence ];
169169 mInternals ->streamHasWork [i] = false ;
170+ mInternals ->timelineKernels [i] = 0 ;
171+ }
172+ if (const char * timeline = getenv (" GPUCA_METAL_TIMELINE" )) {
173+ mInternals ->timeline = fopen (timeline, " w" );
170174 }
171175
172176 mInternals ->mem_host = metalNewSharedBuffer (mInternals ->device , mHostMemorySize );
@@ -247,6 +251,9 @@ uint32_t metalGPUCoreCount()
247251 }
248252 [mInternals->mem_host release ];
249253 }
254+ if (mInternals ->timeline ) {
255+ fflush (mInternals ->timeline ); // not closed: a completion handler may still write to it
256+ }
250257
251258 [mInternals->library release ];
252259 [mInternals->device release ];
@@ -313,6 +320,7 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const
313320 id <MTLCommandBuffer > cb = mInternals ->commandBuffers [realStream];
314321 id <MTLBlitCommandEncoder > blit = [cb blitCommandEncoder ];
315322 metalBeginEncoder (mInternals , blit, realStream);
323+ metalTimelineAdd (mInternals , realStream, " b" , nullptr , sizeBytes);
316324 [blit copyFromBuffer: s.buffer
317325 sourceOffset: s.offset
318326 toBuffer: d.buffer
@@ -346,6 +354,7 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const
346354 id <MTLCommandBuffer > cb = mInternals ->commandBuffers [realStream];
347355 id <MTLBlitCommandEncoder > blit = [cb blitCommandEncoder ];
348356 metalBeginEncoder (mInternals , blit, realStream);
357+ metalTimelineAdd (mInternals , realStream, " b" , nullptr , size);
349358 [blit copyFromBuffer: s.buffer
350359 sourceOffset: s.offset
351360 toBuffer: mInternals ->mem_constant
@@ -384,6 +393,7 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const
384393 const uint64_t value = EventValue (*ev, true );
385394 [cb encodeSignalEvent: mtlEvent value: value];
386395 metalSignalOnError (cb, mtlEvent, value);
396+ metalTimelineAdd (mInternals , stream, " s" , mtlEvent, value);
387397 CommitStream (stream);
388398}
389399
@@ -396,13 +406,27 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const
396406 const uint64_t value = ++mInternals ->streamOrderValue [stream];
397407 [cb encodeSignalEvent: mInternals ->streamOrder[stream] value: value];
398408 metalSignalOnError (cb, mInternals ->streamOrder [stream], value);
409+ if (mInternals ->timeline ) {
410+ metalTimelineAdd (mInternals , stream, " s" , mInternals ->streamOrder [stream], value);
411+ FILE * f = mInternals ->timeline ;
412+ const std::string ops = std::move (mInternals ->timelineOps [stream]);
413+ const uint32_t nKernels = mInternals ->timelineKernels [stream];
414+ const uint64_t seq = mInternals ->timelineBuffers ++;
415+ const double commitTime = metalHostTime ();
416+ [cb addCompletedHandler: ^(id <MTLCommandBuffer > c) {
417+ fprintf (f, " CB %llu s%d st%d commit %.6f gpu %.6f %.6f nk %u:%s\n " , (unsigned long long )seq, stream, (int )c.status , commitTime, c.GPUStartTime , c.GPUEndTime , nKernels, ops.c_str ());
418+ }];
419+ mInternals ->timelineOps [stream].clear ();
420+ mInternals ->timelineKernels [stream] = 0 ;
421+ }
399422 [cb commit ];
400423 // Hand the committed buffer over to the stream's submission list, which owns
401424 // it until it is waited for, and open a fresh one to encode the next work in.
402425 mInternals ->submittedBuffers [stream].emplace_back (cb);
403426 mInternals ->streamHasWork [stream] = false ;
404427 mInternals ->commandBuffers [stream] = [[mInternals->commandQueues[stream] commandBuffer ] retain ];
405428 [mInternals->commandBuffers[stream] encodeWaitForEvent: mInternals ->streamOrder[stream] value: value];
429+ metalTimelineAdd (mInternals , stream, " w" , mInternals ->streamOrder [stream], value);
406430}
407431
408432void GPUReconstructionMetal::DrainStream (int32_t stream)
@@ -421,6 +445,7 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const
421445 // waitUntilCompleted, which sleeps until the completion has been processed.
422446 // Only short waits are worth a busy core, so give up after a while.
423447 bool done = false ;
448+ const double drainStart = mInternals ->timeline && !mInternals ->submittedBuffers [stream].empty () ? metalHostTime () : 0 .;
424449 const auto t0 = std::chrono::steady_clock::now ();
425450 while (!mInternals ->submittedBuffers [stream].empty () && !(done = mInternals ->streamOrder [stream].signaledValue >= mInternals ->streamOrderValue [stream]) && std::chrono::steady_clock::now () - t0 < std::chrono::milliseconds (20 )) {
426451 }
@@ -436,6 +461,9 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const
436461 [cb release ];
437462 }
438463 mInternals ->submittedBuffers [stream].clear ();
464+ if (drainStart) {
465+ fprintf (mInternals ->timeline , " DR s%d %.6f %.6f\n " , stream, drainStart, metalHostTime ());
466+ }
439467 // The work of a failed buffer is lost, so the results are wrong: fail like the other backends.
440468 if (error && !mInternals ->exiting ) {
441469 GPUChkErr (error);
@@ -469,6 +497,7 @@ MetalMemRef metalResolve(const GPUReconstructionMetalInternals* internals, const
469497 auto mtlEvent = (__bridge id <MTLSharedEvent >)(evList[ei].get <void *>());
470498 if (const uint64_t value = EventValue (evList[ei], false )) {
471499 [cb encodeWaitForEvent: mtlEvent value: value];
500+ metalTimelineAdd (mInternals , stream, " w" , mtlEvent, value);
472501 }
473502 }
474503 CommitStream (stream);
0 commit comments