Trình tạo biểu đồ C++ là một công cụ mạnh mẽ để:
- Xây dựng biểu đồ phức tạp
- Các biểu đồ tham số (ví dụ: cài đặt đại biểu trên
InferenceCalculator
, bật/tắt các phần của biểu đồ) - Loại bỏ biểu đồ trùng lặp (ví dụ: thay vì biểu đồ dành riêng cho CPU và GPU trong pbtxt bạn có thể có một mã xây dựng các đồ thị bắt buộc, chia sẻ nhất có thể)
- Hỗ trợ đầu vào/đầu ra biểu đồ tuỳ chọn
- Tuỳ chỉnh biểu đồ theo nền tảng
Cách sử dụng cơ bản
Hãy xem cách sử dụng trình tạo biểu đồ C++ cho một biểu đồ đơn giản:
# 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 {} }
}
}
}
Hàm tạo CalculatorGraphConfig
ở trên có thể có dạng như sau:
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();
}
Tóm tắt ngắn:
- Sử dụng
Graph::In/SideIn
để nhận dữ liệu đầu vào biểu đồ dưới dạngStream/SidePacket
- Dùng
Node::Out/SideOut
để nhận dữ liệu đầu ra của nút dưới dạngStream/SidePacket
- Dùng
Stream/SidePacket::ConnectTo
để kết nối luồng và gói phụ với đầu vào nút (Node::In/SideIn
) và đầu ra biểu đồ (Graph::Out/SideOut
)- Có một "lối tắt" toán tử
>>
mà bạn có thể sử dụng thay cho HàmConnectTo
(ví dụ:x >> node.In("IN")
).
- Có một "lối tắt" toán tử
Stream/SidePacket::Cast
dùng để truyền luồng phát hoặc gói phụ củaAnyType
(ví dụ:Stream<AnyType> in = graph.In(0);
) cho một loại cụ thể- Việc sử dụng các loại thực tế thay vì
AnyType
sẽ giúp bạn có một đường dẫn tốt hơn cho khai thác các chức năng của trình tạo biểu đồ và cải thiện biểu đồ của bạn dễ đọc.
- Việc sử dụng các loại thực tế thay vì
Cách sử dụng nâng cao
Hàm hiệu dụng
Hãy trích xuất mã xây dựng suy luận thành một hàm số hiệu dụng chuyên dụng để giúp dễ đọc và sử dụng lại mã:
// 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();
}
Do đó, RunInference
cung cấp một giao diện rõ ràng cho biết đâu là
đầu vào/đầu ra và các loại của chúng.
Có thể dễ dàng tái sử dụng, ví dụ: bạn chỉ cần nhập một vài dòng nếu muốn chạy thêm suy luận mô hình:
// 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);
Và bạn không cần sao chép tên và thẻ (InferenceCalculator
,
TENSORS
, MODEL
) hoặc giới thiệu các hằng số riêng ở đây – những
thông tin chi tiết được bản địa hoá thành hàm RunInference
.
Hạng tiện ích
Và chắc chắn, không chỉ về chức năng, trong một số trường hợp, sẽ có lợi khi giới thiệu các lớp tiện ích có thể giúp tạo mã xây dựng biểu đồ dễ đọc hơn và ít gặp lỗi hơn.
MediaPipe cung cấp công cụ tính PassThroughCalculator
, chỉ cần vượt qua
thông qua dữ liệu đầu vào:
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"
}
Hãy xem mã xây dựng C++ đơn giản để tạo biểu đồ trên:
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();
}
Mặc dù việc biểu diễn pbtxt
có thể dễ gặp lỗi (khi chúng ta có nhiều dữ liệu đầu vào để truyền
qua), mã C++ trông còn tệ hơn: các thẻ trống và lệnh gọi Cast
lặp lại. Hãy
xem cách chúng tôi có thể làm tốt hơn bằng việc giới thiệu 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_;
};
Và giờ đây, mã xây dựng biểu đồ có thể có dạng như:
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();
}
Giờ đây, bạn không thể có thứ tự hoặc chỉ mục không chính xác trong quá trình tạo bản dựng
và lưu một số nội dung nhập bằng cách đoán loại cho Cast
trong PassThrough
đầu vào.
Những việc nên làm và việc không nên làm
Xác định đầu vào biểu đồ ngay từ đầu nếu có thể
Trong mã bên dưới:
- Có thể khó đoán số lượng dữ liệu đầu vào mà bạn có trong biểu đồ.
- Nhìn chung, có thể dễ gặp lỗi và khó duy trì trong tương lai (ví dụ: chỉ mục chính xác không? tên? điều gì sẽ xảy ra nếu một số thông tin đầu vào bị xoá hoặc đặt thành không bắt buộc? v.v).
- Việc sử dụng lại
RunSomething
bị hạn chế vì các biểu đồ khác có thể có giá trị đầu vào
KHÔNG NÊN — ví dụ về mã không hợp lệ.
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();
}
Thay vào đó, hãy xác định dữ liệu đầu vào biểu đồ ngay từ đầu trình tạo biểu đồ:
NÊN — ví dụ về mã hiệu quả.
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();
}
Sử dụng std::optional
nếu bạn có luồng đầu vào hoặc gói phụ
luôn được xác định và đặt ngay từ đầu:
NÊN — ví dụ về mã hiệu quả.
std::optional<Stream<A>> a;
if (needs_a) {
a = graph.In(0).SetName(a).Cast<A>();
}
Xác định đầu ra của biểu đồ ở cuối
Trong mã bên dưới:
- Có thể khó đoán số đầu ra bạn có trong biểu đồ.
- Nhìn chung, có thể dễ gặp lỗi và khó duy trì trong tương lai (ví dụ: chỉ mục chính xác không? tên? điều gì sẽ xảy ra nếu một số thói quen xấu bị loại bỏ hoặc đặt thành không bắt buộc? v.v).
- Việc sử dụng lại
RunSomething
bị hạn chế vì các biểu đồ khác có thể có kết quả khác nhau
KHÔNG NÊN — ví dụ về mã không hợp lệ.
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();
}
Thay vào đó, hãy xác định kết quả biểu đồ ở cuối trình tạo biểu đồ:
NÊN — ví dụ về mã hiệu quả.
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();
}
Giữ tách các nút khỏi nhau
Trong MediaPipe, luồng gói và gói phụ có ý nghĩa như quá trình xử lý nút. Và bất kỳ yêu cầu đầu vào và sản phẩm đầu ra nào của nút đều được thể hiện rõ ràng độc lập về luồng và gói phụ mà nó sử dụng và tạo ra.
KHÔNG NÊN — ví dụ về mã không hợp lệ.
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();
}
Trong mã trên:
- Các nút được kết nối với nhau, ví dụ:
node4
biết dữ liệu đầu vào của nó ở đâu đến từ (node1
,node2
,node3
) và việc này khiến việc tái cấu trúc trở nên phức tạp, bảo trì và sử dụng lại mã- Mẫu sử dụng như vậy là cách hạ cấp từ cách biểu diễn proto, trong đó các nút sẽ được tách riêng theo mặc định.
- Các lệnh gọi
node#.Out("OUTPUT")
bị trùng lặp và khả năng đọc sẽ bị ảnh hưởng khi bạn có thể sử dụng tên rõ ràng hơn, đồng thời cung cấp một loại thực tế.
Vì vậy, để khắc phục các vấn đề trên, bạn có thể viết mã tạo biểu đồ sau:
NÊN — ví dụ về mã hiệu quả.
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();
}
Bây giờ, nếu cần, bạn có thể dễ dàng xoá node1
và biến b
thành dữ liệu đầu vào biểu đồ và không
cần cập nhật cho node2
, node3
, node4
(tương tự như ở dạng biểu diễn proto
nhân tiện), vì chúng tách rời nhau.
Nhìn chung, mã trên sao chép biểu đồ proto chặt chẽ hơn:
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"
Ngoài ra, giờ đây bạn có thể trích xuất các hàm số hiệu dụng để sử dụng lại trong các biểu đồ khác:
NÊN — ví dụ về mã hiệu quả.
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();
}
Phân tách các nút để dễ đọc hơn
KHÔNG NÊN — ví dụ về mã không hợp lệ.
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();
}
Trong mã trên, bạn có thể khó nắm bắt ý tưởng nơi mỗi nút bắt đầu và kết thúc. Để cải thiện điều này và giúp trình đọc mã, bạn chỉ cần để trống dòng trước và sau mỗi nút:
NÊN — ví dụ về mã hiệu quả.
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();
}
Ngoài ra, cách trình bày ở trên khớp với CalculatorGraphConfig
proto
tốt hơn.
Nếu bạn trích xuất các nút vào hàm hiệu dụng, thì các nút đó sẽ nằm trong phạm vi của các hàm hoạt động đã được xác định rõ ràng nơi bắt đầu và kết thúc, do vậy hoàn toàn không có vấn đề gì có:
NÊN — ví dụ về mã hiệu quả.
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();
}