目录
- 核心概念
- executor.h
- Executor
- NewLocalExecutor
- ExecutorBarrier
- executor.cc
- structs
- GraphView
- ExecutorImpl
- ExecutorState
- details
3.4 ExecutorState
在执行器的执行图计算的时候,需要一个结构来保存当前计算的即时信息,TF为此设计了类ExecutorState,它被用来保存每一个对ExecutorImpl::Run调用的状态信息。它会在一个节点已经准备好之后调度这个节点,并且保存每个节点尚未完成的输入信息。
下面让我们先来看一下这个类的结构:
class ExecutorState { public: ExecutorState(const Executor::Args& args, ExecutorImpl* impl); void RunAsync(Executor::DoneCallback done); private: DeviceContextMap device_context_map_; typedef gtl::InlinedVector<TaggedNode, 8> TaggedNodeSeq; typedef gtl::InlinedVector<Entry, 4> EntryVector; const bool vlog_; const bool log_memory_; int64 step_id_; //未拥有 Rendezvous* rendezvous; SessionState* session_state_; TensorStore* tensor_store_; //每个执行步级别的容器 ScopedStepContainer* step_container_; StepStatesCollector* stats_collector_; checkpoint::TensorSliceReaderCacheWrapper* slice_reader_cache_; FunctionCallFrame* call_frame; const ExecutorImpl* impl_; CancellationManager* cancellation_manager_; Executor::Args::Runner runner_; bool sync_on_finish_; //拥有 bool dumped_on_error_ = false; //当前执行步骤开始的帧 FrameState* root_frame_; //执行器结束时需要调用的回调函数 Executor::DoneCallback done_cb_; std::atomic_int_fast32_t num_outstanding_ops_; mutex mu_; Status status_ GUARDED_BY(mu_); //从帧名称到实际帧的映射。在当前帧的某个迭代周期内,可能会产生一个新的帧。新的子帧的唯一键值必须由父帧的名称、迭代编号、以及由nodedef推断出来的新帧的名称组合而成 gtl::FlatMap<string, FrameState*> outstanding_frames_ GUARDED_BY(mu_); //一个帧的名称 inline string MakeFrameName(FrameState* frame, int64 iter_id, const string& name); //找到一个现存的帧,或者创建一个新帧,在帧frame的iter迭代周期 void FindOrCreateChildFrame(FrameState* frame, int64 iter, const Node* node, FrameState** child); //删除一个帧,当帧调用结束时使用 void DeleteFrame(FrameState* frame, TaggedNodeSeq* ready); //清除那些起源于帧frame和迭代iter的帧,当一个子帧结束时调用 void CleanupFramesIterations(FrameState* frame, int64 iter, TaggedNodeSeq* ready); //在当前的线程中处理一个已准备好的节点 void Process(TaggedNode node, int64 scheduled_usec); //在调用item->kernel之前,先填入其输入 Status PrepareInputs(const NodeItem& item, Entry* first_input, TensorValueVec* inputs, DeviceContextVec* input_device_contexts, AllocatorAttributeVec* input_alloc_attrs, bool* is_input_dead); //在item->kernel计算结束之后,处理输出 Status ProcessOutputs(const NodeItem& item, OpKernelContext* ctx, EntryVector* outputs, NodeExecStats* stats); //在处理完输出之后,将输入传递给下一个输入 void PropagateOutputs(const TaggedNode& tagged_node, const NodeItem* item, EntryVector* outputs, TaggedNodeSeq* ready); //节点计算结束后,接管stats,如果执行完成则返回true bool NodeDone(const Status& s, const Node* node, const TaggedNodeSeq& ready, NodeExecStats* stats, TaggedNodeReadyQueue* inline_ready); //调度ready中的所有复杂节点,然后将ready中的非复杂节点放入inline_ready void ScheduleReady(const TaggedNodeSeq& ready, TaggedNodeReadyQueue* inline_ready); //仅用作调试或记录 inline void MaybeMarkCompleted(FrameState* frame, int64 iter, int64 id); //输出一个未完成或者活跃节点的信息 void DumpPendingNodeState(const int node_id, const Entry* input_vector, bool show_nodes_with_no_ready_inputs); void DumpActiveNodeState(const FrameState* frame, IterationState* iteration); //提供执行器的状态信息 void DumpState(); const Tensor* GetTensorValueForDump(const Entry& input); //当执行器结束执行时,清理 void Finish(); };从API上来看,ExecutorState几乎担当了执行器的职责,从后面的介绍也可以看出,实际上确实如此。执行器内部实际调用的就是ExecutorState内部的API。从类的结构中,我们还是看到了许多未曾相识的结构,下面我们先一一分析这些类的意义和结构。
首先来看Entry,Entry要么是一个张量指针,要么是一个张量值,为计算图中的节点的输入或输出提供了一种统一的类型。
struct Entry { Entry(
