Loading...
Searching...
No Matches
pullBasedExecutorEngine.h
Go to the documentation of this file.
1//
2// Copyright 2025 Pixar
3//
4// Licensed under the terms set forth in the LICENSE.txt file available at
5// https://openusd.org/license.
6//
7#ifndef PXR_EXEC_VDF_PULL_BASED_EXECUTOR_ENGINE_H
8#define PXR_EXEC_VDF_PULL_BASED_EXECUTOR_ENGINE_H
9
11
12#include "pxr/pxr.h"
13
21#include "pxr/exec/vdf/mask.h"
23#include "pxr/exec/vdf/node.h"
24#include "pxr/exec/vdf/output.h"
29#include "pxr/exec/vdf/vector.h"
30
31#include "pxr/base/tf/bits.h"
34
35PXR_NAMESPACE_OPEN_SCOPE
36
37// The TRACE_SCOPE (and FUNCTION) invocations in this file can be pretty
38// expensive. We turn off most of them. However, they are still useful to
39// track down performance issues, which is why we have a quick way of enabling
40// them, by setting _VDF_PBEE_PROFILING_ON to 1.
41#define _VDF_PBEE_PROFILING_ON 0
42#if _VDF_PBEE_PROFILING_ON
43#define VDF_PBEE_TRACE_FUNCTION TRACE_FUNCTION
44#define VDF_PBEE_TRACE_SCOPE TRACE_SCOPE
45#else
46#define VDF_PBEE_TRACE_FUNCTION()
47#define VDF_PBEE_TRACE_SCOPE(name)
48#endif
49
52
53// Forward declare the speculation executor engine with equivalent traits to
54// this executor engine.
55template <typename> class VdfSpeculationExecutorEngine;
56
65
66template <typename DataManagerType>
68{
69public:
70
75 typedef
78
82 const VdfExecutorInterface &executor,
83 DataManagerType *dataManager) :
84 _executor(executor),
85 _dataManager(dataManager)
86 {}
87
92 const VdfSchedule &schedule,
93 const VdfRequest &computeRequest,
94 VdfExecutorErrorLogger *errorLogger) {
96 schedule, computeRequest, errorLogger,
97 [](const VdfMaskedOutput &, size_t){});
98 }
99
105 template < typename F >
106 void RunSchedule(
107 const VdfSchedule &schedule,
108 const VdfRequest &computeRequest,
109 VdfExecutorErrorLogger *errorLogger,
110 F &&callback);
111
112protected:
113
116 typedef typename DataManagerType::DataHandle _DataHandle;
117
121
122 ExecutionStageStart, // Nodes start in this stage.
123
124 ExecutionStagePreRequisitesDone, // After prerequisites have been
125 // computed but before rest of
126 // inputs have been computed.
127
128 ExecutionStageReadsDone, // After the reads have finished --
129 // only needed for speculation engine.
130
131 ExecutionStageCompute, // Final stage before node computation.
132
133 };
134
135
140 void _ComputeNode(
141 const VdfEvaluationState &state,
142 const VdfNode &node,
143 bool absorbLockedCache = false);
144
157 bool _PassThroughNode(
158 const VdfSchedule &schedule,
159 const VdfNode &node,
160 bool absorbLockedCache = false);
161
162
167 const VdfOutput &toOutput,
168 VdfExecutorBufferData *toBuffer,
169 const VdfOutput &fromOutput,
170 const VdfMask &fromMask) const;
171
174 const VdfExecutorInterface &_GetExecutor() { return _executor; }
175
178 DataManagerType *_GetDataManager() { return _dataManager; }
179
186 const _DataHandle dataHandle,
187 const VdfOutput &output,
188 const VdfOutput &source,
189 const VdfMask &inputMask,
190 const VdfSchedule &schedule);
191
192
198 const _DataHandle dataHandle,
199 const VdfOutput &output,
200 const VdfOutput &source,
201 const VdfMask &inputMask,
202 const VdfSchedule &schedule) const;
203
210 const _DataHandle dataHandle,
211 const VdfInput &input,
212 const VdfMask &mask,
213 const VdfSchedule &schedule);
214
219 inline static bool _IsNotPassing(
220 const VdfOutput& output,
221 const VdfSchedule::OutputId& outputId,
222 const VdfSchedule& schedule)
223 {
224 return output.GetAssociatedInput() &&
225 !schedule.GetPassToOutput(outputId);
226 }
227
228private:
229
230 // This struct contains the necessary state to compute an output.
231 //
232 struct _OutputToExecute {
233
234 // Constructor that takes schedule node and output.
235 _OutputToExecute(
236 const VdfSchedule::OutputId &outputId,
237 const VdfMask &lockedCacheMask,
238 bool affective) :
239 outputId(outputId),
240 stage(ExecutionStageStart),
241 lockedCacheMask(lockedCacheMask),
242 affective(affective),
243 absorbLockedCache(false)
244 { }
245
246 // The schedule identifier for the output to execute.
247 VdfSchedule::OutputId outputId;
248
249 // The current phase of this output in the execution stack.
250 _ExecutionStage stage;
251
252 // Current state of the locked cache
253 VdfMask lockedCacheMask;
254
255 // Determines the affective-ness of the output
256 bool affective;
257
258 // Absorb the locked cache for SMBL
259 bool absorbLockedCache;
260
261 };
262
263 // Helper method to _ExecuteOutput.
264 //
265 // This method adds \p output to the \p outputs vector.
266 // Returns \c true if it added a new output and \c false otherwise.
267 //
268 bool _PushBackOutput(
269 std::vector< _OutputToExecute > *outputs,
270 const VdfMask &lockedCacheMask,
271 const VdfOutput &output,
272 const VdfSchedule &schedule);
273
274 // Executes the given \p output.
275 //
276 void _ExecuteOutput(
277 const VdfEvaluationState &state,
278 const VdfOutput &output,
279 TfBits *executedNodes);
280
281 // Finalize the output buffer after computing or passing through. This
282 // sets the computed output mask as well as merges in any data that has
283 // been temporarily held on to.
284 //
285 void _FinalizeComputedOutput(
286 const _DataHandle dataHandle,
287 const VdfMask &requestMask,
288 const bool hasBeenInterrupted,
289 const bool extendRequestMask);
290
291 // Update the output stack entry for SMBL. This refreshes the affectiveness
292 // flag, the lockedCacheMask and the flag that determines whether the
293 // locked cache needs to be absorbed into the executor cache. This method
294 // returns true if any of the relevant flags on the stackEntry object have
295 // been modified.
296 //
297 bool _UpdateOutputForSMBL(
298 const VdfOutput &output,
299 _OutputToExecute *stackEntry,
300 const VdfSchedule &schedule);
301
302 // The executor that uses this engine.
303 //
304 const VdfExecutorInterface &_executor;
305
306 // The data manager for this engine.
307 //
308 DataManagerType *_dataManager;
309
310 // Acceleration structure used for caching output handles, which may be
311 // repeatedly looked up in the same order.
312 //
313 std::vector<_DataHandle> _dataHandleCache;
314
315};
316
318
319template<typename DataManagerType>
320template<typename F>
321void
323 const VdfSchedule &schedule,
324 const VdfRequest &computeRequest,
325 VdfExecutorErrorLogger *errorLogger,
326 F &&callback)
327{
328 TRACE_FUNCTION();
329 TfAutoMallocTag tag("VdfPullBasedExecutorEngine::RunSchedule");
330
331 // Make sure the data manager is appropriately sized.
332 _dataManager->Resize(*schedule.GetNetwork());
333
334 // Indicates which nodes have been executed.
335 TfBits executedNodes(schedule.GetScheduleNodeVector().size());
336
337 // The persistent evaluation state
338 VdfEvaluationState state(_GetExecutor(), schedule, errorLogger);
339
340 // Now execute the uncached, requested outputs.
341 VdfRequest::IndexedView requestView(computeRequest);
342 for (size_t i = 0; i < requestView.GetSize(); ++i) {
343 // Skip outputs not included in the request.
344 const VdfMaskedOutput *maskedOutput = requestView.Get(i);
345 if (!maskedOutput) {
346 continue;
347 }
348
349 // Skip outputs that have already been cached. However, we must invoke
350 // the callback to notify the client side that evaluation of the
351 // requested output has completed.
352 const VdfOutput &output = *maskedOutput->GetOutput();
353 const VdfMask &mask = maskedOutput->GetMask();
354 if (_GetExecutor().GetOutputValue(output, mask)) {
355 callback(*maskedOutput, i);
356 continue;
357 }
358
359 VDF_PBEE_TRACE_SCOPE(
360 "VdfPullBasedExecutorEngine<T>::RunSchedule (executing output)");
361 _ExecuteOutput(state, output, &executedNodes);
362
363 // If we've been interrupted, bail out.
364 if (_GetExecutor().HasBeenInterrupted()) {
365 break;
366 }
367
368 // Invoke the callback once the output has been evaluated, but only
369 // if the executor has not been interrupted.
370 else {
371 callback(*maskedOutput, i);
372 }
373 }
374}
375
376template<typename DataManagerType>
377VdfVector *
379 const VdfOutput &toOutput,
380 VdfExecutorBufferData *toBuffer,
381 const VdfOutput &fromOutput,
382 const VdfMask &fromMask) const
383{
384 TfAutoMallocTag2 tag("Vdf", "VdfPullBasedExecutorEngine<T>::_CopyCache");
385
386 // Note that we must look up the data through the executor, instead of the
387 // data manager, because we may have initially received a cache hit by
388 // looking up the executor. The data may live in the parent executor, for
389 // example, instead of the local data manager.
390 const VdfVector *sourceVector =
391 _executor.GetOutputValue(fromOutput, fromMask);
392
393 if (!sourceVector) {
394 // CODE_COVERAGE_OFF - We should never hit this
395 VDF_FATAL_ERROR(fromOutput.GetNode(),
396 "No cache for output " + fromOutput.GetDebugName());
397 // CODE_COVERAGE_ON
398 }
399
400 VdfVector *result = _dataManager->CreateOutputCache(toOutput, toBuffer);
401 result->Copy(*sourceVector, fromMask);
402
403 if (VdfExecutionStats* stats = _executor.GetExecutionStats()) {
404 stats->LogData(
405 VdfExecutionStats::ElementsCopiedEvent,
406 toOutput.GetNode(),
407 fromMask.GetNumSet());
408 }
409
410 return result;
411}
412
413
414template<typename DataManagerType>
415VdfVector *
417 const _DataHandle dataHandle,
418 const VdfOutput &output,
419 const VdfOutput &source,
420 const VdfMask &inputMask,
421 const VdfSchedule &schedule)
422{
423 VDF_PBEE_TRACE_FUNCTION();
424
425 // The following block of code makes sure that we touch all the outputs
426 // between the source output and the output that was passed the buffer.
427 //
428 // XXX: This loop scales with the number of nodes between the two outputs
429 // and can get quite expensive. It's also very cache unfriendly. It
430 // would be great if we could get away without every touching these
431 // outputs.
432 //
433 const VdfOutput *betweenOutput = VdfGetAssociatedSourceOutput(output);
434 while (betweenOutput && betweenOutput != &source) {
435 _GetExecutor()._TouchOutput(*betweenOutput);
436 betweenOutput = VdfGetAssociatedSourceOutput(*betweenOutput);
437 }
438
439 return _PassOrCopyBufferInternal(
440 dataHandle, output, source, inputMask, schedule);
441}
442
443template<typename DataManagerType>
444VdfVector *
446 const _DataHandle dataHandle,
447 const VdfOutput &output,
448 const VdfOutput &source,
449 const VdfMask &inputMask,
450 const VdfSchedule &schedule) const
451{
452 // Here's where we have the most potential for optimization. We
453 // can re-use our inputs cache (without any copying) if our input
454 // has one and only one output (and that's us)
455 //
456 const _DataHandle sourceHandle =
457 _dataManager->GetDataHandle(source.GetId());
458 VdfSchedule::OutputId sourceId = schedule.GetOutputId(source);
459
460 VdfVector *result = NULL;
461
462 // If this is the output that 'source' is supposed to pass its buffer
463 // to, do so, otherwise copy.
464 if (_dataManager->IsValidDataHandle(sourceHandle) &&
465 &output == schedule.GetPassToOutput(sourceId)) {
466
467 // Retrieve the buffer data from the source data handle.
468 VdfExecutorBufferData *sourceBuffer =
469 _dataManager->GetBufferData(sourceHandle);
470
471 // If the source output does not contain any data, don't even
472 // bother with mung buffer locking or buffer passing.
473 if (sourceBuffer->GetExecutorCache() &&
474 sourceBuffer->GetExecutorCacheMask().IsAnySet()) {
475
476 // Decide whether mung buffer locking should be in effect.
477 // We identify this source output as a likely candidate for buffer
478 // locking (keeping its buffer around) if we observe that the
479 // current output has been recently invalidated while the source
480 // output has not. We optimistically "lock" the buffer by copying
481 // it instead of passing it, so that during the rest of the current
482 // mung (if any), the source buffer will still have its buffer
483 // intact, and we won't have to visit any of its upstream nodes.
484 if (_dataManager->HasInvalidationTimestampMismatch(
485 sourceHandle, dataHandle)) {
486 TF_DEBUG(VDF_MUNG_BUFFER_LOCKING)
487 .Msg("Mung buffer locking between outputs "
488 "'%s' and '%s'.\n",
489 source.GetDebugName().c_str(),
490 output.GetDebugName().c_str());
491 }
492
493 // If mung buffers are not supposed to be locked, pass the buffer
494 // data from the source output to the destination output.
495 else {
496
497 // If the source output does not contain all the data that has
498 // been requested in the inputMask, we cannot pass buffers.
499 // Note, that the requested data being available also implies
500 // that the source output contains the data marked to keep,
501 // since the keep mask is always a subset of the request mask.
502 // This is verified at scheduling time.
503 //
504 // We end up in this particular situation if the execution
505 // engine has found the data living on a parent executor,
506 // i.e. it must be copied before it can be passed to
507 // subsequent outputs.
508 //
509 if (sourceBuffer->GetExecutorCacheMask().Contains(inputMask)) {
510 const VdfMask &keepMask = schedule.GetKeepMask(sourceId);
511 result = _dataManager->PassBuffer(
512 source, sourceBuffer,
513 output, _dataManager->GetBufferData(dataHandle),
514 keepMask);
515
516 if (VdfExecutionStats* stats =
517 _executor.GetExecutionStats()) {
518 stats->LogData(
519 VdfExecutionStats::ElementsCopiedEvent,
520 source.GetNode(),
521 keepMask.GetNumSet());
522 }
523 }
524
525 // Note that result can be NULL and we can still end up in
526 // _CopyCache. This can happen when something cached in the
527 // parent executor is read by a speculating executor.
528 }
529 }
530 }
531
532 if (!result) {
533 VDF_PBEE_TRACE_SCOPE(
534 "VdfPullBasedExecutorEngine<T>::_PassOrCopyBufferInternal "
535 "(copying vector)");
536 result = _CopyCache(
537 output, _dataManager->GetBufferData(dataHandle), source, inputMask);
538 }
539
540 return result;
541}
542
543template<typename DataManagerType>
544void
546 const _DataHandle dataHandle,
547 const VdfInput &input,
548 const VdfMask &mask,
549 const VdfSchedule &schedule)
550{
551 // Get the output associated with the read/write input.
552 const VdfOutput *output = input.GetAssociatedOutput();
553 TF_DEV_AXIOM(output);
554
555 // Here's where we have the most potential for optimization. We
556 // can re-use our inputs cache (without any copying) if our input
557 // has one and only one output (and that's us)
558 const size_t numInputNodes = input.GetNumConnections();
559 if (numInputNodes == 1 && !input[0].GetMask().IsAllZeros()) {
560 _PassOrCopyBufferInternal(
561 dataHandle, *output, input[0].GetSourceOutput(), mask, schedule);
562 return;
563 }
564
565 // If we have no inputs, provide a fresh new cache.
566 _dataManager->CreateOutputCache(
567 *output, _dataManager->GetBufferData(dataHandle));
568}
569
570template<typename DataManagerType>
571bool
573 std::vector< _OutputToExecute > *outputs,
574 const VdfMask& lockedCacheMask,
575 const VdfOutput &output,
576 const VdfSchedule &schedule)
577{
578 VdfSchedule::OutputId outputId = schedule.GetOutputId(output);
579
580 if (outputId.IsValid()) {
581 // Push the output
582 outputs->push_back(
583 _OutputToExecute(
584 outputId,
585 lockedCacheMask,
586 schedule.IsAffective(outputId)));
587 return true;
588 }
589
590 // The output to push is not actually scheduled, which guarantees
591 // that is value will never be needed by any computations. So
592 // just skip it.
593 return false;
594}
595
596template<typename DataManagerType>
597bool
599 const VdfOutput &output,
600 _OutputToExecute *stackEntry,
601 const VdfSchedule &schedule)
602{
603 VDF_PBEE_TRACE_FUNCTION();
604
605 // Retrieve the output data handle.
606 const _DataHandle dataHandle = _dataManager->GetDataHandle(output.GetId());
607 if (!_dataManager->IsValidDataHandle(dataHandle)) {
608 return false;
609 }
610
611 // Get the invalidation timestamp at the output.
612 const VdfInvalidationTimestamp invalidationTs =
613 _dataManager->GetInvalidationTimestamp(dataHandle);
614
615 // If this output has never been invalidated, bail out.
616 if (!invalidationTs) {
617 return false;
618 }
619
620 // If this output was not invalidated during the last invalidation round,
621 // do not consider it for sparse mung buffer locking. The first output that
622 // is no longer part of the last invalidation round will hold the fully
623 // locked mung buffer.
624 // Note, we also have to reset the locked cache mask when crossing the
625 // timestamp edge. If we ever reach back into a pool chain that has the
626 // current invalidation timestamp, we have to start back up with an empty
627 // locked cache mask.
628 if (invalidationTs != _dataManager->GetInvalidationTimestamp()) {
629 if (!stackEntry->lockedCacheMask.IsEmpty()) {
630 stackEntry->lockedCacheMask = VdfMask();
631 return true;
632 }
633 return false;
634 }
635
636 // Output updated?
637 bool updated = false;
638
639 // Append the data sitting at this output to the locked cache mask. This
640 // section of the code is responsible for growing the lockedCacheMask as
641 // we traverse up the pool chain.
642 VdfExecutorBufferData *outputBuffer =
643 _dataManager->GetBufferData(dataHandle);
644 VdfSMBLData *smblData = _dataManager->GetOrCreateSMBLData(dataHandle);
645 const VdfSchedule::OutputId &outputId = stackEntry->outputId;
646 const VdfMask &keepMask = schedule.GetKeepMask(outputId);
647 if (outputBuffer->GetExecutorCache() &&
648 !outputBuffer->GetExecutorCacheMask().IsEmpty() &&
649 !keepMask.IsEmpty()) {
650 smblData->ExtendLockedCacheMask(
651 &stackEntry->lockedCacheMask,
652 outputBuffer->GetExecutorCacheMask());
653 stackEntry->absorbLockedCache = true;
654 updated = true;
655 }
656
657 // If the locked cache mask is still empty, than there is no work to do.
658 if (stackEntry->lockedCacheMask.IsEmpty()) {
659 return false;
660 }
661
662 // Before determining the affective-ness of the node, insure that the data
663 // indicated by the keep mask is stored in the executor cache, and that
664 // any bits not contained in the executor cache are not contained in the
665 // locked cache mask. Otherwise, we could be skipping nodes which really
666 // need to run in order to provide valid values to be kept.
667 if (!keepMask.IsEmpty()) {
668 smblData->RemoveUncachedMask(
669 &stackEntry->lockedCacheMask,
670 outputBuffer->GetExecutorCacheMask(),
671 keepMask);
672 updated = true;
673 }
674
675 // If this node is affective in the schedule, we may be able to get away
676 // without computing it, and making it un-affective. We determine whether
677 // this is the case by looking at the lockedCacheMask to see if it contains
678 // the scheduled affects mask.
679 if (stackEntry->affective &&
680 !smblData->ComputeAffectiveness(
681 stackEntry->lockedCacheMask,
682 schedule.GetAffectsMask(outputId))) {
683 stackEntry->affective = false;
684 return true;
685 }
686
687 // Any updates to the output?
688 return updated;
689}
690
691template<typename DataManagerType>
692void
694 const VdfEvaluationState &state,
695 const VdfOutput &output,
696 TfBits *executedNodes)
697{
698 TF_DEBUG(VDF_PBEE_TRACE).Msg(
699 "----------------- _ExecuteOutput --------- \n");
700
701 // The current schedule
702 const VdfSchedule &schedule = state.GetSchedule();
703
704 // Is Sparse Mung Buffer Locking enabled for this round of evaluation?
705 //
706 // Note that executors that may be interrupted, do not yet support SMBL.
707 // After interruption, a buffer that has not been fully passed down the pool
708 // chain, may contain garbage data. That same buffer may then get picked up
709 // in subsequent evaluation rounds, where it is assumed to be entirely
710 // valid.
711 const bool enableSMBL =
712 schedule.HasSMBL() && !_GetExecutor().GetInterruptionFlag();
713
714 // This is the stack of the outputs currently in the process of execution.
715 std::vector< _OutputToExecute > outputsStack;
716
717 // Add the first output to the stack.
718 _PushBackOutput(&outputsStack, VdfMask(), output, schedule);
719
720 while (!outputsStack.empty()) {
721
722 // If we've been interrupted, bail out.
723 if (_GetExecutor().HasBeenInterrupted()) {
724 break;
725 }
726
727 // Stack Top State
728 VdfSchedule::OutputId outputId = outputsStack.back().outputId;
729 bool affective = outputsStack.back().affective;
730 VdfMask lockedCacheMask = outputsStack.back().lockedCacheMask;
731 bool absorbLockedCache = outputsStack.back().absorbLockedCache;
732
733 // Temporary State
734 const VdfMask *requestMask = NULL;
735 const VdfOutput *output = NULL;
736 const VdfNode &node = *schedule.GetNode(outputId);
737 bool added = false;
738
739 switch (outputsStack.back().stage) {
740
741 case ExecutionStageStart:
742
743 TF_DEBUG(VDF_PBEE_TRACE)
744 .Msg("{ BeginNode(\"%s\");\n", node.GetDebugName().c_str());
745
746 // We have to compute if
747 // o The node has not been executed, yet
748 // o The output is dirty
749 // o The cache is empty
750 // o The computed mask doesn't cover what is asked for in the
751 // schedule.
752 output = schedule.GetOutput(outputId);
753 requestMask = &schedule.GetRequestMask(outputId);
754 if (executedNodes->IsSet(schedule.GetScheduleNodeIndex(outputId)) ||
755 _GetExecutor().GetOutputValue(*output, *requestMask)) {
756
757 // Pop off the top of the output stack
758 outputsStack.pop_back();
759
760 TF_DEBUG(VDF_PBEE_TRACE).Msg(" EndNodeFoundCache(); }\n");
761 continue;
762 }
763
764 // Update the output for SMBL. This refreshes the affective-ness
765 // flag, the lockedCacheMask and the flag that indicates whether
766 // the locked cache should be absorbed into the executor cache.
767 if (enableSMBL && Vdf_IsPoolOutput(*output)) {
768 // Update the top of the output stack. Since no new outputs
769 // have been pushed onto the stack at this point, the top is
770 // still the output we are currently executing.
771 _OutputToExecute *stackTop = &outputsStack.back();
772 if (_UpdateOutputForSMBL(*output, stackTop, schedule)) {
773 affective = stackTop->affective;
774 lockedCacheMask = stackTop->lockedCacheMask;
775 absorbLockedCache = stackTop->absorbLockedCache;
776 }
777 }
778
779 // The first stage of computation is to execute all the
780 // prerequisites for current output. So we push them on our stack
781 // and wait for them to be computed.
782
783 // Mark that we've processed the prerequisites for this output.
784 outputsStack.back().stage = ExecutionStagePreRequisitesDone;
785
786 // Push back all the prerequisites if this output will do anything
787 if (affective) {
788 for (const VdfScheduleInput &input : schedule.GetInputs(node)) {
789 if (input.input->GetSpec().IsPrerequisite()) {
790 added |= _PushBackOutput(
791 &outputsStack, VdfMask(), *input.source, schedule);
792 }
793 }
794 }
795
796 // If we added inputs then we want to go back to the top of the
797 // loop and execute our inputs, otherwise we will fall through to
798 // the next stage.
799 if (added) {
800 break;
801 } // else fall through to the next stage.
802
803 case ExecutionStagePreRequisitesDone:
804
805 // Now that all the prerequisites are done, the second stage
806 // of computation is to use the prerequisites to determine what
807 // other inputs we need to run to satisfy the current output.
808
809 // Mark that all the inputs have now been processed for the
810 // current output.
811 outputsStack.back().stage = ExecutionStageCompute;
812
813 // Note that outputs added are executed in reverse order. So we
814 // push last the nodes that we want to run first.
815
816 // Only run the reads if the output is expected to modify
817 // anything.
818 if (affective) {
819
820 // Get the list of required inputs based on the prerequisite
821 // computations.
822 VdfRequiredInputsPredicate inputsPredicate =
823 node.GetRequiredInputsPredicate(VdfContext(state, node));
824
825 // Run the required reads last.
826 // Here we try to run the "read" inputs after the "read/write"
827 // inputs.
828 if (inputsPredicate.HasRequiredReads()) {
829 for (const VdfScheduleInput &input :
830 schedule.GetInputs(node)) {
831 if (inputsPredicate.IsRequiredRead(*input.input)) {
832 added |= _PushBackOutput(
833 &outputsStack, VdfMask(),
834 *input.source, schedule);
835 }
836 }
837 }
838 }
839
840 // Run the read/writes first, so that we can maximize the chance of
841 // being able to re-use the kept buffers for speculations.
842 for (const VdfScheduleInput &input : schedule.GetInputs(node)) {
843 const VdfOutput *assocOutput =
844 input.input->GetAssociatedOutput();
845 if (!assocOutput) {
846 continue;
847 }
848
849 // Does this output have a pass-through scheduled?
850 const VdfSchedule::OutputId &assocOutputId =
851 schedule.GetOutputId(*assocOutput);
852 if (assocOutputId.IsValid()) {
853 if (const VdfOutput *fromBufferOutput =
854 schedule.GetFromBufferOutput(assocOutputId)) {
855 added |= _PushBackOutput(
856 &outputsStack, lockedCacheMask,
857 *fromBufferOutput, schedule);
858 continue;
859 }
860 }
861
862 // If the associated output is not scheduled, or it does not
863 // have a pass-through scheduled, we need to consider all
864 // connected source outputs!
865 added |= _PushBackOutput(
866 &outputsStack, lockedCacheMask, *input.source, schedule);
867 }
868
869 // If we added inputs then we want to go back to the top of the
870 // loop and execute our inputs, otherwise we will fall through to
871 // the next stage.
872 if (added) {
873 break;
874 } // else fall through to the next stage.
875
876 default:
877
878 // Set a bit indicating that this node has been executed.
879 executedNodes->Set(schedule.GetScheduleNodeIndex(outputId));
880
881 // Compute the node.
882 if (affective) {
883 _ComputeNode(state, node, absorbLockedCache);
884
885 TF_DEBUG(VDF_PBEE_TRACE).Msg(
886 "ComputedNode(\"%s\"); }\n", node.GetDebugName().c_str());
887
888 } else {
889 // The node doesn't have any outputs that need to be computed.
890 // Skip the node passing through the data for read/write
891 // outputs.
892 _PassThroughNode(schedule, node, absorbLockedCache);
893
894 TF_DEBUG(VDF_PBEE_TRACE)
895 .Msg("ComputedNodeInaffective(\"%s\"); }\n",
896 node.GetDebugName().c_str());
897 }
898
899 // Pop the output off the stack, once we are done with it
900 outputsStack.pop_back();
901 }
902 }
903}
904
905template<typename DataManagerType>
906void
908 const VdfEvaluationState &state,
909 const VdfNode &node,
910 bool absorbLockedCache)
911{
912 VDF_PBEE_TRACE_FUNCTION();
913
914 VdfExecutionStats *stats = _executor.GetExecutionStats();
915
917 compute(stats, node, VdfExecutionStats::NodeEvaluateEvent);
918
919 if (stats) {
920 stats->LogTimestamp(VdfExecutionStats::NodeDidComputeEvent, node);
921 }
922
923
924 // The current schedule.
925 const VdfSchedule &schedule = state.GetSchedule();
926
927 // Clear the acceleration structure for output data lookups.
928 _dataHandleCache.clear();
929
930 VDF_FOR_EACH_SCHEDULED_OUTPUT_ID(outputId, schedule, node) {
931 const VdfOutput &output = *schedule.GetOutput(outputId);
932
933 // Retrieve the data handle and cache it for accelerated lookup below.
934 const _DataHandle dataHandle =
935 _dataManager->GetOrCreateDataHandle(output.GetId());
936 _dataHandleCache.push_back(dataHandle);
937
938 // Retrieve the buffer data associated with the handle.
939 VdfExecutorBufferData *bufferData =
940 _dataManager->GetBufferData(dataHandle);
941
942 // If this output still contains data (i.e., invalidation did not
943 // remove the cache), it may have been locked and we may want to retain
944 // the data to absorb it shortly.
945 if (absorbLockedCache ||
946 (bufferData->GetExecutorCache() &&
947 _IsNotPassing(output, outputId, schedule))) {
948 bufferData->RetainExecutorCache(
949 output.GetSpec(),
950 _dataManager->GetOrCreateSMBLData(dataHandle));
951 }
952
953 // Before we compute the output, we have to make sure that all
954 // the recipients of its cache are cleared and that the cache is
955 // reclaimed by output.
956 bufferData->ResetExecutorCache();
957
958 // Mark the output as having been touched during evaluation.
959 _dataManager->Touch(dataHandle);
960
961 // If this is a read/write output, make sure the buffer has been
962 // passed down. We also need to set the computed output mask here,
963 // because the node will read input values of read/write inputs
964 // directly at this output.
965 // Note, that on interruption this mask must be reset!
966 if (const VdfInput *ai = output.GetAssociatedInput()) {
967 const VdfMask &requestMask = schedule.GetRequestMask(outputId);
968 _PrepareReadWriteBuffer(dataHandle, *ai, requestMask, schedule);
969 _dataManager->SetComputedOutputMask(bufferData, requestMask);
970 }
971 }
972
973 // Compute the node
974 {
975 VDF_PBEE_TRACE_SCOPE(
976 "VdfPullBasedExecutorEngine<T>::_ComputeNode "
977 "(node callback)");
978
979 node.Compute(VdfContext(state, node));
980 }
981
982 // Has the node been interrupted during execution?
983 const bool hasBeenInterrupted = _GetExecutor().HasBeenInterrupted();
984
985 // Deallocate temporary buffers which the schedule knows can be deallocated
986 // now that this node has run (they will never be read again before they
987 // are deallocated due to invalidation).
988 if (const VdfOutput* ctd = schedule.GetOutputToClear(node)) {
989 // Fetch the data handle directly from _dataManager, rather than
990 // through a virtual method, because we only ever want to eagerly clear
991 // temporary buffers in our own data manager (never a parent's).
992 const _DataHandle dataHandle =
993 _dataManager->GetDataHandle(ctd->GetId());
994 if (_dataManager->IsValidDataHandle(dataHandle)) {
995 _dataManager->GetBufferData(dataHandle)->Reset();
996 }
997 }
998
999 // We now need to mark the computed parts of our vectors.
1000 size_t outputIndex = 0;
1001 VDF_FOR_EACH_SCHEDULED_OUTPUT_ID(outputId, schedule, node) {
1002 const VdfOutput &output = *schedule.GetOutput(outputId);
1003 const VdfMask &requestMask = schedule.GetRequestMask(outputId);
1004
1005 // Retrieve the data handle from the cache.
1006 const _DataHandle dataHandle = _dataHandleCache[outputIndex++];
1007
1008 // Retrieve the buffer data associated with the handle.
1009 VdfExecutorBufferData *bufferData =
1010 _dataManager->GetBufferData(dataHandle);
1011
1012 // Check to see if the node did indeed produce values for this
1013 // output. We don't want to post warnings for missing output values
1014 // if the node has been interrupted.
1015 if (!hasBeenInterrupted &&
1016 !output.GetAssociatedInput() &&
1017 !bufferData->GetExecutorCache()) {
1018
1019 // This is an output without an associated input that has
1020 // no value even though it was requested. (We know it is
1021 // requested because otherwise, it wouldn't be in the schedule,
1022 // because of VdfScheduler::_RemoveTrivialNodes.)
1023 TF_WARN(
1024 "No value set for output " + output.GetDebugName() +
1025 " of type " + output.GetSpec().GetType().GetTypeName() +
1026 " named " + output.GetName().GetString());
1027
1028 //XXX: This is not 100% right when we use a single data flow
1029 // element to hold multiple values (as we do for shaped
1030 // attributes). FillVector() would need to know that this
1031 // is the case and it would need to know the # of values
1032 // to package into the output. This can happen anywhere
1033 // in the network, but for now, I only added a workaround
1034 // in the EfCopyToPoolNode.
1036 output.GetSpec().GetType(),
1037 requestMask.GetSize(),
1038 _dataManager->GetOrCreateOutputValueForWriting(
1039 output, dataHandle));
1040 }
1041
1042 // If the node has been interrupted, make sure to reset the computed
1043 // output mask: Read/writes will already have their mask set.
1044 _FinalizeComputedOutput(
1045 dataHandle,
1046 requestMask,
1047 hasBeenInterrupted,
1048 _IsNotPassing(output, outputId, schedule));
1049
1050 // Log stats
1051 if (stats) {
1052 const VdfNode& node = output.GetNode();
1053
1054 stats->LogData(
1055 VdfExecutionStats::ElementsProcessedEvent,
1056 node,
1057 schedule.GetAffectsMask(outputId).GetNumSet());
1058 }
1059 }
1060}
1061
1062template<typename DataManagerType>
1063bool
1065 const VdfSchedule &schedule,
1066 const VdfNode &node,
1067 bool absorbLockedCache)
1068{
1069 VDF_PBEE_TRACE_FUNCTION();
1070
1071 bool passedThrough = false;
1072
1073 VdfExecutionStats *stats = _executor.GetExecutionStats();
1075 compute(stats, node, VdfExecutionStats::NodeEvaluateEvent);
1076
1077 VDF_FOR_EACH_SCHEDULED_OUTPUT_ID(outputId, schedule, node) {
1078 const VdfOutput &output = *schedule.GetOutput(outputId);
1079 const VdfMask &requestMask = schedule.GetRequestMask(outputId);
1080
1081 // Retrieve the data handle.
1082 const _DataHandle dataHandle =
1083 _dataManager->GetOrCreateDataHandle(output.GetId());
1084
1085 // Get the buffer data associated with the data handle.
1086 VdfExecutorBufferData *bufferData =
1087 _dataManager->GetBufferData(dataHandle);
1088
1089 // If this output still contains data (i.e., invalidation did not
1090 // remove the cache), it may have been locked and we may want to retain
1091 // the data to absorb it shortly.
1092 if (absorbLockedCache ||
1093 (bufferData->GetExecutorCache()
1094 && _IsNotPassing(output, outputId, schedule))) {
1095 bufferData->RetainExecutorCache(
1096 output.GetSpec(),
1097 _dataManager->GetOrCreateSMBLData(dataHandle));
1098 }
1099
1100 // Before we pass the output data through, we have to make sure that
1101 // all the recipients of its cache are cleared and that the cache is
1102 // reclaimed by output.
1103 bufferData->ResetExecutorCache();
1104
1105 // Marked the output as having been touched during evaluation, in order
1106 // for invalidation to consider this output.
1107 _dataManager->Touch(dataHandle);
1108
1109 if (const VdfOutput *fromBufferOutput =
1110 schedule.GetFromBufferOutput(outputId)) {
1111
1112 _PassOrCopySourceOutputBuffer(
1113 dataHandle, output, *fromBufferOutput, requestMask, schedule);
1114
1115 passedThrough = true;
1116
1117 } else if (const VdfInput *ai = output.GetAssociatedInput()) {
1118
1119 // We better have one and only one connection on this input
1120 // connector. Otherwise we can't pass anything through.
1121 TF_DEV_AXIOM(output.GetAssociatedInput()->GetNumConnections()==1);
1122
1123 // If the output has an associated input, pass the data through.
1124 _PrepareReadWriteBuffer(dataHandle, *ai, requestMask, schedule);
1125 passedThrough = true;
1126 }
1127
1128 // Finalize the computed output, by merging in any temporary data and
1129 // setting the appropriate computed output mask.
1130 _FinalizeComputedOutput(
1131 dataHandle,
1132 requestMask,
1133 false, /* hasBeenInterrupted */
1134 _IsNotPassing(output, outputId, schedule));
1135 }
1136
1137 return passedThrough;
1138}
1139
1140template<typename DataManagerType>
1141void
1143 const _DataHandle dataHandle,
1144 const VdfMask &requestMask,
1145 const bool hasBeenInterrupted,
1146 const bool extendRequestMask)
1147{
1148 // Retrieve the buffer data associated with the data handle.
1149 VdfExecutorBufferData *bufferData = _dataManager->GetBufferData(dataHandle);
1150
1151 // Merge in temporary data, if available. Note, we must release the
1152 // SMBL data despite any possible interruption!
1153 VdfMask lockedMask =
1154 bufferData->ReleaseExecutorCache(_dataManager->GetSMBLData(dataHandle));
1155
1156 // Has the executor been interrupted? Make sure to reset the computed
1157 // output mask, so that subsequent cache hits do not return garbage data.
1158 if (hasBeenInterrupted) {
1159 _dataManager->SetComputedOutputMask(bufferData, VdfMask());
1160 }
1161
1162 // Otherwise, set the computed output mask to the request mask.
1163 else {
1164 // If extendRequestMask is set and the cache's mask is non-empty,
1165 // copy the bits merge the requestMask and the cacheMask. Otherwise,
1166 // set using the standard requestMask.
1167 _dataManager->SetComputedOutputMask(
1168 bufferData,
1169 extendRequestMask && !lockedMask.IsEmpty() ?
1170 lockedMask | requestMask :
1171 requestMask);
1172 }
1173}
1174
1176
1177PXR_NAMESPACE_CLOSE_SCOPE
1178
1179#endif
Fast bit array that keeps track of the number of bits set and can find the next set in a timely manne...
Definition bits.h:49
void Set(size_t index)
Sets bit # index to one.
Definition bits.h:377
bool IsSet(size_t index) const
Returns true, if bit # index is set.
Definition bits.h:412
Scoped (i.e.
Definition mallocTag.h:336
A context is the parameter bundle passed to callbacks of computations.
Definition context.h:40
This object holds state that remains persistent during one round of network evaluation.
const VdfSchedule & GetSchedule() const
The schedule used for evaluation.
Execution stats profiling event logger.
void LogData(EventType event, const VdfNode &node, EventData data)
Log event API.
void LogTimestamp(EventType event, const VdfNode &node)
Log timestamp API.
static VDF_API void FillVector(TfType type, size_t numElements, VdfVector *vector)
Fills vector with the fallback value registered for the given type.
This object is responsible for storing the executor buffer data, comprised of the executor cache vect...
void ResetExecutorCache(const VdfMask &mask)
Reset the executor cache without releasing any memory and set the executor cache mask to mask.
VDF_API void RetainExecutorCache(const VdfOutputSpec &spec, VdfSMBLData *smblData)
Takes the existing executor cache and retains it within the existing VdfSMBLData object.
const VdfMask & GetExecutorCacheMask() const
Get the available mask.
VdfVector * GetExecutorCache() const
Returns the executor cache stored at this buffer data instance.
VDF_API VdfMask ReleaseExecutorCache(VdfSMBLData *smblData)
Merges the executor cache previously retained in smblData into this cache and releases the SMBL data.
A client may instantiate an object of this class and set it in an executor, to collect errors that ma...
Abstract base class for classes that execute a VdfNetwork to compute a requested set of values.
A VdfInput is used to connect a VdfNode to one or more VdfNodes' outputs.
Definition input.h:36
size_t GetNumConnections() const
Returns the number of connections for this input.
Definition input.h:58
const VdfOutput * GetAssociatedOutput() const
Returns the output corresponding to this input.
Definition input.h:82
A VdfMask is placed on connections to specify the data flowing through them.
Definition mask.h:37
size_t GetSize() const
Returns the size of the mask.
Definition mask.h:158
bool IsAnySet() const
Returns true, if there is at least a single set entry.
Definition mask.h:216
bool IsEmpty() const
Returns true if this mask is empty, i.e.
Definition mask.h:168
bool Contains(const VdfMask &mask) const
Returns true if mask is a subset-of or equal to this mask, false otherwise.
Definition mask.h:186
size_t GetNumSet() const
Returns the number of set bits in the mask.
Definition mask.h:246
Class to hold on to an externally owned output and a mask.
VdfOutput * GetOutput() const
Returns the VdfOutput.
const VdfMask & GetMask() const
Returns the VdfMask.
This is the base class for all nodes in a VdfNetwork.
Definition node.h:53
VDF_API const std::string GetDebugName() const
Returns the debug name for this node, if one is registered.
virtual VDF_API VdfRequiredInputsPredicate GetRequiredInputsPredicate(const VdfContext &context) const
Returns a predicate, determining whether a given input and its connections are required in order to f...
virtual void Compute(const VdfContext &context) const =0
This is the method called to perform computation.
A VdfOutput represents an output on a node.
Definition output.h:32
const VdfNode & GetNode() const
Returns the owning node for this output.
Definition output.h:57
VDF_API std::string GetDebugName() const
Returns the debug name for this output.
VdfId GetId() const
The unique id of this output.
Definition output.h:100
This class is a collection of common functions used by pulled based executors.
VdfVector * _PassOrCopySourceOutputBuffer(const _DataHandle dataHandle, const VdfOutput &output, const VdfOutput &source, const VdfMask &inputMask, const VdfSchedule &schedule)
Fast path for when we know ahead of time the output from which we wish to pass the buffer (or copy) a...
VdfPullBasedExecutorEngine(const VdfExecutorInterface &executor, DataManagerType *dataManager)
Constructor.
void _ComputeNode(const VdfEvaluationState &state, const VdfNode &node, bool absorbLockedCache=false)
Computes node.
static bool _IsNotPassing(const VdfOutput &output, const VdfSchedule::OutputId &outputId, const VdfSchedule &schedule)
Returns true if the output is associative but does not pass the buffer to another output.
void _PrepareReadWriteBuffer(const _DataHandle dataHandle, const VdfInput &input, const VdfMask &mask, const VdfSchedule &schedule)
Prepares a buffer for a read/write output.
void RunSchedule(const VdfSchedule &schedule, const VdfRequest &computeRequest, VdfExecutorErrorLogger *errorLogger)
Executes the given schedule with a computeRequest and an optional /p errorLogger.
_ExecutionStage
This enum describes the stages that a node goes through in execution.
DataManagerType * _GetDataManager()
Returns the data manager used by this engine.
VdfVector * _CopyCache(const VdfOutput &toOutput, VdfExecutorBufferData *toBuffer, const VdfOutput &fromOutput, const VdfMask &fromMask) const
Helper method to _PrepareReadWriteBuffer that copies the cache from fromOutput to toOutput.
const VdfExecutorInterface & _GetExecutor()
Returns the executor running this engine.
VdfSpeculationExecutorEngine< DataManagerType > SpeculationExecutorEngine
The equivalent speculation executor engine.
VdfVector * _PassOrCopyBufferInternal(const _DataHandle dataHandle, const VdfOutput &output, const VdfOutput &source, const VdfMask &inputMask, const VdfSchedule &schedule) const
Common method for _PrepareReadWriteBuffer and _PassOrCopySourceOutputBuffer that attempts to pass the...
bool _PassThroughNode(const VdfSchedule &schedule, const VdfNode &node, bool absorbLockedCache=false)
Causes the outputs with associated inputs in node to have their data passed through.
DataManagerType::DataHandle _DataHandle
The data handle type from the data manager implementation.
This predicate determines whether a given input value is needed to fulfill the input dependencies req...
bool IsRequiredRead(const VdfInput &input) const
Is this input a required read? Note that read/writes as well as prerequisite inputs are not required ...
bool HasRequiredReads() const
Are any inputs required?
VdfSMBLData holds per-output data that is meant to be consumed by the executor.
Definition smblData.h:31
void RemoveUncachedMask(VdfMask *lockedCacheMask, const VdfMask &cacheMask, const VdfMask &keepMask)
Make sure that all the bits in the keepMask are provided by the cacheMask.
Definition smblData.h:250
bool ComputeAffectiveness(const VdfMask &lockedCacheMask, const VdfMask &affectsMask)
Computes the affectiveness of the corresponding output given the accumulated lockedCacheMask and the ...
Definition smblData.h:87
void ExtendLockedCacheMask(VdfMask *lockedCacheMask, const VdfMask &cacheMask)
Extends the lockedCacheMask by appending the bits stored in the executor cacheMask.
Definition smblData.h:65
An OutputId is a small key object that, once obtained for a particular VdfOutput, can be used to quer...
Definition schedule.h:92
bool IsValid() const
Returns whether this OutputId can be used to make queries about an output's scheduling.
Definition schedule.h:98
Contains a specification of how to execute a particular VdfNetwork.
Definition schedule.h:42
VDF_API const VdfMask & GetAffectsMask(const OutputId &outputId) const
Returns the affects mask associated with the given OutputId.
const VdfNetwork * GetNetwork() const
Returns the network for this schedule.
Definition schedule.h:179
VDF_API OutputId GetOutputId(const VdfOutput &output) const
Returns a small, cheap OutputId, which can be passed to other Get* methods in this class to efficient...
VDF_API const VdfMask & GetRequestMask(const OutputId &outputId) const
Returns the request mask associated with the given OutputId.
VDF_API const VdfOutput * GetFromBufferOutput(const OutputId &outputId) const
Returns the "from buffer's" output associated with the given OutputId.
VDF_API InputsRange GetInputs(const VdfNode &node) const
Returns a range of inputs scheduled for the given node.
int GetScheduleNodeIndex(const OutputId &outputId) const
Returns the node index of the schedule node associated with the given outputId.
Definition schedule.h:515
VDF_API const VdfMask & GetKeepMask(const OutputId &outputId) const
Returns the keep mask associated with the given OutputId.
VDF_API const VdfOutput * GetPassToOutput(const OutputId &outputId) const
Returns the "pass to" output associated with the given OutputId.
VDF_API const VdfNode * GetNode(const OutputId &outputId) const
Returns the VdfNode that owns the VdfOutput associated with the given outputId.
VDF_API const VdfOutput * GetOutputToClear(const VdfNode &node) const
Returns the output whose temporary buffer can be immediately deallocated after node has finished exec...
ScheduleNodeVector & GetScheduleNodeVector()
Returns the vector of schedule nodes in this schedule.
Definition schedule.h:504
VDF_API bool IsAffective(const OutputId &outputId) const
Returns true if the output is expected to have an effect on its corresponding input,...
VDF_API const VdfOutput * GetOutput(const OutputId &outputId) const
Returns the scheduled VdfOutput associated with the given OutputId.
bool HasSMBL() const
Returns true if this schedule participates in sparse mung buffer locking.
Definition schedule.h:354
This class provides an executor engine to the speculation executor.
This class is used to abstract away knowledge of the cache data used for each node.
Definition vector.h:59
void Copy(const VdfVector &rhs, const VdfMask &mask)
Copies the contents of rhs into this vector.
Definition vector.h:284
unsigned int VdfInvalidationTimestamp
Type of the timestamp that identifies the most recent round of invalidation.
Definition types.h:74
#define TF_DEBUG(enumVal)
Evaluate and print debugging message msg if enumVal is enabled for debugging.
Definition debug.h:501
#define TF_DEV_AXIOM(cond)
The same as TF_AXIOM, but compiled only in dev builds.
Definition diagnostic.h:206
#define TF_WARN(...)
Issue a warning, but continue execution.
Definition diagnostic.h:133
VDF_API const VdfOutput * VdfGetAssociatedSourceOutput(const VdfOutput &output)
Returns the output that is the source of the associated input of output, if any and NULL otherwise.
bool Vdf_IsPoolOutput(const VdfOutput &output)
Returns true if output is a pool output, i.e., an output that has an associated input,...
This class contains scheduling information for an input.
Scoped event that automatically pushes and pops malloc tags for the given VdfNode.