เครื่องมือสร้างกราฟ C++ เป็นเครื่องมือที่มีประสิทธิภาพสำหรับ
- การสร้างกราฟที่ซับซ้อน
- กราฟที่ใช้พารามิเตอร์แบบพาราเมเตอร์ (เช่น การตั้งค่าผู้รับมอบสิทธิ์ใน
InferenceCalculator
การเปิด/ปิดใช้ส่วนต่างๆ ของกราฟ) - การกรองกราฟที่ซ้ำกันออก (เช่น แทนที่จะเป็นกราฟเฉพาะสำหรับ CPU และ GPU ใน pbtxt คุณสามารถมีโค้ดเดียวที่สร้างกราฟที่จำเป็น เพื่อแบ่งปัน ให้มากที่สุด)
- รองรับอินพุต/เอาต์พุตกราฟที่ไม่บังคับ
- การปรับแต่งกราฟต่อแพลตฟอร์ม
การใช้งานพื้นฐาน
มาดูกันว่าเครื่องมือสร้างกราฟ C++ สามารถใช้สำหรับกราฟง่ายๆ ได้อย่างไร
# Graph inputs.
input_stream: "input_tensors"
input_side_packet: "model"
# Graph outputs.
output_stream: "output_tensors"
node {
calculator: "InferenceCalculator"
input_stream: "TENSORS:input_tensors"
input_side_packet: "MODEL:model"
output_stream: "TENSORS:output_tensors"
options: {
[drishti.InferenceCalculatorOptions.ext] {
# Requesting GPU delegate.
delegate { gpu {} }
}
}
}
ฟังก์ชันในการสร้าง CalculatorGraphConfig
ข้างต้นอาจมีลักษณะดังนี้
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Graph inputs.
Stream<std::vector<Tensor>> input_tensors =
graph.In(0).SetName("input_tensors").Cast<std::vector<Tensor>>();
SidePacket<TfLiteModelPtr> model =
graph.SideIn(0).SetName("model").Cast<TfLiteModelPtr>();
auto& inference_node = graph.AddNode("InferenceCalculator");
auto& inference_opts =
inference_node.GetOptions<InferenceCalculatorOptions>();
// Requesting GPU delegate.
inference_opts.mutable_delegate()->mutable_gpu();
input_tensors.ConnectTo(inference_node.In("TENSORS"));
model.ConnectTo(inference_node.SideIn("MODEL"));
Stream<std::vector<Tensor>> output_tensors =
inference_node.Out("TENSORS").Cast<std::vector<Tensor>>();
// Graph outputs.
output_tensors.SetName("output_tensors").ConnectTo(graph.Out(0));
// Get `CalculatorGraphConfig` to pass it into `CalculatorGraph`
return graph.GetConfig();
}
สรุปสั้นๆ:
- ใช้
Graph::In/SideIn
เพื่อรับอินพุตกราฟเป็นStream/SidePacket
- ใช้
Node::Out/SideOut
เพื่อรับเอาต์พุตของโหนดเป็นStream/SidePacket
- ใช้
Stream/SidePacket::ConnectTo
เพื่อเชื่อมต่อสตรีมและแพ็กเก็ตด้านข้างกับ อินพุตโหนด (Node::In/SideIn
) และเอาต์พุตกราฟ (Graph::Out/SideOut
)- มี "ทางลัด"
>>
ที่คุณสามารถใช้แทนConnectTo
(เช่นx >> node.In("IN")
)
- มี "ทางลัด"
Stream/SidePacket::Cast
ใช้เพื่อแคสต์สตรีมหรือแพ็กเก็ตด้านข้างของAnyType
(เช่นStream<AnyType> in = graph.In(0);
) เป็นประเภทที่เฉพาะเจาะจง- การใช้ประเภทจริงแทน
AnyType
จะทำให้คุณไปบนเส้นทางที่ดีกว่าสำหรับ ปลดปล่อยความสามารถของเครื่องมือสร้างกราฟและปรับปรุงกราฟของคุณ ความอ่านง่าย
- การใช้ประเภทจริงแทน
การใช้งานขั้นสูง
ฟังก์ชันยูทิลิตี
เราจะแยกโค้ดการสร้างการอนุมานเป็นฟังก์ชันยูทิลิตีเฉพาะเพื่อ ความช่วยเหลือเพื่อให้อ่านง่ายและนำโค้ดกลับมาใช้ใหม่ได้
// Updates graph to run inference.
Stream<std::vector<Tensor>> RunInference(
Stream<std::vector<Tensor>> tensors, SidePacket<TfLiteModelPtr> model,
const InferenceCalculatorOptions::Delegate& delegate, Graph& graph) {
auto& inference_node = graph.AddNode("InferenceCalculator");
auto& inference_opts =
inference_node.GetOptions<InferenceCalculatorOptions>();
*inference_opts.mutable_delegate() = delegate;
tensors.ConnectTo(inference_node.In("TENSORS"));
model.ConnectTo(inference_node.SideIn("MODEL"));
return inference_node.Out("TENSORS").Cast<std::vector<Tensor>>();
}
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Graph inputs.
Stream<std::vector<Tensor>> input_tensors =
graph.In(0).SetName("input_tensors").Cast<std::vector<Tensor>>();
SidePacket<TfLiteModelPtr> model =
graph.SideIn(0).SetName("model").Cast<TfLiteModelPtr>();
InferenceCalculatorOptions::Delegate delegate;
delegate.mutable_gpu();
Stream<std::vector<Tensor>> output_tensors =
RunInference(input_tensors, model, delegate, graph);
// Graph outputs.
output_tensors.SetName("output_tensors").ConnectTo(graph.Out(0));
return graph.GetConfig();
}
ด้วยเหตุนี้ RunInference
จึงมีอินเทอร์เฟซที่ชัดเจนซึ่งระบุว่า
อินพุต/เอาต์พุต และประเภทของข้อมูลเหล่านั้น
ทั้งยังนำมาใช้ซ้ำได้ง่ายๆ เช่น เพียงไม่กี่บรรทัดหากคุณต้องการเรียกใช้ส่วนขยาย การอนุมานโมเดล:
// Run first inference.
Stream<std::vector<Tensor>> output_tensors =
RunInference(input_tensors, model, delegate, graph);
// Run second inference on the output of the first one.
Stream<std::vector<Tensor>> extra_output_tensors =
RunInference(output_tensors, extra_model, delegate, graph);
โดยไม่จำเป็นต้องใช้ชื่อและแท็กซ้ำ (InferenceCalculator
TENSORS
, MODEL
) หรือแนะนำค่าคงที่เฉพาะที่นี่
รายละเอียดได้รับการแปลเป็นภาษาRunInference
ชั้นเรียนยูทิลิตี
และแน่นอนว่าไม่ได้มีแค่ฟังก์ชันเท่านั้น แต่ในบางกรณีอาจเป็นประโยชน์ แนะนำคลาสยูทิลิตี้ที่ช่วยทำให้โค้ดการสร้างกราฟของคุณ อ่านได้ง่ายกว่าและมีโอกาสเกิดข้อผิดพลาดน้อยลง
MediaPipe มีเครื่องคำนวณ PassThroughCalculator
ซึ่งทำได้แค่ผ่าน
ผ่านอินพุตต่อไปนี้
input_stream: "float_value"
input_stream: "int_value"
input_stream: "bool_value"
output_stream: "passed_float_value"
output_stream: "passed_int_value"
output_stream: "passed_bool_value"
node {
calculator: "PassThroughCalculator"
input_stream: "float_value"
input_stream: "int_value"
input_stream: "bool_value"
# The order must be the same as for inputs (or you can use explicit indexes)
output_stream: "passed_float_value"
output_stream: "passed_int_value"
output_stream: "passed_bool_value"
}
ลองดูโค้ดการสร้าง C++ ที่ไม่ซับซ้อนเพื่อสร้างกราฟด้านบนกัน
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Graph inputs.
Stream<float> float_value = graph.In(0).SetName("float_value").Cast<float>();
Stream<int> int_value = graph.In(1).SetName("int_value").Cast<int>();
Stream<bool> bool_value = graph.In(2).SetName("bool_value").Cast<bool>();
auto& pass_node = graph.AddNode("PassThroughCalculator");
float_value.ConnectTo(pass_node.In("")[0]);
int_value.ConnectTo(pass_node.In("")[1]);
bool_value.ConnectTo(pass_node.In("")[2]);
Stream<float> passed_float_value = pass_node.Out("")[0].Cast<float>();
Stream<int> passed_int_value = pass_node.Out("")[1].Cast<int>();
Stream<bool> passed_bool_value = pass_node.Out("")[2].Cast<bool>();
// Graph outputs.
passed_float_value.SetName("passed_float_value").ConnectTo(graph.Out(0));
passed_int_value.SetName("passed_int_value").ConnectTo(graph.Out(1));
passed_bool_value.SetName("passed_bool_value").ConnectTo(graph.Out(2));
// Get `CalculatorGraphConfig` to pass it into `CalculatorGraph`
return graph.GetConfig();
}
แม้ว่าการแทนด้วย pbtxt
อาจเกิดข้อผิดพลาดได้ง่าย (เมื่อเรามีข้อมูลที่ต้องส่งจำนวนมาก
ถึง) โค้ด C++ ยังแย่ลงไปอีก: มีการใช้แท็กว่างซ้ำกันและการเรียก Cast
มาเริ่มกันเลย
มาดูกันว่าเราจะปรับปรุงให้ดีขึ้นได้อย่างไรบ้างด้วยการแนะนำ PassThroughNodeBuilder
class PassThroughNodeBuilder {
public:
explicit PassThroughNodeBuilder(Graph& graph)
: node_(graph.AddNode("PassThroughCalculator")) {}
template <typename T>
Stream<T> PassThrough(Stream<T> stream) {
stream.ConnectTo(node_.In(index_));
return node_.Out(index_++).Cast<T>();
}
private:
int index_ = 0;
GenericNode& node_;
};
และตอนนี้โค้ดการสร้างกราฟอาจมีลักษณะดังนี้
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Graph inputs.
Stream<float> float_value = graph.In(0).SetName("float_value").Cast<float>();
Stream<int> int_value = graph.In(1).SetName("int_value").Cast<int>();
Stream<bool> bool_value = graph.In(2).SetName("bool_value").Cast<bool>();
PassThroughNodeBuilder pass_node_builder(graph);
Stream<float> passed_float_value = pass_node_builder.PassThrough(float_value);
Stream<int> passed_int_value = pass_node_builder.PassThrough(int_value);
Stream<bool> passed_bool_value = pass_node_builder.PassThrough(bool_value);
// Graph outputs.
passed_float_value.SetName("passed_float_value").ConnectTo(graph.Out(0));
passed_int_value.SetName("passed_int_value").ConnectTo(graph.Out(1));
passed_bool_value.SetName("passed_bool_value").ConnectTo(graph.Out(2));
// Get `CalculatorGraphConfig` to pass it into `CalculatorGraph`
return graph.GetConfig();
}
ตอนนี้คุณจะมีลำดับหรือดัชนีที่ไม่ถูกต้องในบัตรผ่านการก่อสร้างไม่ได้
และบันทึกการพิมพ์โดยเดาประเภท Cast
จาก PassThrough
อินพุต
สิ่งที่ควรและไม่ควรทำ
กำหนดอินพุตกราฟตั้งแต่เริ่มต้น หากเป็นไปได้
ในโค้ดด้านล่าง
- การคาดเดาจำนวนอินพุตที่คุณมีในกราฟอาจเป็นเรื่องยาก
- อาจเกิดความผิดพลาดในภาพรวมและยากที่จะดูแลรักษาในอนาคต (เช่น ดัชนีถูกต้องหรือไม่ ชื่อ จะเกิดอะไรขึ้นหากมีการนำข้อมูลบางส่วนออกหรือกำหนดให้ไม่บังคับ เป็นต้น)
- มีการจำกัดการใช้
RunSomething
ซ้ำเนื่องจากกราฟอื่นๆ อาจมีความแตกต่าง อินพุต
สิ่งที่ไม่ควรทำ — ตัวอย่างของโค้ดที่ไม่ถูกต้อง
Stream<D> RunSomething(Stream<A> a, Stream<B> b, Graph& graph) {
Stream<C> c = graph.In(2).SetName("c").Cast<C>(); // Bad.
// ...
}
CalculatorGraphConfig BuildGraph() {
Graph graph;
Stream<A> a = graph.In(0).SetName("a").Cast<A>();
// 10/100/N lines of code.
Stream<B> b = graph.In(1).SetName("b").Cast<B>() // Bad.
Stream<D> d = RunSomething(a, b, graph);
// ...
return graph.GetConfig();
}
แต่ให้กำหนดอินพุตกราฟที่จุดเริ่มต้นของเครื่องมือสร้างกราฟแทน ดังนี้
ควร — ตัวอย่างของโค้ดที่ดี
Stream<D> RunSomething(Stream<A> a, Stream<B> b, Stream<C> c, Graph& graph) {
// ...
}
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Inputs.
Stream<A> a = graph.In(0).SetName("a").Cast<A>();
Stream<B> b = graph.In(1).SetName("b").Cast<B>();
Stream<C> c = graph.In(2).SetName("c").Cast<C>();
// 10/100/N lines of code.
Stream<D> d = RunSomething(a, b, c, graph);
// ...
return graph.GetConfig();
}
ใช้ std::optional
หากคุณมีสตรีมอินพุตหรือแพ็กเก็ตด้านข้างที่ไม่มี
กำหนดไว้และวางไว้ตอนต้นเสมอ
ควร — ตัวอย่างของโค้ดที่ดี
std::optional<Stream<A>> a;
if (needs_a) {
a = graph.In(0).SetName(a).Cast<A>();
}
กำหนดเอาต์พุตกราฟที่ส่วนท้ายสุด
ในโค้ดด้านล่าง
- การคาดเดาจำนวนเอาต์พุตของคุณในกราฟอาจเป็นเรื่องยาก
- อาจเกิดความผิดพลาดในภาพรวมและยากที่จะดูแลรักษาในอนาคต (เช่น ดัชนีถูกต้องหรือไม่ ชื่อ จะเกิดอะไรขึ้นหากมีการนำจุดนอกออกหรือถูกกำหนดให้ไม่บังคับ เป็นต้น)
- ใช้
RunSomething
ซ้ำได้อย่างจำกัด เนื่องจากกราฟอื่นๆ อาจมีเอาต์พุตต่างกัน
สิ่งที่ไม่ควรทำ — ตัวอย่างของโค้ดที่ไม่ถูกต้อง
void RunSomething(Stream<Input> input, Graph& graph) {
// ...
node.Out("OUTPUT_F")
.SetName("output_f").ConnectTo(graph.Out(2)); // Bad.
}
CalculatorGraphConfig BuildGraph() {
Graph graph;
// 10/100/N lines of code.
node.Out("OUTPUT_D")
.SetName("output_d").ConnectTo(graph.Out(0)); // Bad.
// 10/100/N lines of code.
node.Out("OUTPUT_E")
.SetName("output_e").ConnectTo(graph.Out(1)); // Bad.
// 10/100/N lines of code.
RunSomething(input, graph);
// ...
return graph.GetConfig();
}
แต่ให้กำหนดผลลัพธ์กราฟที่ส่วนท้ายสุดของเครื่องมือสร้างกราฟแทน ดังนี้
ควร — ตัวอย่างของโค้ดที่ดี
Stream<F> RunSomething(Stream<Input> input, Graph& graph) {
// ...
return node.Out("OUTPUT_F").Cast<F>();
}
CalculatorGraphConfig BuildGraph() {
Graph graph;
// 10/100/N lines of code.
Stream<D> d = node.Out("OUTPUT_D").Cast<D>();
// 10/100/N lines of code.
Stream<E> e = node.Out("OUTPUT_E").Cast<E>();
// 10/100/N lines of code.
Stream<F> f = RunSomething(input, graph);
// ...
// Outputs.
d.SetName("output_d").ConnectTo(graph.Out(0));
e.SetName("output_e").ConnectTo(graph.Out(1));
f.SetName("output_f").ConnectTo(graph.Out(2));
return graph.GetConfig();
}
แยกโหนดออกจากกัน
ใน MediaPipe สตรีมแพ็กเก็ตและแพ็กเก็ตด้านข้างจะมีความหมายพอๆ กับการประมวลผล และข้อกำหนดอินพุตของโหนดและผลิตภัณฑ์เอาต์พุตจะแสดงอย่างชัดเจน และแยกออกจากกันในแง่ของสตรีมและแพ็กเก็ตด้านข้างที่ใช้และ ผลิตได้จริง
สิ่งที่ไม่ควรทำ — ตัวอย่างของโค้ดที่ไม่ถูกต้อง
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Inputs.
Stream<A> a = graph.In(0).Cast<A>();
auto& node1 = graph.AddNode("Calculator1");
a.ConnectTo(node1.In("INPUT"));
auto& node2 = graph.AddNode("Calculator2");
node1.Out("OUTPUT").ConnectTo(node2.In("INPUT")); // Bad.
auto& node3 = graph.AddNode("Calculator3");
node1.Out("OUTPUT").ConnectTo(node3.In("INPUT_B")); // Bad.
node2.Out("OUTPUT").ConnectTo(node3.In("INPUT_C")); // Bad.
auto& node4 = graph.AddNode("Calculator4");
node1.Out("OUTPUT").ConnectTo(node4.In("INPUT_B")); // Bad.
node2.Out("OUTPUT").ConnectTo(node4.In("INPUT_C")); // Bad.
node3.Out("OUTPUT").ConnectTo(node4.In("INPUT_D")); // Bad.
// Outputs.
node1.Out("OUTPUT").SetName("b").ConnectTo(graph.Out(0)); // Bad.
node2.Out("OUTPUT").SetName("c").ConnectTo(graph.Out(1)); // Bad.
node3.Out("OUTPUT").SetName("d").ConnectTo(graph.Out(2)); // Bad.
node4.Out("OUTPUT").SetName("e").ConnectTo(graph.Out(3)); // Bad.
return graph.GetConfig();
}
ในโค้ดข้างต้น
- โหนดต่างๆ จะเชื่อมต่อกัน เช่น
node4
รู้ว่าอินพุตอยู่ตรงไหน ที่มาจาก (node1
,node2
,node3
) ซึ่งทำให้เปลี่ยนโครงสร้างภายในโค้ดได้ง่าย การบำรุงรักษาและการนำโค้ดมาใช้ซ้ำ- รูปแบบการใช้งานดังกล่าวเป็นการดาวน์เกรดจากการแสดงโปรโต โดยโหนด จะถูกแยกออกโดยค่าเริ่มต้น
- การโทร
node#.Out("OUTPUT")
ครั้งซ้ำกันและคุณจะอ่านได้ง่ายขึ้น สามารถใช้ชื่อที่ดูสะอาดตาแทนและระบุประเภทจริง
ดังนั้น เพื่อแก้ไขปัญหาข้างต้น คุณสามารถเขียนโค้ดการสร้างกราฟต่อไปนี้
ควร — ตัวอย่างของโค้ดที่ดี
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Inputs.
Stream<A> a = graph.In(0).Cast<A>();
// `node1` usage is limited to 3 lines below.
auto& node1 = graph.AddNode("Calculator1");
a.ConnectTo(node1.In("INPUT"));
Stream<B> b = node1.Out("OUTPUT").Cast<B>();
// `node2` usage is limited to 3 lines below.
auto& node2 = graph.AddNode("Calculator2");
b.ConnectTo(node2.In("INPUT"));
Stream<C> c = node2.Out("OUTPUT").Cast<C>();
// `node3` usage is limited to 4 lines below.
auto& node3 = graph.AddNode("Calculator3");
b.ConnectTo(node3.In("INPUT_B"));
c.ConnectTo(node3.In("INPUT_C"));
Stream<D> d = node3.Out("OUTPUT").Cast<D>();
// `node4` usage is limited to 5 lines below.
auto& node4 = graph.AddNode("Calculator4");
b.ConnectTo(node4.In("INPUT_B"));
c.ConnectTo(node4.In("INPUT_C"));
d.ConnectTo(node4.In("INPUT_D"));
Stream<E> e = node4.Out("OUTPUT").Cast<E>();
// Outputs.
b.SetName("b").ConnectTo(graph.Out(0));
c.SetName("c").ConnectTo(graph.Out(1));
d.SetName("d").ConnectTo(graph.Out(2));
e.SetName("e").ConnectTo(graph.Out(3));
return graph.GetConfig();
}
ในตอนนี้ คุณสามารถนํา node1
ออกได้ง่ายๆ แล้วทําให้ b
เป็นกราฟอินพุตและ
จำเป็นต้องอัปเดต node2
, node3
, node4
(เหมือนกับการแสดง Proto
ด้วย) เพราะถูกแยกออกจากกัน
โดยรวมแล้ว โค้ดข้างต้นจะจำลองกราฟ Proto อย่างใกล้ชิดมากขึ้น
input_stream: "a"
node {
calculator: "Calculator1"
input_stream: "INPUT:a"
output_stream: "OUTPUT:b"
}
node {
calculator: "Calculator2"
input_stream: "INPUT:b"
output_stream: "OUTPUT:C"
}
node {
calculator: "Calculator3"
input_stream: "INPUT_B:b"
input_stream: "INPUT_C:c"
output_stream: "OUTPUT:d"
}
node {
calculator: "Calculator4"
input_stream: "INPUT_B:b"
input_stream: "INPUT_C:c"
input_stream: "INPUT_D:d"
output_stream: "OUTPUT:e"
}
output_stream: "b"
output_stream: "c"
output_stream: "d"
output_stream: "e"
ยิ่งไปกว่านั้น ตอนนี้คุณสามารถดึงฟังก์ชันยูทิลิตีเพื่อนำกลับมาใช้ซ้ำในกราฟอื่นๆ ได้แล้ว โดยทำดังนี้
ควร — ตัวอย่างของโค้ดที่ดี
Stream<B> RunCalculator1(Stream<A> a, Graph& graph) {
auto& node = graph.AddNode("Calculator1");
a.ConnectTo(node.In("INPUT"));
return node.Out("OUTPUT").Cast<B>();
}
Stream<C> RunCalculator2(Stream<B> b, Graph& graph) {
auto& node = graph.AddNode("Calculator2");
b.ConnectTo(node.In("INPUT"));
return node.Out("OUTPUT").Cast<C>();
}
Stream<D> RunCalculator3(Stream<B> b, Stream<C> c, Graph& graph) {
auto& node = graph.AddNode("Calculator3");
b.ConnectTo(node.In("INPUT_B"));
c.ConnectTo(node.In("INPUT_C"));
return node.Out("OUTPUT").Cast<D>();
}
Stream<E> RunCalculator4(Stream<B> b, Stream<C> c, Stream<D> d, Graph& graph) {
auto& node = graph.AddNode("Calculator4");
b.ConnectTo(node.In("INPUT_B"));
c.ConnectTo(node.In("INPUT_C"));
d.ConnectTo(node.In("INPUT_D"));
return node.Out("OUTPUT").Cast<E>();
}
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Inputs.
Stream<A> a = graph.In(0).Cast<A>();
Stream<B> b = RunCalculator1(a, graph);
Stream<C> c = RunCalculator2(b, graph);
Stream<D> d = RunCalculator3(b, c, graph);
Stream<E> e = RunCalculator4(b, c, d, graph);
// Outputs.
b.SetName("b").ConnectTo(graph.Out(0));
c.SetName("c").ConnectTo(graph.Out(1));
d.SetName("d").ConnectTo(graph.Out(2));
e.SetName("e").ConnectTo(graph.Out(3));
return graph.GetConfig();
}
แยกโหนดเพื่อให้อ่านง่ายขึ้น
สิ่งที่ไม่ควรทำ — ตัวอย่างของโค้ดที่ไม่ถูกต้อง
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Inputs.
Stream<A> a = graph.In(0).Cast<A>();
auto& node1 = graph.AddNode("Calculator1");
a.ConnectTo(node1.In("INPUT"));
Stream<B> b = node1.Out("OUTPUT").Cast<B>();
auto& node2 = graph.AddNode("Calculator2");
b.ConnectTo(node2.In("INPUT"));
Stream<C> c = node2.Out("OUTPUT").Cast<C>();
auto& node3 = graph.AddNode("Calculator3");
b.ConnectTo(node3.In("INPUT_B"));
c.ConnectTo(node3.In("INPUT_C"));
Stream<D> d = node3.Out("OUTPUT").Cast<D>();
auto& node4 = graph.AddNode("Calculator4");
b.ConnectTo(node4.In("INPUT_B"));
c.ConnectTo(node4.In("INPUT_C"));
d.ConnectTo(node4.In("INPUT_D"));
Stream<E> e = node4.Out("OUTPUT").Cast<E>();
// Outputs.
b.SetName("b").ConnectTo(graph.Out(0));
c.SetName("c").ConnectTo(graph.Out(1));
d.SetName("d").ConnectTo(graph.Out(2));
e.SetName("e").ConnectTo(graph.Out(3));
return graph.GetConfig();
}
ในโค้ดข้างต้น อาจเป็นเรื่องยากที่จะเข้าใจแนวคิดว่าแต่ละโหนดเริ่มต้นที่ใด สิ้นสุด เพื่อปรับปรุงวิธีการนี้และช่วยโปรแกรมอ่านโค้ด คุณเพียงแค่เว้นว่าง ก่อนและหลังแต่ละโหนด
ควร — ตัวอย่างของโค้ดที่ดี
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Inputs.
Stream<A> a = graph.In(0).Cast<A>();
auto& node1 = graph.AddNode("Calculator1");
a.ConnectTo(node1.In("INPUT"));
Stream<B> b = node1.Out("OUTPUT").Cast<B>();
auto& node2 = graph.AddNode("Calculator2");
b.ConnectTo(node2.In("INPUT"));
Stream<C> c = node2.Out("OUTPUT").Cast<C>();
auto& node3 = graph.AddNode("Calculator3");
b.ConnectTo(node3.In("INPUT_B"));
c.ConnectTo(node3.In("INPUT_C"));
Stream<D> d = node3.Out("OUTPUT").Cast<D>();
auto& node4 = graph.AddNode("Calculator4");
b.ConnectTo(node4.In("INPUT_B"));
c.ConnectTo(node4.In("INPUT_C"));
d.ConnectTo(node4.In("INPUT_D"));
Stream<E> e = node4.Out("OUTPUT").Cast<E>();
// Outputs.
b.SetName("b").ConnectTo(graph.Out(0));
c.SetName("c").ConnectTo(graph.Out(1));
d.SetName("d").ConnectTo(graph.Out(2));
e.SetName("e").ConnectTo(graph.Out(3));
return graph.GetConfig();
}
ตัวแทนข้างต้นตรงกับ CalculatorGraphConfig
Proto ด้วย
การนำเสนอวิดีโอที่ดีขึ้น
หากคุณแยกโหนดเป็นฟังก์ชันยูทิลิตี โหนดจะกำหนดขอบเขตภายในฟังก์ชัน และเห็นได้ชัดเจนว่าเริ่มจากจุดใดและถึงจุดไหน ดังนั้นการมี มี:
ควร — ตัวอย่างของโค้ดที่ดี
CalculatorGraphConfig BuildGraph() {
Graph graph;
// Inputs.
Stream<A> a = graph.In(0).Cast<A>();
Stream<B> b = RunCalculator1(a, graph);
Stream<C> c = RunCalculator2(b, graph);
Stream<D> d = RunCalculator3(b, c, graph);
Stream<E> e = RunCalculator4(b, c, d, graph);
// Outputs.
b.SetName("b").ConnectTo(graph.Out(0));
c.SetName("c").ConnectTo(graph.Out(1));
d.SetName("d").ConnectTo(graph.Out(2));
e.SetName("e").ConnectTo(graph.Out(3));
return graph.GetConfig();
}