• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Function Flow Runtime Task Graph (C)
2
3<!--Kit: Function Flow Runtime Kit-->
4<!--Subsystem: Resourceschedule-->
5<!--Owner: @chuchihtung; @yanleo-->
6<!--Designer: @geoffrey_guo; @huangyouzhong-->
7<!--Tester: @lotsof; @sunxuhao-->
8<!--Adviser: @foryourself-->
9
10## Overview
11
12The FFRT task graph supports task dependency and data dependency. Each node in the task graph indicates a task, and each edge indicates the build dependency between tasks. Task dependency is classified into input dependency (`in_deps`) and output dependency (`out_deps`).
13
14You can use either of the following ways to build a task graph:
15
16- Use the task dependency to build a task graph. The task `handle` is used to indicate a task object.
17- Use the data dependency to build a task graph. The data object is abstracted as a data signature, and each data signature uniquely indicates a data object.
18
19### Task dependency
20
21> **NOTE**
22>
23> When a task handle appears in `in_deps`, the corresponding task is the previous task. When a task handle appears in `out_deps`, the corresponding task is the subsequent task.
24
25Task dependency applies to scenarios where tasks have specific sequence or logical process requirements. For example:
26
27- Tasks with sequence. For example, a data preprocessing task is executed before a model training task.
28- Logic process control. For example, in a commodity transaction process, three steps need to be performed in sequence: order placement, production, and logistics transportation.
29- Multi-level chain: For example, during video processing, you can perform tasks such as transcoding, generating thumbnails, adding watermarks, and releasing the final video.
30
31### Data Dependency
32
33> **NOTE**
34>
35> When the signature of a data object appears in `in_deps` of a task, the task is referred to as a consumer task that executes without modifying the original input data object.
36> When the signature of a data object appears in `out_deps` of a task, the task is referred to as a producer task that updates the output data object's content to create a new version.
37
38Data dependency applies to scenarios where tasks are triggered by data production and consumption relationships.
39
40A data object may have multiple versions. Each version corresponds to one producer task and zero, one, or more consumer tasks. A sequence of the data object versions and the version-specific producer task and consumer tasks are defined according to the delivery sequence of the producer task and consumer tasks.
41
42When all producer tasks and consumer tasks of the data object of all the available versions are executed, the data dependency is removed. In this case, the task enters the ready state and can be scheduled for execution.
43
44FFRT can dynamically build producer/consumer-based data dependencies between tasks at runtime and perform scheduling based on the task data dependency status, including:
45
46- Producer-Consumer dependency
47
48  A dependency formed between the producer task of a data object of a specific version and a consumer task of the data object of the same version. It is also referred to as a read-after-write dependency.
49
50- Consumer-Producer dependency
51
52  A dependency formed between a consumer task of a data object of a specific version and the producer task of the data object of the next version. It is also referred to as a write-after-read dependency.
53
54- Producer-Producer dependency
55
56  A dependency formed between the producer task of a data object of a specific version and a producer task of the data object of the next version. It is also referred to as a write-after-write dependency.
57
58For example, the relationship between a group of tasks and data A is expressed as follows:
59
60```cpp
61task1(OUT A);
62task2(IN A);
63task3(IN A);
64task4(OUT A);
65task5(OUT A);
66```
67
68![image](figures/ffrt_figure3.png)
69
70For ease of description, circles are used to represent tasks and squares are used to represent data.
71
72The following conclusions can be drawn:
73
74- task1 and task2/task3 form a producer-consumer dependency. This means that task2/task3 can read data A only after task1 writes data A.
75- task2/task3 and task4 form a consumer-producer dependency. This means that task4 can write data A only after task2/task3 reads data A.
76- task 4 and task 5 form a producer-producer dependency. This means that task 5 can write data A only after task 4 writes data A.
77
78## Example: Streaming Video Processing
79
80A user uploads a video to the platform. The processing steps include: parsing, transcoding, generating a thumbnail, adding a watermark, and releasing the video. Transcoding and thumbnail generation can occur simultaneously. The following figure shows the task process.
81
82![image](figures/ffrt_figure1.png)
83
84The FFRT provides task graph that can describe the task dependency and parallelize the preceding video processing process. The code is as follows:
85
86```c
87#include <stdio.h>
88#include "ffrt/ffrt.h" // From the OpenHarmony third-party library "@ppd/ffrt"
89
90void func_TaskA(void* arg)
91{
92    printf("Parse\n");
93}
94
95void func_TaskB(void* arg)
96{
97    printf("Transcode\n");
98}
99
100void func_TaskC(void* arg)
101{
102    printf("Generate a thumbnail\n");
103}
104
105void func_TaskD(void* arg)
106{
107    printf("Add watermark\n");
108}
109
110void func_TaskE(void* arg)
111{
112    printf("Release\n");
113}
114
115int main()
116{
117    // Submit task A.
118    ffrt_task_handle_t hTaskA = ffrt_submit_h_f(func_TaskA, NULL, NULL, NULL, NULL);
119
120    // Submit tasks B and C.
121    ffrt_dependence_t taskA_deps[] = {{ffrt_dependence_task, hTaskA}};
122    ffrt_deps_t dTaskA = {1, taskA_deps};
123    ffrt_task_handle_t hTaskB = ffrt_submit_h_f(func_TaskB, NULL, &dTaskA, NULL, NULL);
124    ffrt_task_handle_t hTaskC = ffrt_submit_h_f(func_TaskC, NULL, &dTaskA, NULL, NULL);
125
126    // Submit task D.
127    ffrt_dependence_t taskBC_deps[] = {{ffrt_dependence_task, hTaskB}, {ffrt_dependence_task, hTaskC}};
128    ffrt_deps_t dTaskBC = {2, taskBC_deps};
129    ffrt_task_handle_t hTaskD = ffrt_submit_h_f(func_TaskD, NULL, &dTaskBC, NULL, NULL);
130
131    // Submit task E.
132    ffrt_dependence_t taskD_deps[] = {{ffrt_dependence_task, hTaskD}};
133    ffrt_deps_t dTaskD = {1, taskD_deps};
134    ffrt_submit_f(func_TaskE, NULL, &dTaskD, NULL, NULL);
135
136    // Wait until all tasks are complete.
137    ffrt_wait();
138
139    ffrt_task_handle_destroy(hTaskA);
140    ffrt_task_handle_destroy(hTaskB);
141    ffrt_task_handle_destroy(hTaskC);
142    ffrt_task_handle_destroy(hTaskD);
143    return 0;
144}
145```
146
147The expected output may be as follows:
148
149```plain
150Video parsing
151Video transcoding
152Thumbnails generation
153Watermark adding
154Video release
155```
156
157## Example: Fibonacci Sequence
158
159Each number in the Fibonacci sequence is the sum of the first two numbers. The process of calculating the Fibonacci number can well express the task dependency through the data object. The code for calculating the Fibonacci number using the FFRT framework is as follows:
160
161```c
162#include <stdio.h>
163#include "ffrt/ffrt.h" // From the OpenHarmony third-party library "@ppd/ffrt"
164
165typedef struct {
166    int x;
167    int* y;
168} fib_ffrt_s;
169
170void fib_ffrt(void* arg)
171{
172    fib_ffrt_s* p = (fib_ffrt_s*)arg;
173    int x = p->x;
174    int* y = p->y;
175
176    if (x <= 1) {
177        *y = x;
178    } else {
179        int y1, y2;
180        fib_ffrt_s s1 = {x - 1, &y1};
181        fib_ffrt_s s2 = {x - 2, &y2};
182
183        // Build data dependencies.
184        ffrt_dependence_t dx_deps[] = {{ffrt_dependence_data, &x}};
185        ffrt_deps_t dx = {1, dx_deps};
186        ffrt_dependence_t dy1_deps[] = {{ffrt_dependence_data, &y1}};
187        ffrt_deps_t dy1 = {1, dy1_deps};
188        ffrt_dependence_t dy2_deps[] = {{ffrt_dependence_data, &y2}};
189        ffrt_deps_t dy2 = {1, dy2_deps};
190        ffrt_dependence_t dy12_deps[] = {{ffrt_dependence_data, &y1}, {ffrt_dependence_data, &y2}};
191        ffrt_deps_t dy12 = {2, dy12_deps};
192
193        // Submit tasks separately.
194        ffrt_submit_f(fib_ffrt, &s1, &dx, &dy1, NULL);
195        ffrt_submit_f(fib_ffrt, &s2, &dx, &dy2, NULL);
196
197        // Wait until the task is complete.
198        ffrt_wait_deps(&dy12);
199        *y = y1 + y2;
200    }
201}
202
203int main()
204{
205    int r;
206    fib_ffrt_s s = {5, &r};
207    ffrt_dependence_t dr_deps[] = {{ffrt_dependence_data, &r}};
208    ffrt_deps_t dr = {1, dr_deps};
209    ffrt_submit_f(fib_ffrt, &s, NULL, &dr, NULL);
210
211    // Wait until the task is complete.
212    ffrt_wait_deps(&dr);
213    printf("Fibonacci(5) is %d\n", r);
214    return 0;
215}
216```
217
218Expected output:
219
220```plain
221Fibonacci(5) is 5
222```
223
224In the example, `fibonacci(x-1)` and `fibonacci(x-2)` are submitted to FFRT as two tasks. After the two tasks are complete, the results are accumulated. Although a single task is split into two subtasks, the subtasks can be further split. Therefore, the concurrency of the entire computational graph is very high.
225
226Each task forms a call tree in the FFRT.
227
228![image](figures/ffrt_figure2.png)
229
230## Available APIs
231
232The main FFRT APIs involved in the preceding example are as follows:
233
234| Name                                                      | Description                            |
235| ---------------------------------------------------------- | -------------------------------- |
236| [ffrt_submit_f](ffrt-api-guideline-c.md#ffrt_submit_f)     | Submits a task.              |
237| [ffrt_submit_h_f](ffrt-api-guideline-c.md#ffrt_submit_h_f) | Submits a task, and obtains the task handle.|
238| [ffrt_wait_deps](ffrt-api-guideline-c.md#ffrt_wait_deps)   | Waits until the dependent tasks are complete.            |
239
240> **NOTE**
241>
242> - For details about how to use FFRT C++ APIs, see [Using FFRT C++ APIs](ffrt-development-guideline.md#using-ffrt-c-api-1).
243> - When using FFRT C or C++ APIs, you can refer to the FFRT C++ API third-party library to simplify the header file inclusion, that is, use the `#include "ffrt/ffrt.h"` header file to include statements.
244
245## Constraints
246
247- For `ffrt_submit_base`, the total number of input dependencies and output dependencies of each task cannot exceed 8.
248- For `ffrt_submit_h_base`, the total number of input dependencies and output dependencies of each task cannot exceed 7.
249- When a parameter is used as both input dependency and output dependency, the number of dependencies is counted only once. For example, if the input dependency is `{&x}` and the output dependency is `{&x}`, the actual number of dependencies is 1.
250