1## FFRT 用户编程指南 2 3> Function Flow编程模型是一种基于任务和数据驱动的并发编程模型,允许开发者通过任务及其依赖关系描述的方式进行应用开发。FFRT(Function Flow运行时)是支持Function Flow编程模型的软件运行时库,用于调度执行开发者基于Function Flow编程模型开发的应用。通过Function Flow编程模型和FFRT,开发者可专注于应用功能开发,由FFRT在运行时根据任务依赖状态和可用执行资源自动并发调度和执行任务。 4> 5> 本文用于指导开发者基于Function Flow编程模型和FFRT实现并行编程。 6 7<hr/> 8# 版本 9 10| 版本 | 编辑 | 主要变更 | 日期 | 11| ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | ---------- | 12| V0.1 | linjiashu <br />zhangguowei <br />huangyouzhong | 发布以下API:<br />1. task 管理,包括:submit,wait,task_attr, task_handle/submit_h<br />2. 同步原语,包括:mutex,condition_variable<br />3. Deadline 调度<br />4. 杂项:sleep,yield<br /> | 2022/09/26 | 13 14 15<br/> 16 17<hr/> 18# 缩写 19 20| 缩略语 | 英文全名 | 中文解释 | 21| ------------- | ------------------------------- | ------------------------------------------------------------ | 22| FFRT | Function Flow Run Time | 软件实现Function Flow运行时用于任务调度和执行 | 23| Function Flow | Function Flow Programming Model | Function Flow编程模型 | 24| Pure Function | Pure Function | 纯函数,注意本文中定义的纯函数指的是通过表达相互间数据依赖即可由调度系统保证正确执行的任务。 | 25 26 27<br/> 28<hr/> 29# 编程模型 30## 两种编程模型 31 32| | 线程编程模型 | FFRT任务编程模型 | 33| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 34| 并行度挖掘方式 | 程序员通过创建多线程并把任务分配到每个线程中执行来挖掘运行时的并行度 | 程序员(编译器工具或语言特性配合)静态编程时将应用分解成任务及其数据依赖关系,运行时调度器分配任务到工作线程执行 | 35| 谁负责线程创建 | 程序员负责创建线程,线程编程模型无法约束线程的创建,滥用可能造成系统中大量线程 | FFRT运行时负责工作线程池的创建和管理由调度器负责,程序员无法直接创建线程 | 36| 负载均衡 | 程序员静态编程时将任务映射到线程,映射不合理或任务执行时间不确定造成线程负载不均 | FFRT运行时根据线程执行状态调度就绪任务到空闲线程执行,减轻了线程负载不均问题 | 37| 调度开销 | 线程调度由内核态调度器完成,调度开销大 | FFRT运行时在用户态以协程方式调度执行,相比内核线程调度机制更为轻量,减小调度的开销,并可通过硬化调度卸载进一步减小调度开销 | 38| 依赖表达 | 线程创建时即处于可执行状态,执行时与其他线程同步操作,增加线程切换 | FFRT运行时根据任务创建时显式表达的输入依赖和输出依赖关系判断任务可执行状态,当输入依赖不满足时,任务不被调度执行 | 39 40 41 42## Function Flow 任务编程模型 43 44Function Flow编程模型允许开发者通过任务及其依赖关系描述的方式进行应用开发,其主要特性包括`Task-Based` 和 `Data-Driven` 。 45 46### Task-Based 特性 47 48`Task-Based` 指在Function Flow编程模型中开发者以任务方式来组织应用程序表达,运行时以任务粒度执行调度。 49 50任务定义为一种面向开发者的编程线索和面向运行时的执行对象,通常包含一组指令序列及其操作的数据上下文环境。 51 52Function Flow编程模型中的任务包含以下主要特征: 53 54- 任务之间可指定依赖关系,依赖关系通过`Data-Driven`方式表达。 55- 任务可支持嵌套,即任务在执行过程中可生成新的任务下发给运行时,形成父子任务关系。 56- 多任务支持互同步操作,例如等待,锁,条件变量等。 57 58> 注意 59> 60> 任务颗粒度影响应用执行性能,颗粒度过小增加调度开销,颗粒度过大降低并行度。Function Flow编程模型中任务的目标颗粒度最小为100us量级,开发者应注意合理控制任务颗粒度。 61 62### Data-Driven 特性 63 64`Data-Driven`指任务之间的依赖关系通过数据依赖表达。 65 66任务执行过程中对其关联的数据对象进行读写操作。在Function Flow编程模型中,数据对象表达抽象为数据签名,每个数据签名唯一对应一个数据对象。 67 68数据依赖抽象为任务所操作的数据对象的数据签名列表,包括输入数据依赖`in_deps`和输出数据依赖`out_deps`。数据对象的签名出现在一个任务的`in_deps`中时,该任务称为数据对象的消费者任务,消费者任务执行不改变其输入数据对象的内容;数据对象的签名出现在任务的`out_deps`中时,该任务称为数据对象的生产者任务,生产者任务执行改变其输出数据对象的内容,从而生成该数据对象的一个新的版本。 69 70一个数据对象可能存在多个版本,每个版本对应一个生产者任务和零个,一个或多个消费者任务,根据生产者任务和消费者任务的下发顺序定义数据对象的多个版本的顺序以及每个版本所对应的生产者和消费者任务。 71 72数据依赖解除的任务进入就绪状态允许被调度执行,依赖解除状态指任务所有输入数据对象版本的生产者任务执行完成,且所有输出数据对象版本的所有消费者任务执行完成的状态。 73 74通过上述`Data-Driven`的数据依赖表达,FFRT在运行时可动态构建任务之间的基于生产者/消费者的数据依赖关系并遵循任务数据依赖状态执行调度,包括: 75 76- Producer-Consumer 依赖 77 78 一个数据对象版本的生产者任务和该数据对象版本的消费者任务之间形成的依赖关系,也称为Read-after-Write依赖。 79 80- Consumer-Producer 依赖 81 82 一个数据对象版本的消费者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Read依赖。 83 84- Producer-Producer 依赖 85 86 一个数据对象版本的生产者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Write依赖。 87 88 89例如,如果有这么一些任务,与数据A的关系表述为: 90```{.cpp} 91task1(OUT A); 92task2(IN A); 93task3(IN A); 94task4(OUT A); 95task5(OUT A); 96``` 97 98<img src="images/image-20220926150341102.png" style="zoom:60%" /> 99 100> 为表述方便,本文中的数据流图均以圆圈表示 Task,方块表示数据。 101 102可以得出以下结论: 103- task1 与task2/task3 构成Producer-Consumer 依赖,即:task2/task3 需要等到task1 写完A之后才能读A 104- task2/task3 与task4 构成Consumer-Producer 依赖,即:task4 需要等到task2/task3 读完A之后才能写A 105- task4 与task5 构成Producer-Producer 依赖,即:task5 需要等到task4 写完A之后才能写A 106 107 108 109# C++ API 110 111> C++ API采用接近C++11的命名风格,以`ffrt`命名空间替代`std`命名空间 112> 需编译使用-std=c++17 113 114## 任务管理 115### submit 116<hr/> 117* 向调度器提交一个task 118* 该接口是异步的,即该接口不等到task完成即可返回,因此,通常与[wait](#wait) 配合使用 119 120#### 声明 121 122```{.cpp} 123namespace ffrt { 124void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {}); 125void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {}); 126} 127``` 128 129#### 参数 130 131`func` 132 133* 可被std::function 接收的一切CPU 可执行体,可以为C++ 定义的Lambda 函数闭包,函数指针,甚至是函数对象 134 135`in_deps` 136 137* 该参数是可选的 138* 该参数用于描述该任务的输入依赖,FFRT 通过数据的虚拟地址作为数据的Signature 来建立依赖 139 140`out_deps` 141 142* 该参数是可选的 143* 该参数用于描述该任务的输出依赖 144* `注意`:该依赖值本质上是一个数值,ffrt没办法区分该值是合理的还是不合理的,会假定输入的值是合理的进行处理;但不建议采用NULL,1, 2 等值来建立依赖关系,建议采用实际的内存地址,因为前者使用不当会建立起不必要的依赖,影响并发 145 146`attr` 147 148* 该参数是可选的 149* 该参数用于描述Task 的属性,比如qos 等,详见 [task_attr](#task_attr)章节 150 151#### 返回值 152 153* 不涉及 154 155#### 描述 156* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用 157 158* 该接口支持嵌套调用,即任务中可以继续提交子任务 159 160* 该接口在实现上使用多个重载版本以优化性能(基于移动语义,初始化列表等),用户只需按上述原型使用,编译时会自动选择最佳版本,支持的重载版本有: 161 162 ```{.cpp} 163 void submit(std::function<void()>&& func); 164 void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps); 165 void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps); 166 void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps, const task_attr& attr); 167 168 void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps); 169 void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps); 170 void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps, const task_attr& attr); 171 172 void submit(const std::function<void()>& func); 173 void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps); 174 void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps); 175 void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps, const task_attr& attr); 176 177 void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps); 178 void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps); 179 void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps, const task_attr& attr); 180 ``` 181 182 183#### 样例 184 185**submit and wait** 186 187```{.cpp} 188#include <iostream> 189#include "ffrt.h" 190 191int main(int narg, char** argv) 192{ 193 int i = 0; 194 for (i = 0; i < 3; i++) { 195 ffrt::submit([i] { std::cout << "num: " << i << std::endl; }); 196 } 197 ffrt::wait(); 198 return 0; 199} 200``` 201 202`解析`: 203 2041) 该示例中连续下发了3个Task,Task 使用C++ 11 Lambda 来描述(实际中Task 还可以使用函数指针,函数对象来描述),这些 Task 都会读取i,但是不会写任何变量; 205 2062) ffrt::submit 为异步下发,因此,Task2 并不会等到 Task1 执行完成之后再下发; 207 2083) ffrt::wait 用于实现待所有Task 都执行完成之后 main 函数再退出; 209 2104) 由于3个Task 在数据依赖关系上没有生产者-消费者或生产者-生产者依赖关系,因此3个 Task 是可以并行的,1种可能的输出是: 211 212```{.cpp} 213num: 0 214num: 2 215num: 1 216``` 217 218`注意`: 219 220如果将Lambda 表达式中的值捕获设置成引用捕获(即`[&i] { std::cout << "num: " << i << std::endl; }`),可能得到的输出为: 221 222```{.cpp} 223num: 2 224num: 2 225num: 2 226``` 227 228这是因为FFRT 是异步编程模型,在第一个task 真正开始执行的时候,i 的值可能已经被修改为1或者2 229 230 231 232**data verison** 233 234<img src="images/image-20220926150341102.png" style="zoom:60%" /> 235 236```{.cpp} 237#include <iostream> 238#include "ffrt.h" 239 240int main(int narg, char** argv) 241{ 242 int x = 1; 243 ffrt::submit([&] {x = 100; std::cout << "x:" << x << std::endl;}, {}, {&x}); 244 ffrt::submit([&] {std::cout << "x:" << x << std::endl;}, {&x}, {}); 245 ffrt::submit([&] {std::cout << "x:" << x << std::endl;}, {&x}, {}); 246 ffrt::submit([&] {x++; std::cout << "x:" << x << std::endl;}, {}, {&x}); 247 ffrt::submit([&] {x++; std::cout << "x:" << x << std::endl;}, {}, {&x}); 248 249 ffrt::wait(); 250 return 0; 251} 252``` 253 254 `解析`: 255 2561) 按上一章节[Data-Driven 特性](#Data-Driven 特性)的描述,输出一定为: 257 258```{.cpp} 259x:100 260x:100 261x:100 262x:101 263x:102 264``` 265 266**nested task** 267 268```{.cpp} 269#include <iostream> 270#include "ffrt.h" 271 272int main(int narg, char** argv) 273{ 274 ffrt::submit([&] { 275 std::cout << "task 1" << std::endl; 276 ffrt::submit([&] {std::cout << "nested task 1.1" << std::endl;}, {}, {}); 277 ffrt::submit([&] {std::cout << "nested task 1.2" << std::endl;}, {}, {}); 278 ffrt::wait(); 279 }, {}, {}); 280 281 ffrt::submit([&] { 282 std::cout << "task 2" << std::endl; 283 ffrt::submit([&] {std::cout << "nested task 2.1" << std::endl;}, {}, {}); 284 ffrt::submit([&] {std::cout << "nested task 2.2" << std::endl;}, {}, {}); 285 ffrt::wait(); 286 }, {}, {}); 287 ffrt::wait(); 288 return 0; 289} 290``` 291 292 `解析`: 293 2941) FFRT允许在 Task 内部继续提交多个SubTask,这样 Task 之间可以建立起一颗调用树; 295 2962) Task1 和Task2 可以并行,Task 1.1/1.2/2.1/2.2 之间也可以并行,因此1种可行的输出为: 297 298``` 299task 1 300nested task 1.1 301task 2 302nested task 1.2 303nested task 2.2 304nested task 2.1 305``` 306 307### wait 308<hr/> 309* 同步等待,与[submit](#submit) 配合使用 310* 等待指定的数据被生产完成,或等待当前任务的所有子任务完成,在不满足条件之前,当前的执行上下文被suspend,在满足条件后恢复执行 311 312#### 声明 313 314```{.cpp} 315namespace ffrt { 316void wait(const std::vector<const void*>& deps); 317void wait(); 318} 319``` 320 321#### 参数 322 323`deps` 324 325* 需要等待被生产完成的数据的虚拟地址,这些地址可能作为某些任务在submit 时的out_deps 326 327#### 返回值 328 329* 不涉及 330 331#### 描述 332* wait(deps) 用于等待deps指代的数据被生产完成才能执行后面的代码 333* wait() 用于等待当前上下文提交的所有子任务(`注意:不包括孙子任务`)都完成才能执行后面的代码 334* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用 335* 在FFRT task 外部调用的wait 是OS 能够感知的等待,相对于FFRT task 内部调用的wait 是更加昂贵的,因此我们希望尽可能让更多的wait 发生在FFRT task 内部 ,而不是FFRT task 外部 336 337#### 样例 338 339**recursive fibonacci** 340 341串行版的fibonacci 可以实现为: 342 343```{.cpp} 344#include <iostream> 345 346void fib(int x, int& y) { 347 if (x <= 1) { 348 y = x; 349 } else { 350 int y1, y2; 351 fib(x - 1, y1); 352 fib(x - 2, y2); 353 y = y1 + y2; 354 } 355} 356 357int main(int narg, char** argv) 358{ 359 int r; 360 fib(10, r); 361 std::cout << "fibonacci 10: " << r << std::endl; 362 return 0; 363} 364``` 365 366若要使用 FFRT 实现并行(注,对于单纯的fibonacci,单个 Task 的计算量极小,不具有并行加速的意义,但这种调用pattern 对并行编程模型的灵活性考验是非常高的),其中1种可行的实现为: 367 368```{.cpp} 369#include <iostream> 370 371#include "ffrt.h" 372 373void fib_ffrt(int x, int& y) 374{ 375 if (x <= 1) { 376 y = x; 377 } else { 378 int y1, y2; 379 ffrt::submit([&] {fib_ffrt(x - 1, y1);}, {&x}, {&y1} ); 380 ffrt::submit([&] {fib_ffrt(x - 2, y2);}, {&x}, {&y2} ); 381 ffrt::wait({&y1, &y2}); 382 y = y1 + y2; 383 } 384} 385 386int main(int narg, char** argv) 387{ 388 int r; 389 ffrt::submit([&] { fib_ffrt(10, r); }, {}, {&r}); 390 ffrt::wait({&r}); 391 std::cout << "fibonacci 10: " << r << std::endl; 392 return 0; 393} 394``` 395 396`解析`: 397 3981) 将fibonacci (x-1)和fibonacci (x-2) 作为2个Task 提交给FFRT,在两个Task 完成之后将结果累加; 399 4002) 虽然单个Task 只能拆分成2个SubTask 但是子Task 可以继续拆分,因此,整个计算图的并行度是非常高的,Task 之间在FFRT 内部形成了一颗调用树; 401 402<img src="images/image-20220926152331554.png" style="zoom:100%" /> 403 404 405### task_attr 406<hr/> 407* 定义task 的属性的辅助类,与[submit](#submit) 配合使用 408 409#### 声明 410 411```{.cpp} 412namespace ffrt { 413enum qos { 414 qos_inherit = -1, 415 qos_background, 416 qos_utility, 417 qos_default, 418 qos_user_initiated, 419}; 420 421class task_attr { 422public: 423 task_attr& qos(enum qos qos); // set qos 424 enum qos qos() const; // get qos 425 task_attr& name(const char* name); // set name 426 const char* name() const; // get name 427}; 428} 429``` 430 431#### 参数 432 433`qos` 434 435* qos 设定的枚举类型 436* inherent 是一个qos 设定策略,代表即将submit 的task 的qos 继承当前task 的qos 437 438#### 返回值 439 440* 不涉及 441 442#### 描述 443* 约定 444 * 在submit 时,如果不通过task_attr 设定qos,那么默认该提交的task的qos 为`qos_default` 445 * 在submit 时,如果通过task_attr 设定qos 为`qos_inherent`,表示将该提交的task 的qos 与当前task 的qos 相同,在FFRT task 外部提交的属性为`qos_inherent` 的task,其qos 为`qos_default` 446 * 其他情况下,该提交的task 的qos 被设定为指定的值 447* qos 级别从上到下依次递增,qos_user_interactive拥有最高优先级 448 449#### 样例 450 451```{.cpp} 452#include <iostream> 453#include "ffrt.h" 454 455int main(int narg, char** argv) 456{ 457 ffrt::submit([] { std::cout << "hello ffrt" << std::endl; }, {}, {}, 458 ffrt::task_attr().qos(ffrt::qos_background)); 459 ffrt::wait(); 460 return 0; 461} 462``` 463 464* 提交一个qos 级别为background 的任务 465 466 467 468### submit_h 469 470<hr/> 471 472* 向调度器提交一个task,与[submit](#submit) 的差别在于返回task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 473 474#### 声明 475 476```{.cpp} 477namespace ffrt { 478class task_handle { 479public: 480 task_handle(); 481 task_handle(ffrt_task_handle_t p); 482 483 task_handle(task_handle const&) = delete; 484 void operator=(task_handle const&) = delete; 485 486 task_handle(task_handle&& h); 487 task_handle& operator=(task_handle&& h); 488 489 operator void* () const; 490}; 491 492task_handle submit_h(std::function<void()>&& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {}); 493task_handle submit_h(const std::function<void()>& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {}); 494} 495``` 496 497#### 参数 498 499`func` 500 501* 同submit,详见[submit](#submit) 定义 502 503`in_deps` 504 505* 同submit,详见[submit](#submit) 定义 506 507`out_deps` 508 509* 同submit,详见[submit](#submit) 定义 510 511`attr` 512 513* 同submit,详见[submit](#submit) 定义 514 515#### 返回值 516 517* task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 518 519#### 描述 520 521* 该接口与submit 使用基本相同,从性能的角度,在不需要返回task handle 的场景,可以调用submit 接口相对于submit_h 有更好的性能 522* task_handle 可以和其他的数据depends 同时作为某个task 的in_deps,表示该task 的执行依赖task_handle 对应的task 执行完成 523* task_handle 可以和其他的数据depends 同时作为wait 的deps,表示当前任务将被suspend,直到task_handle 对应的task 执行完成后将被恢复 524* task_handle 不建议作为某个task 的out_deps,其行为是未定义的 525 526 527#### 样例 528 529```{.cpp} 530#include <iostream> 531#include "ffrt.h" 532 533int main(int narg, char** argv) 534{ 535 // handle work with submit 536 ffrt::task_handle h = ffrt::submit_h([] { std::cout << "hello "; }); // not need some data in this task 537 int x = 1; 538 ffrt::submit([&] { x++; }, {}, {&x}); 539 ffrt::submit([&] { std::cout << "world, x = " << x << std::endl; }, {&x, h}); // this task depend x and h 540 541 // handle work with wait 542 ffrt::task_handle h2 = ffrt::submit_h([&] { std::cout << "handle wait" << std::endl; x++; }); 543 ffrt::wait({h2}); 544 std::cout << "x = " << x << std::endl; 545 ffrt::wait(); 546 return 0; 547} 548``` 549 550* 预期的输出为 551 552``` 553hello world, x = 2 554handle wait 555x = 3 556``` 557 558### get_id 559 560<hr/> 561 562* 返回当前task的id标识,更多使用用于维测(原因是task name可能重名) 563 564#### 声明 565 566```{.cpp} 567namespace ffrt { 568namespace this_task { 569uint64_t get_id(); 570} 571} 572``` 573 574#### 参数 575 576* 不涉及 577 578#### 返回值 579 580* 当前task的id 581 582#### 描述 583 584* 该接口在task内部调用将返回当前task的id标识,在task外部调用将返回0 585* 可以基于该接口在task外部调用返回0的特性来区分函数是运行在FFRT 工作线程上还是非FFRT工作线程上 586* task id为从1开始编码,每提交一个task便增加1,被设计成64bit,即便是每秒百万次提交,也需要292471.2年才会发生翻转 587 588#### 样例 589 590```{.cpp} 591#include <iostream> 592#include "ffrt.h" 593 594int main(int narg, char** argv) 595{ 596 ffrt::submit([] { std::cout << "task id: " << ffrt::this_task::get_id() << std::endl; }); 597 ffrt::submit([] { std::cout <<"task id: " << ffrt::this_task::get_id() << std::endl; }); 598 ffrt::wait(); 599 std::cout << "task id: " << ffrt::this_task::get_id() << std::endl; 600 return 0; 601} 602``` 603 604* 可能的输出为: 605 606``` 607task id: 1 608task id: 2 609task id: 0 610``` 611 612### update_qos 613 614<hr/> 615 616* 更新当前正在执行的task的优先级 617 618#### 声明 619 620```{.cpp} 621namespace ffrt { 622namespace this_task { 623int update_qos(enum qos qos); 624} 625} 626``` 627 628#### 参数 629 630`qos` 631* 新的qos等级 632 633#### 返回值 634 635* 0表示成功,非0表示失败 636 637#### 描述 638 639* 该接口对当前task的qos调整会立即生效 640* 如果新设定的qos与当前的qos不一致,则会block当前task的执行,再按照新的qos恢复执行 641* 如果新设定的qos与当前的qos一致,则接口会立即返回,不做任何处理 642* **如果在非task内部调用该接口,则返回非0值,用户可以选择忽略或其他处理** 643 644#### 样例 645 646```{.cpp} 647#include <iostream> 648#include <thread> 649#include "ffrt.h" 650 651int main(int narg, char** argv) 652{ 653 ffrt::submit([] { 654 std::cout << "thread id: " << std::this_thread::get_id() << std::endl; 655 std::cout << "return " << ffrt::this_task::update_qos(ffrt::qos_user_initiated) << std::endl; 656 std::cout << "thread id: " << std::this_thread::get_id() << std::endl; 657 }); 658 ffrt::wait(); 659 std::cout << "return " << ffrt::this_task::update_qos(ffrt::qos_user_initiated) << std::endl; 660 return 0; 661} 662``` 663 664* 可能的输出为: 665 666``` 667thread id: 1024 668return 0 669thread id: 2222 670return 1 671``` 672 673 674 675## 同步原语 676 677### mutex 678<hr/> 679* FFRT提供的类似std::mutex 的性能实现 680 681#### 声明 682 683```{.cpp} 684namespace ffrt { 685class mutex { 686public: 687 mutex(mutex const &) = delete; 688 void operator =(mutex const &) = delete; 689 690 void lock(); 691 void unlock(); 692 bool try_lock(); 693}; 694} 695``` 696 697#### 参数 698 699* 不涉及 700 701#### 返回值 702 703* 不涉及 704 705#### 描述 706* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 707* 该功能能够避免传统的std::mutex 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能 708 709#### 样例 710 711```{.cpp} 712#include <iostream> 713#include "ffrt.h" 714 715void ffrt_mutex_task() 716{ 717 int sum = 0; 718 ffrt::mutex mtx; 719 for (int i = 0; i < 10; i++) { 720 ffrt::submit([&sum, i, &mtx] { 721 mtx.lock(); 722 sum = sum + i; 723 mtx.unlock(); 724 }, {}, {}); 725 } 726 ffrt::wait(); 727 std::cout << "sum = " << sum << std::endl; 728} 729 730int main(int narg, char** argv) 731{ 732 int r; 733 ffrt::submit(ffrt_mutex_task); 734 ffrt::wait(); 735 return 0; 736} 737``` 738 739预期输出为 740 741``` 742sum=45 743``` 744 745* 该例子为功能示例,实际中并不鼓励这样使用 746 747 748### condition_variable 749<hr/> 750 751* FFRT提供的类似std::condition_variable 的性能实现 752 753#### 声明 754 755```{.cpp} 756namespace ffrt { 757enum class cv_status { 758 no_timeout, 759 timeout 760}; 761 762class condition_variable { 763public: 764 using TimePoint = std::chrono::steady_clock::time_point; 765 template<typename Clock, typename Duration, typename Pred> 766 bool wait_until(std::unique_lock<mutex>& lk, 767 const std::chrono::time_point<Clock, Duration>& tp, 768 Pred&& pred) noexcept; 769 770 template<typename Clock, typename Duration> 771 cv_status wait_until(std::unique_lock<mutex>& lk, 772 const std::chrono::time_point<Clock, Duration>& tp) noexcept; 773 774 template<typename Rep, typename Period> 775 cv_status wait_for(std::unique_lock<mutex>& lk, 776 const std::chrono::duration<Rep, Period>& sleep_time) noexcept; 777 778 template<typename Rep, typename Period, typename Pred> 779 bool wait_for(std::unique_lock<mutex>& lk, 780 const std::chrono::duration<Rep, Period>& sleepTime, 781 Pred&& pred) noexcept; 782 783 void wait(std::unique_lock<mutex>& lk); 784 785 template<typename Pred> 786 void wait(std::unique_lock<mutex>& lk, Pred&& pred); 787 788 void notify_one() noexcept; 789 790 void notify_all() noexcept; 791}; 792} 793``` 794 795#### 参数 796 797`lk` 798* mutex互斥量 799`tp` 800* 等待时间 801`sleep_time` 802* 等待时间 803`pred` 804* 检查是否等待函数 805#### 返回值 806 807* 不涉及 808 809#### 描述 810* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 811* 该功能能够避免传统的std::condition_variable 在条件不满足时陷入内核的问题,在使用得当的条件下将会有更好的性能 812 813#### 样例 814 815```{.cpp} 816#include <iostream> 817#include "ffrt.h" 818 819void ffrt_cv_task() 820{ 821 ffrt::condition_variable cond; 822 int a = 0; 823 ffrt::mutex lock_; 824 ffrt::submit([&] { 825 std::unique_lock lck(lock_); 826 cond.wait(lck, [&] { return a == 1; }); 827 std::cout << "a = " << a << std::endl; 828 }, {}, {}); 829 ffrt::submit([&] { 830 std::unique_lock lck(lock_); 831 a = 1; 832 cond.notify_one(); 833 }, {}, {}); 834 835 ffrt::wait(); 836} 837 838int main(int narg, char** argv) 839{ 840 int r; 841 ffrt::submit(ffrt_cv_task); 842 ffrt::wait(); 843 return 0; 844} 845 846``` 847 848预期输出为: 849 850``` 851a=1 852``` 853 854* 该例子为功能示例,实际中并不鼓励这样使用 855 856## 杂项 857 858### sleep 859 860<hr/> 861* FFRT提供的类似std::this_thread::sleep_for / std::this_thread::sleep_until 的性能实现 862 863#### 声明 864 865```{.cpp} 866namespace ffrt { 867namespace this_task { 868template<class _Rep, class _Period> 869void sleep_for(const std::chrono::duration<_Rep, _Period>& sleep_duration); 870 871template<class _Clock, class _Duration> 872void sleep_until(const std::chrono::time_point<_Clock, _Duration>& sleep_time); 873} 874} 875``` 876 877#### 参数 878 879`sleep_duration` 880 881* 睡眠的时长 882 883`sleep_time` 884 885* 睡眠到达的时间点 886 887#### 返回值 888 889* 不涉及 890 891#### 描述 892* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 893* 该功能能够避免传统的std::this_thread::sleep_for 睡眠时陷入内核的问题,在使用得当的条件下将会有更好的性能 894* 该接口调用后实际睡眠时长不小于配置值 895 896#### 样例 897 898```{.cpp} 899#include <chrono> 900#include <iostream> 901#include "ffrt.h" 902 903using namespace std::chrono_literals; 904int main(int narg, char** argv) 905{ 906 ffrt::submit([] { 907 std::cout << "Hello waiter\n" << std::flush; 908 auto start = std::chrono::high_resolution_clock::now(); 909 ffrt::this_task::sleep_for(2000ms); 910 auto end = std::chrono::high_resolution_clock::now(); 911 std::chrono::duration<double, std::milli> elapsed = end-start; 912 std::cout << "Waited " << elapsed.count() << " ms\n"; 913 }); 914 ffrt::wait(); 915 return 0; 916} 917``` 918 919* 预期输出为 920 921``` 922Hello waiter 923Waited 2000.12 ms 924``` 925 926### yield 927<hr/> 928* 当前task 主动让出CPU 执行资源,让其他可以被执行的task 先执行,如果没有其他可被执行的task,yield 无效 929 930#### 声明 931 932```{.cpp} 933namespace ffrt { 934namespace this_task { 935void yield(); 936} 937} 938``` 939 940#### 参数 941 942* 不涉及 943 944#### 返回值 945 946* 不涉及 947 948#### 描述 949* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 950* 此函数的确切行为取决于实现,特别是使用中的FFRT 调度程序的机制和系统状态 951 952#### 样例 953 954```{.cpp} 955#include <chrono> 956#include "ffrt.h" 957 958using namespace std::chrono_literals; 959// "busy sleep" while suggesting that other tasks run 960// for a small amount of time 961void little_sleep(std::chrono::microseconds us) 962{ 963 auto start = std::chrono::high_resolution_clock::now(); 964 auto end = start + us; 965 do { 966 ffrt::this_task::yield(); 967 } while (std::chrono::high_resolution_clock::now() < end); 968} 969 970int main(int narg, char** argv) 971{ 972 ffrt::submit([] { little_sleep(200us); }); 973 ffrt::wait(); 974 return 0; 975} 976``` 977 978* 这是一个`busy sleep`,同时允许其他可以被执行的task 插入执行 979 980 981# C API 982 983> C API采用接近C11/pthread (https://zh.cppreference.com/w/c) 的命名风格,并冠以`ffrt_`前缀,以`_base`为后缀的API是内部API,通常不被用户直接调用 984> 985> **出于易用性方面的考虑,除非必要,强烈建议你使用C++ API(亦满足二进制兼容要求),调用C API将会使你的代码非常臃肿** 986 987## 任务管理 988 989### ffrt_submit_base 990 991* 该接口为ffrt动态库的导出接口,基于此可以封装出不同的C++ API ffrt::submit和C API ffrt_submit,满足二进制兼容 992 993#### 声明 994 995```{.cpp} 996const int ffrt_auto_managed_function_storage_size = 64 + sizeof(ffrt_function_header_t); 997typedef enum { 998 ffrt_function_kind_general, 999 ffrt_function_kind_queue 1000} ffrt_function_kind_t; 1001 1002void* ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_t kind); 1003 1004typedef void(*ffrt_function_t)(void*); 1005typedef struct { 1006 ffrt_function_t exec; 1007 ffrt_function_t destroy; 1008 uint64_t reserve[2]; 1009} ffrt_function_header_t; 1010 1011void ffrt_submit_base(ffrt_function_header_t* func, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr); 1012``` 1013 1014#### 参数 1015 1016`kind` 1017 1018* function子类型,用于优化内部数据结构,默认使用ffrt_function_kind_general类型 1019 1020`func` 1021 1022* CPU Function的指针,该指针执行的数据结构,按照`ffrt_function_header_t`定义的描述了该CPU Task如何执行和销毁的函数指针,FFRT通过这两个函数指针完成Task的执行和销毁 1023 1024`in_deps` 1025 1026* 同ffrt_submit 1027 1028 1029`out_deps` 1030 1031* 同ffrt_submit 1032 1033`attr` 1034 1035* 同ffrt_submit 1036 1037#### 返回值 1038 1039* 不涉及 1040 1041#### 描述 1042 1043* ffrt_submit_base不建议用户直接调用,推荐使用基于此封装的C++接口(亦满足二进制兼容) 1044* **ffrt_submit_base作为底层能力,只有在用户需要自定义task类型时使用,使用时需要满足以下限制:** 1045 * ffrt_submit_base入参中的func指针只能通过ffrt_alloc_auto_managed_function_storage_base申请,且二者的调用需一一对应 1046 * ffrt_alloc_auto_managed_function_storage_base申请的内存为ffrt_auto_managed_function_storage_size字节,其生命周期归ffrt管理,在该task结束时,由FFRT自动释放,用户无需释放 1047* ffrt_function_header_t 中定义了两个函数指针: 1048 * exec:用于描述该Task如何被执行,当FFRT需要执行该Task时由FFRT调用 1049 * destroy:用于描述该Task如何被执行,当FFRT需要执行该Task时由FFRT调用 1050 1051#### 样例 1052 1053* 通过该接口提供C++11 Lambda表达式的支持(该代码已经在ffrr.h中提供,默认支持) 1054 1055```{.cpp} 1056template<class T> 1057struct function { 1058 template<class CT> 1059 function(ffrt_function_header_t h, CT&& c) : header(h), closure(std::forward<CT>(c)) {} 1060 ffrt_function_header_t header; 1061 T closure; 1062}; 1063 1064template<class T> 1065void exec_function_wrapper(void* t) 1066{ 1067 auto f = (function<std::decay_t<T>>*)t; 1068 f->closure(); 1069} 1070 1071template<class T> 1072void destroy_function_wrapper(void* t) 1073{ 1074 auto f = (function<std::decay_t<T>>*)t; 1075 f->closure = nullptr; 1076} 1077 1078template<class T> 1079inline ffrt_function_header_t* create_function_wrapper(T&& func) 1080{ 1081 using function_type = function<std::decay_t<T>>; 1082 static_assert(sizeof(function_type) <= ffrt_auto_managed_function_storage_size, 1083 "size of function must be less than ffrt_auto_managed_function_storage_size"); 1084 1085 auto p = ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1086 auto f = new (p) function_type( 1087 {exec_function_wrapper<T>, destroy_function_wrapper<T>}, 1088 std::forward<T>(func)); 1089 return (ffrt_function_header_t*)f; 1090} 1091 1092static inline void submit(std::function<void()>&& func) 1093{ 1094 return ffrt_submit_base(create_function_wrapper(std::move(func)), NULL, NULL, NULL); 1095} 1096``` 1097 1098### ffrt_wait 1099 1100<hr/> 1101* 同步等待,与ffrt_submit 配合使用 1102* 等待指定的数据被生产完成,或等待当前任务的所有子任务完成,在不满足条件之前,当前的执行上下文被suspend,在满足条件后恢复执行 1103 1104#### 声明 1105 1106```{.cpp} 1107void ffrt_wait_deps(ffrt_deps_t* deps); 1108void ffrt_wait(); 1109``` 1110 1111#### 参数 1112 1113`deps` 1114 1115* 需要等待被生产完成的数据的虚拟地址,这些地址可能作为某些任务在submit 时的out_deps,该依赖的生成见ffrt_deps_t章节,空指针表示无依赖 1116 1117#### 返回值 1118 1119* 不涉及 1120 1121#### 描述 1122* ffrt_wait_deps(deps) 用于等待deps指代的数据被生产完成才能执行后面的代码 1123* ffrt_wait() 用于等待当前上下文提交的所有子任务(`注意:不包括孙任务和下级子任务`)都完成才能执行后面的代码 1124* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用 1125* 在FFRT task 外部调用的wait 是OS 能够感知的等待,相对于FFRT task 内部调用的wait 是更加昂贵的,因此我们希望尽可能让更多的wait 发生在FFRT task 内部 ,而不是FFRT task 外部 1126 1127#### 样例 1128 1129**recursive fibonacci** 1130 1131串行版的fibonacci 可以实现为: 1132 1133```{.c} 1134#include <stdio.h> 1135 1136void fib(int x, int* y) { 1137 if (x <= 1) { 1138 *y = x; 1139 } else { 1140 int y1, y2; 1141 fib(x - 1, &y1); 1142 fib(x - 2, &y2); 1143 *y = y1 + y2; 1144 } 1145} 1146int main(int narg, char** argv) 1147{ 1148 int r; 1149 fib(10, &r); 1150 printf("fibonacci 10: %d\n", r); 1151 return 0; 1152} 1153``` 1154 1155若要使用 FFRT 实现并行(注,对于单纯的fibonacci,单个 Task 的计算量极小,不具有并行加速的意义,但这种调用pattern 对并行编程模型的灵活性考验是非常高的),其中1种可行的实现为: 1156 1157```{.c} 1158#include <stdio.h> 1159#include "ffrt.h" 1160 1161typedef struct { 1162 int x; 1163 int* y; 1164} fib_ffrt_s; 1165 1166typedef struct { 1167 ffrt_function_header_t header; 1168 ffrt_function_t func; 1169 ffrt_function_t after_func; 1170 void* arg; 1171} c_function; 1172 1173static void ffrt_exec_function_wrapper(void* t) 1174{ 1175 c_function* f = (c_function*)t; 1176 if (f->func) { 1177 f->func(f->arg); 1178 } 1179} 1180 1181static void ffrt_destroy_function_wrapper(void* t) 1182{ 1183 c_function* f = (c_function*)t; 1184 if (f->after_func) { 1185 f->after_func(f->arg); 1186 } 1187} 1188 1189#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1190static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1191 const ffrt_function_t after_func, void* arg) 1192{ 1193 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1194 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1195 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1196 f->header.exec = ffrt_exec_function_wrapper; 1197 f->header.destroy = ffrt_destroy_function_wrapper; 1198 f->func = func; 1199 f->after_func = after_func; 1200 f->arg = arg; 1201 return (ffrt_function_header_t*)f; 1202} 1203 1204static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1205 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1206{ 1207 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1208} 1209 1210#define ffrt_deps_define(name, dep1, ...) const void* __v_##name[] = {dep1, ##__VA_ARGS__}; \ 1211 ffrt_deps_t name = {sizeof(__v_##name) / sizeof(void*), __v_##name} 1212 1213void fib_ffrt(void* arg) 1214{ 1215 fib_ffrt_s* p = (fib_ffrt_s*)arg; 1216 int x = p->x; 1217 int* y = p->y; 1218 1219 if (x <= 1) { 1220 *y = x; 1221 } else { 1222 int y1, y2; 1223 fib_ffrt_s s1 = {x - 1, &y1}; 1224 fib_ffrt_s s2 = {x - 2, &y2}; 1225 ffrt_deps_define(dx, &x); 1226 ffrt_deps_define(dy1, &y1); 1227 ffrt_deps_define(dy2, &y2); 1228 ffrt_deps_define(dy12, &y1, &y2); 1229 ffrt_submit_c(fib_ffrt, NULL, &s1, &dx, &dy1, NULL); 1230 ffrt_submit_c(fib_ffrt, NULL, &s2, &dx, &dy2, NULL); 1231 ffrt_wait_deps(&dy12); 1232 *y = y1 + y2; 1233 } 1234} 1235 1236int main(int narg, char** argv) 1237{ 1238 int r; 1239 fib_ffrt_s s = {10, &r}; 1240 ffrt_deps_define(dr, &r); 1241 ffrt_submit_c(fib_ffrt, NULL, &s, NULL, &dr, NULL); 1242 ffrt_wait_deps(&dr); 1243 printf("fibonacci 10: %d\n", r); 1244 return 0; 1245} 1246``` 1247 1248`解析`: 1249 12501) 将fibonacci (x-1)和fibonacci (x-2) 作为2个Task 提交给FFRT,在两个Task 完成之后将结果累加; 1251 12522) 虽然单个Task 只能拆分成2个SubTask 但是子Task 可以继续拆分,因此,整个计算图的并行度是非常高的,Task 之间在FFRT 内部形成了一颗调用树; 1253 1254<img src="images/image-20220926152331554.png" style="zoom:100%" /> 1255 1256> 以上实现,逻辑上虽与C++ API中的实现类似,但是用户显式管理数据生命周期和函数入参打包两个因素将使代码异常复杂 1257 1258 1259 1260### ffrt_deps_t 1261 1262* C API中对依赖数组的抽象,逻辑上等同于C++ API中的`std::vector<void*>` 1263 1264#### 声明 1265 1266```{.cpp} 1267typedef struct { 1268 uint32_t len; 1269 const void* const * items; 1270} ffrt_deps_t; 1271``` 1272 1273#### 参数 1274 1275`len` 1276 1277* 所依赖的Signature的个数,取值大于等于0 1278 1279`item` 1280 1281* len个Signature的起始地址指针 1282 1283#### 返回值 1284 1285* 不涉及 1286 1287#### 描述 1288 1289* item为len个Signature的起始指针,该指针可以指向堆空间,也可以指向栈空间,但是要求分配的空间大于等于len * sizeof(void*) 1290 1291#### 样例 1292 1293* item指向栈空间的ffrt_deps_t 1294 1295```{.c} 1296#include "ffrt.h" 1297 1298int main(int narg, char** argv) 1299{ 1300 int x1 = 1; 1301 int x2 = 2; 1302 1303 void *t[] = {&x1, &x2}; 1304 ffrt_deps_t deps = {2, (const void* const *)&t}; 1305 // some code use deps 1306 return 0; 1307} 1308``` 1309 1310* item指向栈空间的ffrt_deps_t 1311 1312```{.c} 1313#include <stdlib.h> 1314#include "ffrt.h" 1315 1316int main(int narg, char** argv) 1317{ 1318 int x1 = 1; 1319 int x2 = 2; 1320 1321 void** t = (void**)malloc(sizeof(void*) * 2); 1322 t[0]= &x1; 1323 t[1]= &x2; 1324 ffrt_deps_t deps = {2, t}; 1325 1326 // some code use deps 1327 free(t); 1328 return 0; 1329} 1330``` 1331 1332### ffrt_task_attr_t 1333 1334<hr/> 1335* 定义task 的属性的辅助类,与ffrt_submit 配合使用 1336 1337#### 声明 1338 1339```{.c} 1340typedef enum { 1341 ffrt_qos_inherent = -1, 1342 ffrt_qos_background, 1343 ffrt_qos_utility, 1344 ffrt_qos_default, 1345 ffrt_qos_user_initiated, 1346} ffrt_qos_t; 1347 1348typedef struct { 1349 char storage[ffrt_task_attr_storage_size]; 1350} ffrt_task_attr_t; 1351typedef void* ffrt_task_handle_t; 1352 1353int ffrt_task_attr_init(ffrt_task_attr_t* attr); 1354void ffrt_task_attr_destroy(ffrt_task_attr_t* attr); 1355void ffrt_task_attr_set_qos(ffrt_task_attr_t* attr, ffrt_qos_t qos); 1356ffrt_qos_t ffrt_task_attr_get_qos(const ffrt_task_attr_t* attr); 1357void ffrt_task_attr_set_name(ffrt_task_attr_t* attr, const char* name); 1358const char* ffrt_task_attr_get_name(const ffrt_task_attr_t* attr); 1359``` 1360 1361#### 参数 1362 1363`attr` 1364 1365* 创建的tasks属性的句柄 1366 1367`qos` 1368 1369* qos 设定的枚举类型 1370* inherent 是一个qos 设定策略,代表即将ffrt_submit 的task 的qos 继承当前task 的qos 1371 1372#### 返回值 1373 1374* 不涉及 1375 1376#### 描述 1377* `attr`所传递的内容会在ffrt_submit内部完成取存,ffrt_submit返回后用户即可销毁 1378* 约定 1379 * 在submit 时,如果不通过task_attr 设定qos,那么默认该提交的task的qos 为`ffrt_qos_default` 1380 * 在submit 时,如果通过task_attr 设定qos 为`ffrt_qos_inherent`,表示将该提交的task 的qos 与当前task 的qos 相同,在FFRT task 外部提交的属性为`ffrt_qos_inherent` 的task,其qos 为`ffrt_qos_default` 1381 * 其他情况下,该提交的task 的qos 被设定为指定的值 1382* 在`ffrt_task_attr_destroy`之后再对task_attr进行访问,其行为是未定义的 1383 1384#### 样例 1385 1386```{.c} 1387#include <stdio.h> 1388#include "ffrt.h" 1389 1390void my_print(void* arg) 1391{ 1392 printf("hello ffrt\n"); 1393} 1394 1395typedef struct { 1396 ffrt_function_header_t header; 1397 ffrt_function_t func; 1398 ffrt_function_t after_func; 1399 void* arg; 1400} c_function; 1401 1402static void ffrt_exec_function_wrapper(void* t) 1403{ 1404 c_function* f = (c_function*)t; 1405 if (f->func) { 1406 f->func(f->arg); 1407 } 1408} 1409 1410static void ffrt_destroy_function_wrapper(void* t) 1411{ 1412 c_function* f = (c_function*)t; 1413 if (f->after_func) { 1414 f->after_func(f->arg); 1415 } 1416} 1417 1418#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1419static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1420 const ffrt_function_t after_func, void* arg) 1421{ 1422 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1423 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1424 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1425 f->header.exec = ffrt_exec_function_wrapper; 1426 f->header.destroy = ffrt_destroy_function_wrapper; 1427 f->func = func; 1428 f->after_func = after_func; 1429 f->arg = arg; 1430 return (ffrt_function_header_t*)f; 1431} 1432 1433static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1434 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1435{ 1436 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1437} 1438 1439int main(int narg, char** argv) 1440{ 1441 ffrt_task_attr_t attr; 1442 ffrt_task_attr_init(&attr); 1443 ffrt_task_attr_set_qos(&attr, ffrt_qos_background); 1444 ffrt_submit_c(my_print, NULL, NULL, NULL, NULL, &attr); 1445 ffrt_task_attr_destroy(&attr); 1446 ffrt_wait(); 1447 return 0; 1448} 1449``` 1450 1451* 提交一个qos 级别为background 的任务 1452 1453 1454 1455### ffrt_submit_h 1456 1457<hr/> 1458 1459* 向调度器提交一个task,与ffrt_submit 的差别在于返回task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 1460 1461#### 声明 1462 1463```{.cpp} 1464typedef void* ffrt_task_handle_t; 1465 1466ffrt_task_handle_t ffrt_submit_h(ffrt_function_t func, void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr); 1467void ffrt_task_handle_destroy(ffrt_task_handle_t handle); 1468``` 1469 1470#### 参数 1471 1472`func` 1473 1474* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1475 1476`in_deps` 1477 1478* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1479 1480`out_deps` 1481 1482* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1483 1484`attr` 1485 1486* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1487 1488#### 返回值 1489 1490* task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 1491 1492#### 描述 1493 1494* C API中的ffrt_task_handle_t的使用与C++ API中的ffrt::task_handle相同 1495* **差异在于:C API中的ffrt_task_handle_t需要用户调用`ffrt_task_handle_destroy`显式销毁,而C++ API无需该操作** 1496 1497 1498#### 样例 1499 1500```{.c} 1501#include <stdio.h> 1502#include "ffrt.h" 1503 1504void func0(void* arg) 1505{ 1506 printf("hello "); 1507} 1508 1509void func1(void* arg) 1510{ 1511 (*(int*)arg)++; 1512} 1513 1514void func2(void* arg) 1515{ 1516 printf("world, x = %d\n", *(int*)arg); 1517} 1518 1519void func3(void* arg) 1520{ 1521 printf("handle wait"); 1522 (*(int*)arg)++; 1523} 1524 1525typedef struct { 1526 ffrt_function_header_t header; 1527 ffrt_function_t func; 1528 ffrt_function_t after_func; 1529 void* arg; 1530} c_function; 1531 1532static void ffrt_exec_function_wrapper(void* t) 1533{ 1534 c_function* f = (c_function*)t; 1535 if (f->func) { 1536 f->func(f->arg); 1537 } 1538} 1539 1540static void ffrt_destroy_function_wrapper(void* t) 1541{ 1542 c_function* f = (c_function*)t; 1543 if (f->after_func) { 1544 f->after_func(f->arg); 1545 } 1546} 1547 1548#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1549static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1550 const ffrt_function_t after_func, void* arg) 1551{ 1552 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1553 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1554 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1555 f->header.exec = ffrt_exec_function_wrapper; 1556 f->header.destroy = ffrt_destroy_function_wrapper; 1557 f->func = func; 1558 f->after_func = after_func; 1559 f->arg = arg; 1560 return (ffrt_function_header_t*)f; 1561} 1562 1563static inline ffrt_task_handle_t ffrt_submit_h_c(ffrt_function_t func, const ffrt_function_t after_func, 1564 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1565{ 1566 return ffrt_submit_h_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1567} 1568 1569static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1570 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1571{ 1572 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1573} 1574 1575#define ffrt_deps_define(name, dep1, ...) const void* __v_##name[] = {dep1, ##__VA_ARGS__}; \ 1576 ffrt_deps_t name = {sizeof(__v_##name) / sizeof(void*), __v_##name} 1577 1578int main(int narg, char** argv) 1579{ 1580 // handle work with submit 1581 ffrt_task_handle_t h = ffrt_submit_h_c(func0, NULL, NULL, NULL, NULL, NULL); // not need some data in this task 1582 int x = 1; 1583 ffrt_deps_define(d1, &x); 1584 ffrt_deps_define(d2, &x, h); 1585 ffrt_submit_c(func1, NULL, &x, NULL, &d1, NULL); 1586 ffrt_submit_c(func2, NULL, &x, &d2, NULL, NULL); // this task depend x and h 1587 ffrt_task_handle_destroy(h); 1588 1589 // handle work with wait 1590 ffrt_task_handle_t h2 = ffrt_submit_h_c(func3, NULL, &x, NULL, NULL, NULL); 1591 ffrt_deps_define(d3, h2); 1592 ffrt_wait_deps(&d3); 1593 ffrt_task_handle_destroy(h2); 1594 printf("x = %d", x); 1595 ffrt_wait(); 1596 return 0; 1597} 1598``` 1599 1600* 预期的输出为 1601 1602``` 1603hello world, x = 2 1604handle wait 1605x = 3 1606``` 1607 1608 1609 1610### ffrt_this_task_get_id 1611 1612<hr/> 1613 1614* 返回当前task的id标识,更多使用用于维测(原因是task name可能重名) 1615 1616#### 声明 1617 1618```{.c} 1619uint64_t ffrt_this_task_get_id(); 1620``` 1621 1622#### 参数 1623 1624* 不涉及 1625 1626#### 返回值 1627 1628* 当前task的id 1629 1630#### 描述 1631 1632* 该接口在task内部调用将返回当前task的id标识,在task外部调用将返回0 1633* 可以基于该接口在task外部调用返回0的特性来区分函数是运行在FFRT 工作线程上还是非FFRT工作线程上 1634* task id为从1开始编码,每提交一个task便增加1,被设计成64bit,即便是每秒百万次提交,也需要292471.2年才会发生翻转 1635 1636#### 样例 1637 1638* 忽略 1639 1640 1641 1642### ffrt_this_task_update_qos 1643 1644<hr/> 1645 1646* 更新当前正在执行的task的优先级 1647 1648#### 声明 1649 1650```{.cpp} 1651int ffrt_this_task_update_qos(ffrt_qos_t qos); 1652``` 1653 1654#### 参数 1655 1656* `qos` 新的优先级 1657 1658#### 返回值 1659 1660* 0表示成功,非0表示失败 1661 1662#### 描述 1663 1664* 该接口对当前task的qos调整会立即生效 1665* 如果新设定的qos与当前的qos不一致,则会block当前task的执行,再按照新的qos恢复执行 1666* 如果新设定的qos与当前的qos一致,则接口会立即返回0,不做任何处理 1667* **如果在非task内部调用该接口,则返回非0值,用户可以选择忽略或其他处理** 1668 1669#### 样例 1670 1671* 忽略 1672 1673 1674 1675## 同步原语 1676 1677### ffrt_mutex_t 1678<hr/> 1679* FFRT提供的类似pthread mutex 的性能实现 1680 1681#### 声明 1682 1683```{.cpp} 1684typedef enum { 1685 ffrt_error = -1, 1686 ffrt_success = 0, 1687 ffrt_error_nomem = ENOMEM, 1688 ffrt_error_timedout = ETIMEDOUT, 1689 ffrt_error_busy = EBUSY, 1690 ffrt_error_inval = EINVAL 1691} ffrt_error_t; 1692 1693struct ffrt_mutex_t; 1694 1695int ffrt_mutex_init(ffrt_mutex_t* mutex, const ffrt_mutexattr_t* attr); 1696int ffrt_mutex_lock(ffrt_mutex_t* mutex); 1697int ffrt_mutex_unlock(ffrt_mutex_t* mutex); 1698int ffrt_mutex_trylock(ffrt_mutex_t* mutex); 1699int ffrt_mutex_destroy(ffrt_mutex_t* mutex); 1700``` 1701 1702#### 参数 1703 1704`attr` 1705 1706* 当前FFRT只支持基础类型的mutex,因此attr必须为空指针 1707 1708`mutex` 1709 1710* 指向所操作的互斥锁的指针 1711 1712#### 返回值 1713 1714* 若成功则为 ffrt_success ,否则发生错误 1715 1716#### 描述 1717* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 1718* 该功能能够避免pthread传统的pthread_mutex_t 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能 1719* **注意:目前暂不支持递归和定时功能** 1720* **注意:C API中的ffrt_mutex_t需要用户调用`ffrt_mutex_init`和`ffrt_mutex_destroy`显式创建和销毁,而C++ API无需该操作** 1721 1722#### 样例 1723 1724```{.c} 1725#include <stdio.h> 1726#include "ffrt.h" 1727 1728typedef struct { 1729 int* sum; 1730 ffrt_mutex_t* mtx; 1731} tuple; 1732 1733void func(void* arg) 1734{ 1735 tuple* t = (tuple*)arg; 1736 1737 int ret = ffrt_mutex_lock(t->mtx); 1738 if (ret != ffrt_success) { 1739 printf("error\n"); 1740 } 1741 (*t->sum)++; 1742 ret = ffrt_mutex_unlock(t->mtx); 1743 if (ret != ffrt_success) { 1744 printf("error\n"); 1745 } 1746} 1747 1748typedef struct { 1749 ffrt_function_header_t header; 1750 ffrt_function_t func; 1751 ffrt_function_t after_func; 1752 void* arg; 1753} c_function; 1754 1755static void ffrt_exec_function_wrapper(void* t) 1756{ 1757 c_function* f = (c_function*)t; 1758 if (f->func) { 1759 f->func(f->arg); 1760 } 1761} 1762 1763static void ffrt_destroy_function_wrapper(void* t) 1764{ 1765 c_function* f = (c_function*)t; 1766 if (f->after_func) { 1767 f->after_func(f->arg); 1768 } 1769} 1770 1771#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1772static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1773 const ffrt_function_t after_func, void* arg) 1774{ 1775 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1776 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1777 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1778 f->header.exec = ffrt_exec_function_wrapper; 1779 f->header.destroy = ffrt_destroy_function_wrapper; 1780 f->func = func; 1781 f->after_func = after_func; 1782 f->arg = arg; 1783 return (ffrt_function_header_t*)f; 1784} 1785 1786static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1787 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1788{ 1789 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1790} 1791 1792void ffrt_mutex_task() 1793{ 1794 int sum = 0; 1795 ffrt_mutex_t mtx; 1796 tuple t = {&sum, &mtx}; 1797 int ret = ffrt_mutex_init(&mtx, NULL); 1798 if (ret != ffrt_success) { 1799 printf("error\n"); 1800 } 1801 for (int i = 0; i < 10; i++) { 1802 ffrt_submit_c(func, NULL, &t, NULL, NULL, NULL); 1803 } 1804 ffrt_mutex_destroy(&mtx); 1805 ffrt_wait(); 1806 printf("sum = %d", sum); 1807} 1808 1809int main(int narg, char** argv) 1810{ 1811 int r; 1812 ffrt_submit_c(ffrt_mutex_task, NULL, NULL, NULL, NULL, NULL); 1813 ffrt_wait(); 1814 return 0; 1815} 1816``` 1817 1818预期输出为 1819 1820``` 1821sum=10 1822``` 1823 1824* 该例子为功能示例,实际中并不鼓励这样使用 1825 1826 1827### ffrt_cond_t 1828<hr/> 1829 1830* FFRT提供的类似pthread 信号量的性能实现 1831 1832#### 声明 1833 1834```{.c} 1835typedef enum { 1836 ffrt_error = -1, 1837 ffrt_success = 0, 1838 ffrt_error_nomem = ENOMEM, 1839 ffrt_error_timedout = ETIMEDOUT, 1840 ffrt_error_busy = EBUSY, 1841 ffrt_error_inval = EINVAL 1842} ffrt_error_t; 1843 1844struct ffrt_cond_t; 1845typedef enum { 1846 ffrt_clock_realtime = CLOCK_REALTIME, 1847 ffrt_clock_monotonic = CLOCK_MONOTONIC 1848} ffrt_clockid_t; 1849 1850int ffrt_condattr_init(ffrt_condattr_t* attr); 1851int ffrt_condattr_destroy(ffrt_condattr_t* attr); 1852int ffrt_condattr_setclock(ffrt_condattr_t* attr, ffrt_clockid_t clock); 1853int ffrt_condattr_getclock(const ffrt_condattr_t* attr, ffrt_clockid_t* clock); 1854 1855int ffrt_cond_init(ffrt_cond_t* cond, const ffrt_condattr_t* attr); 1856int ffrt_cond_signal(ffrt_cond_t* cond); 1857int ffrt_cond_broadcast(ffrt_cond_t* cond); 1858int ffrt_cond_wait(ffrt_cond_t*cond, ffrt_mutex_t* mutex); 1859int ffrt_cond_timedwait(ffrt_cond_t* cond, ffrt_mutex_t* mutex, const struct timespec* time_point); 1860int ffrt_cond_destroy(ffrt_cond_t* cond); 1861``` 1862 1863#### 参数 1864 1865`cond` 1866 1867* 指向所操作的信号量的指针 1868 1869`attr` 1870 1871* 属性设定,空指针表示使用默认属性 1872 1873`mutex` 1874 1875* 指向要在阻塞期间解锁的互斥锁的指针 1876 1877`time_point` 1878 1879* 指向指定等待时限时间的对象的指针 1880 1881 1882#### 返回值 1883 1884* 若成功则为 ffrt_success,若在锁定互斥前抵达时限则为 ffrt_error_timedout 1885 1886#### 描述 1887* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 1888* 该功能能够避免传统的pthread_cond_t在条件不满足时陷入内核的问题,在使用得当的条件下将会有更好的性能 1889* **注意:C API中的ffrt_cond_t需要用户调用`ffrt_cond_init`和`ffrt_cond_destroy`显式创建和销毁,而C++ API中依赖构造和析构自动完成** 1890 1891#### 样例 1892 1893```{.c} 1894#include <stdio.h> 1895#include "ffrt.h" 1896 1897typedef struct { 1898 ffrt_cond_t* cond; 1899 int* a; 1900 ffrt_mutex_t* lock_; 1901} tuple; 1902 1903void func1(void* arg) 1904{ 1905 tuple* t = (tuple*)arg; 1906 int ret = ffrt_mutex_lock(t->lock_); 1907 if (ret != ffrt_success) { 1908 printf("error\n"); 1909 } 1910 while (*t->a != 1) { 1911 ret = ffrt_cond_wait(t->cond, t->lock_); 1912 if (ret != ffrt_success) { 1913 printf("error\n"); 1914 } 1915 } 1916 ret = ffrt_mutex_unlock(t->lock_); 1917 if (ret != ffrt_success) { 1918 printf("error\n"); 1919 } 1920 printf("a = %d", *(t->a)); 1921} 1922 1923void func2(void* arg) 1924{ 1925 tuple* t = (tuple*)arg; 1926 int ret = ffrt_mutex_lock(t->lock_); 1927 if (ret != ffrt_success) { 1928 printf("error\n"); 1929 } 1930 *(t->a) = 1; 1931 ret = ffrt_cond_signal(t->cond); 1932 if (ret != ffrt_success) { 1933 printf("error\n"); 1934 } 1935 ret = ffrt_mutex_unlock(t->lock_); 1936 if (ret != ffrt_success) { 1937 printf("error\n"); 1938 } 1939} 1940 1941typedef struct { 1942 ffrt_function_header_t header; 1943 ffrt_function_t func; 1944 ffrt_function_t after_func; 1945 void* arg; 1946} c_function; 1947 1948static void ffrt_exec_function_wrapper(void* t) 1949{ 1950 c_function* f = (c_function*)t; 1951 if (f->func) { 1952 f->func(f->arg); 1953 } 1954} 1955 1956static void ffrt_destroy_function_wrapper(void* t) 1957{ 1958 c_function* f = (c_function*)t; 1959 if (f->after_func) { 1960 f->after_func(f->arg); 1961 } 1962} 1963 1964#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1965static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1966 const ffrt_function_t after_func, void* arg) 1967{ 1968 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1969 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1970 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1971 f->header.exec = ffrt_exec_function_wrapper; 1972 f->header.destroy = ffrt_destroy_function_wrapper; 1973 f->func = func; 1974 f->after_func = after_func; 1975 f->arg = arg; 1976 return (ffrt_function_header_t*)f; 1977} 1978 1979static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1980 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1981{ 1982 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1983} 1984 1985void ffrt_cv_task() 1986{ 1987 ffrt_cond_t cond; 1988 int ret = ffrt_cond_init(&cond, NULL); 1989 if (ret != ffrt_success) { 1990 printf("error\n"); 1991 } 1992 int a = 0; 1993 ffrt_mutex_t lock_; 1994 tuple t = {&cond, &a, &lock_}; 1995 ret = ffrt_mutex_init(&lock_, NULL); 1996 if (ret != ffrt_success) { 1997 printf("error\n"); 1998 } 1999 ffrt_submit_c(func1, NULL, &t, NULL, NULL, NULL); 2000 ffrt_submit_c(func2, NULL, &t, NULL, NULL, NULL); 2001 ffrt_wait(); 2002 ffrt_cond_destroy(&cond); 2003 ffrt_mutex_destroy(&lock_); 2004} 2005 2006int main(int narg, char** argv) 2007{ 2008 ffrt_submit_c(ffrt_cv_task, NULL, NULL, NULL, NULL, NULL); 2009 ffrt_wait(); 2010 return 0; 2011} 2012``` 2013 2014预期输出为: 2015 2016``` 2017a=1 2018``` 2019 2020* 该例子为功能示例,实际中并不鼓励这样使用 2021 2022## 杂项 2023 2024### ffrt_usleep 2025 2026<hr/> 2027* FFRT提供的类似C11 sleep和linux usleep的性能实现 2028 2029#### 声明 2030 2031```{.c} 2032int ffrt_usleep(uint64_t usec); 2033``` 2034 2035#### 参数 2036 2037`usec` 2038 2039* 睡眠的us数 2040 2041#### 返回值 2042 2043* 不涉及 2044 2045#### 描述 2046* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 2047* 该功能能够避免传统的sleep 睡眠时陷入内核的问题,在使用得当的条件下将会有更好的性能 2048 2049#### 样例 2050 2051```{.c} 2052#include <time.h> 2053#include <stdio.h> 2054#include "ffrt.h" 2055 2056void func(void* arg) 2057{ 2058 printf("Time: %s", ctime(&(time_t){time(NULL)})); 2059 ffrt_usleep(2000000); // 睡眠 2 秒 2060 printf("Time: %s", ctime(&(time_t){time(NULL)})); 2061} 2062 2063typedef struct { 2064 ffrt_function_header_t header; 2065 ffrt_function_t func; 2066 ffrt_function_t after_func; 2067 void* arg; 2068} c_function; 2069 2070static void ffrt_exec_function_wrapper(void* t) 2071{ 2072 c_function* f = (c_function*)t; 2073 if (f->func) { 2074 f->func(f->arg); 2075 } 2076} 2077 2078static void ffrt_destroy_function_wrapper(void* t) 2079{ 2080 c_function* f = (c_function*)t; 2081 if (f->after_func) { 2082 f->after_func(f->arg); 2083 } 2084} 2085 2086#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 2087static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 2088 const ffrt_function_t after_func, void* arg) 2089{ 2090 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 2091 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 2092 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 2093 f->header.exec = ffrt_exec_function_wrapper; 2094 f->header.destroy = ffrt_destroy_function_wrapper; 2095 f->func = func; 2096 f->after_func = after_func; 2097 f->arg = arg; 2098 return (ffrt_function_header_t*)f; 2099} 2100 2101static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 2102 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 2103{ 2104 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 2105} 2106 2107int main(int narg, char** argv) 2108{ 2109 ffrt_submit_c(func, NULL, NULL, NULL, NULL, NULL); 2110 ffrt_wait(); 2111 return 0; 2112} 2113``` 2114 2115### ffrt_yield 2116<hr/> 2117* 当前task 主动让出CPU 执行资源,让其他可以被执行的task 先执行,如果没有其他可被执行的task,yield 无效 2118 2119#### 声明 2120 2121```{.cpp} 2122void ffrt_yield(); 2123``` 2124 2125#### 参数 2126 2127* 不涉及 2128 2129#### 返回值 2130 2131* 不涉及 2132 2133#### 描述 2134* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 2135* 此函数的确切行为取决于实现,特别是使用中的FFRT 调度程序的机制和系统状态 2136 2137#### 样例 2138 2139* 省略 2140 2141 2142# 部署 2143 2144## 部署方式 2145<img src="images/image-20230120153923679.png" alt="image-20230120153923679" style="zoom:67%;" /> 2146 2147* FFRT的部署依赖FFRT动态库libffrt.so和一组header头文件 2148 2149* FFRT的头文件为`ffrt.h`,内部包含了C++ API,C API和C base API 2150 * ffrt.h 定义为: 2151 ```{.cpp} 2152 #ifndef FFRT_API_FFRT_H 2153 #define FFRT_API_FFRT_H 2154 #ifdef __cplusplus 2155 #include "cpp/task.h" 2156 #include "cpp/deadline.h" 2157 #include "cpp/sys_event.h" 2158 #include "cpp/mutex.h" 2159 #include "cpp/condition_variable.h" 2160 #include "cpp/sleep.h" 2161 #include "cpp/thread.h" 2162 #include "cpp/config.h" 2163 #include "cpp/future.h" 2164 #else 2165 #include "c/task.h" 2166 #include "c/deadline.h" 2167 #include "c/sys_event.h" 2168 #include "c/mutex.h" 2169 #include "c/condition_variable.h" 2170 #include "c/sleep.h" 2171 #include "c/thread.h" 2172 #include "c/config.h" 2173 #endif 2174 #endif 2175 ``` 2176 * C base API定义示例: 2177 ```{.cpp} 2178 void ffrt_submit_base(ffrt_function_header_t* func, ...); 2179 int ffrt_mutex_init(...); 2180 ``` 2181 * C API定义示例: 2182 ```{.cpp} 2183 static inline void ffrt_submit(ffrt_function_t func, void* arg, ...) 2184 { 2185 ffrt_submit_base(ffrt_create_function_wrapper(func, arg), ...); 2186 } 2187 ``` 2188 * C++ API定义示例: 2189 ```{.cpp} 2190 namespace ffrt { 2191 static inline void submit(std::function& func, ...) 2192 { 2193 ffrt_submit_base(ffrt_create_function_wrapper(func), ...); 2194 } 2195 struct mutex { 2196 mutex() { 2197 ffrt_mutex_init(...); 2198 ... 2199 }; 2200 } 2201 ``` 2202 2203* **出于易用性方面的考虑,除非必要,强烈建议你使用C++ API,调用C API将会使你的代码非常臃肿或者更容易产生资源未释放问题** 2204 2205| 需求列表 | 2206| ------------------------------------------------------------ | 2207| 需求1:ABI兼容性,在NDK场景中由于用户的编译环境与FFRT的编译环境不同,使用C++接口可能存在ABI兼容性问题,要有解决方案 | 2208| 需求2:用户的编译环境为纯C编译环境,不想因为引入FFRT而引入C++元素的场景,要有解决方案 | 2209| 需求3:易用性,尽可能让接口简单易用,用户少出错 | 2210 2211* 对于需求1,通过在用户调用的C++接口和FFRT的实现之间增加一个C base API层,并基于头文件方式将API中的C++的元素编译到用户的so,从而解决ABI兼容的问题 2212* 对于需求2,可以通过C Base API解决 2213* 对于需求3,建议用户尽可能使用C++ API,以避免C API固有的资源未初始化/释放、参数冗长等问题,对于不得不使用C API的场景,FFRT仍然支持用户使用C API和C base API 2214 2215 2216 2217<br/> 2218<br/> 2219 2220<hr/> 2221# 实战指南 2222 2223## 步骤1: 分析应用 2224 2225使用 FFRT 并行编程的第一步便是您需要了解你的应用。 2226 2227【建议1】:使用 Task 梳理应用的流程。 2228 2229使用 Task 梳理应用的流程,并且尽可能使用数据来表达 Task 之间的依赖。当然如果两个 Task 之间如无数据依赖,仅存在控制依赖,您也可以创建一个虚拟的(或者逻辑上的)数据依赖。 2230 2231<img src="images/image-20220926152831526.png" style="zoom:70%" /> 2232 2233<center>AIRAW 的数据流图</center> 2234 2235基于数据流图,可以很容易判定出哪些 Task 是可以并发的,比如,Slice0 的 NPU Task 和 Slice1 的 GPU Pre Task 是可以并发的,因为它们没有任何依赖。 2236 2237反过来,如果并发的效果不理想,也可以通过调整数据流图来优化并发。例如,假如上图中GPU Pre Task 执行时间有很大波动,但平均耗时略小于 NPU Task,会出现某些时刻 GPU Pre Task 拖慢整个执行时间。此时,如果将 GPU Pre Task 的输出 Buffer 改成3个(或者更多)的 Buffer ,可以增加 GPU Pre Task 和 NPU Task 的并发机会,将降低波动对总执行时间的影响。 2238 2239 2240 2241【建议2】:这里不用太担心 Task 大或小的问题,因为 FFRT 允许你在 Task 内部继续拆分 SubTask,可以逐步细化。 2242 2243下图中,第一次画数据流图时,可以不将 FaceDirection 和 UpdateExistFaceImageInfo 两个 Task 展开,可以逐步细化。 2244 2245<img src="images/image-20220926153003884.png" style="zoom:70%" /> 2246 2247<center>某拍照业务的数据流图</center> 2248 2249【建议3】:上述流程图或者数据流图不要求是静态图(即 Task 数量和 Task 依赖关系是固定的) 2250 2251FFRT 允许动态提交 Task ,在编程界面上不体现图的概念,FFRT 内部会根据Task 之间的依赖关系动态调整数据流图的节点。 2252 2253 2254【建议4】:尽可能对应用做热点分析 2255 2256如果是对存量代码的 FFRT 化改造,那么,使用 System Trace 这类工具能帮助您聚焦在性能热点上,比如下图可以很容易知道当前的性能Bound,在分析数据流图时,可以重点关注这些热点任务。 2257 2258<img src="images/image-20220926153030993.png" style="zoom:70%" /> 2259 2260<center>某业务的System Trace</center> 2261 2262## 步骤2: 并行化应用 2263 2264【建议1】:不要直接使用线程,使用 FFRT 提交Task。 2265 2266如果应用中有明显的数据依赖关系,那么 FFRT 将会非常适合;最差的情况是应用没有数据依赖或难以并行(如果真的存在),您仍然可以把 FFRT 当做一个高效的进程级线程池、或者协程库去使用它,但非常不建议你继续创建线程。 2267 2268 2269 2270【建议2】:Task 最好被建模为纯函数。 2271 2272纯函数是指其执行没有副作用,例如更新全局数据结构。每个任务都依赖于其输入/输出签名来连接到其他任务。 2273 2274请注意,即使 Task 不是"纯"的,FFRT 仍然适用。只要任务使用的数据依赖或者锁足以保证正确执行,FFRT 就能正常工作。 2275 2276 2277 2278【建议3】:尽可能尝试通过 inDeps/outDeps 表达依赖,而不是使用 ffrt::wait()。 2279 2280这是因为 FFRT 跟踪和处理 inDeps/outDeps 比调用显式 ffrt::wait() 函数更自然、更便宜。 2281 2282 2283 2284【建议4】:注意 Task 粒度 2285 2286以适当的粒度提交任务至关重要:目前每个任务的调度开销约为 10 us。如果 Task 的粒度非常小,那么开销的百分比将会很高。FFRT 会继续基于软硬件的方式优化调度开销。 2287 2288 2289 2290【建议5】:尽可能使用 FFRT 原语 2291 2292如果需要mutex、sleep、异步 I/O,请使用 FFRT 原语,而不是使用OS 提供的版本。因为这些 FFRT 提供的实现在与 FFRT 配合时开销将会更小。 2293 2294 2295 2296【建议6】:在需要时,使用 ffrt::wait() 确保栈变量的生命周期。 2297 2298如果子任务使用驻留在父任务栈上的数据,则父任务应避免在子任务执行完成前返回。在父任务的末尾添加 ffrt::wait() 可以解决这个问题。 2299 2300 2301 2302## 步骤3: 优化应用 2303 2304【建议1】:基于System Trace,分析并行是否符合预期 2305 2306FFRT 已经内置 SysTrace 支持,默认以txx.xx表示,非常有利于分析 Task 粒度和并发度。未来,在性能分析和维测方面将继续增强。 2307 2308<img src="images/image-20220926153209875.png" style="zoom:70%" /> 2309 2310【建议2】:对于耗时的 Task,尝试提交 SubTask,提升应用的并行度 2311 2312 2313【建议3】:在合适的场景,使用 Deadline 调度,实现能效和性能的平衡 2314 2315方案正在验证中,待更新。 2316 2317 2318 2319## 样例: CameraHal QuickThumb 2320 2321### 步骤1: 分析应用 2322 2323<img src="images/image-20220926153255824.png" style="zoom:70%" /> 2324 23251) QuickThumb 是 CameraHal 中实现的对一张图片进行缩小的功能,整体运行时间约30 us; 2326 23272) 在实现上分为两层循环,外层的一次循环输出1行,内层的1次循环输出该行的m列; 2328 23293) 在划分 Task 时,一种简单的做法是1行的处理就是1个Task。 2330 2331### 步骤2: 并行化应用 2332<img src="images/image-20220926153509205.png" style="zoom:100%" /> 2333 2334 1) 根据纯函数的定义,该 Task 的输入输出的数据是非常之多的,因此,这个场景下使用更宽泛的纯函数的定义,只需要考虑在 Task 内部会被写,但是却被定义在 Task 外部的变量即可; 2335 23362) 按照上面的原则,将 py/puv 的定义移到 Task 内部可避免多个 Task 同时写 py/puv 的问题; 2337 23383) s32r 的处理可以有两种方式,都能得到正确的功能:a. 保持定义在Task 外部,但作为Task 的输出依赖;b. 将s32r定义在Task 内部,作为Task的私有变量。显然,b 方案能够获得更好的性能 2339 2340 2341 2342### 步骤3: 优化应用 2343 2344通过System Trace,会发现上述改造方案的Task 粒度较小,大约单个Task 耗时在5us左右,因此,扩大Task的粒度为32行处理,得到最终的并行结果,下图为使用4小核和3中核的结果。 2345 2346<img src="images/image-20220926153603572.png" style="zoom:100%" /> 2347 2348 2349 2350## 样例: Camera AIRAW 2351 2352### 步骤1: 分析应用 2353<img src="images/image-20220926153611121.png" style="zoom:70%" /> 2354 2355AIRAW 的处理包括了3个处理步骤,在数据面上,可以按slice 进行切分,在不考虑pre_outbuf和npu_outbuf 在slice间复用的情况下,数据流图如上图所示。 2356 2357<img src="images/image-20220926152831526.png" style="zoom:70%" /> 2358 2359为了节省运行过程中的内存占用,但不影响整体性能,可以只保留2个pre_outbuf和2个npu_outbuf。 2360 2361`为此付出的代价是:Buffer 的复用产生了slice3 的GPU Pre Task 依赖slice1 的NPU Task 完成,俗称反压,又称生产者依赖关系。但是,如果您使用 FFRT 来实现,将会是非常自然而高效的` 2362 2363### 步骤2: 并行化应用 2364 2365```{.cpp} 2366constexpr uint32_t SLICE_NUM = 24; 2367constexpr uint32_t BUFFER_NUM = 2; 2368 2369int input[SLICE_NUM]; // input is split into SLICE_NUM slices 2370int pre_outbuf[BUFFER_NUM]; // gpu pre task output buffers 2371int npu_outbuf[BUFFER_NUM]; // npu output buffers 2372int output[SLICE_NUM]; // output is split into SLICE_NUM slices 2373 2374for (uint32_t i = 0; i < SLICE_NUM; i++) { 2375 uint32_t buf_id = i % BUFFER_NUM; 2376 ffrt::submit(gpuPreTask, {input + i}, {pre_outbuf + buf_id}); 2377 ffrt::submit(npuTask, {pre_outbuf + buf_id}, {npu_outbuf + buf_id}); 2378 ffrt::submit(gpuPostTask, {npu_outbuf + buf_id}, {output + i}); 2379} 2380 2381ffrt::wait(); 2382``` 2383 2384### 步骤3: 优化应用 2385 2386<img src="images/image-20220926153825527.png" style="zoom:100%" /> 2387 2388基于以上实现,从上面的Trace中我们看到,NPU 的硬件时间被完全用满,系统端到端性能达到最优,而付出的开发代价将会比GCD 或多线程小的多。 2389 2390 2391 2392## 样例: Camera FaceStory 2393 2394### 步骤1: 分析应用 2395 2396<img src="images/image-20220926153003884.png" style="zoom:70%" /> 2397 2398 2399 2400### 步骤2: 并行化应用 2401 2402<img src="images/image-20220926153906692.png" style="zoom:100%" /> 2403 2404代码改造样例 2405 24061) 该场景输出存量代码迁移,只需将原先串行的代码以Task的方式提交即可; 2407 24082) 过程中需要考虑Data Race和数据生命周期; 2409 24103) 先提交大的Task,根据需要逐步拆分SubTask。 2411 2412 2413 2414### 步骤3: 优化应用 2415 2416<img src="images/image-20220926153030993.png" style="zoom:100%" /> 2417 2418<center>原始System Trace</center> 2419 2420<img src="images/image-20220926153925963.png" style="zoom:100%" /> 2421 2422 2423 2424 2425<center>改造后System Trace</center> 2426 2427并行化的收益来自于: 2428 24291) 多分支或循环并发,实现CPU前后处理和NPU的并发 2430 24312) 子任务拆分,进一步提升并行度 2432 24333) 基于数据流图优化CPU L2 Cache Flush频次 2434 24354) NPU Worker Thread实时优先级调整,后续FFRT中考虑独立出XPU调度Worker来保证实时性 2436 24375) 在未来,模型加载使用FFRT submit,模型加载内部也可以使用submit来继续拆分,能够优化整个业务的启动耗时。 2438 2439<br/> 2440<br/> 2441<hr/> 2442 2443 2444 2445# 使用建议 2446 2447## 建议1: 函数化 2448 2449**基本思想:计算过程函数化** 2450 2451* 程序过程各步骤以函数封装表达,函数满足类纯函数特性 2452* 无全局数据访问 2453* 无内部状态保留 2454* 通过ffrt::submit()接口以异步任务方式提交函数执行 2455* 将函数访问的数据对象以及访问方式在ffrt::submit()接口中的in_deps/out_deps参数表达 2456* 程序员通过inDeps/outDeps参数表达任务间依赖关系以保证程序执行的正确性 2457 2458> 做到纯函数的好处在于:1. 能够最大化挖掘并行度,2.避免DataRace和锁的问题 2459 2460 2461 2462**在实际中,可以根据场景放松纯函数的约束,但前提是:** 2463 2464* 确定添加的in_deps/out_deps可确保程序正确执行 2465* 通过FFRT提供的锁机制保护对全局变量的访问 2466 2467 2468 2469## 建议2: 注意任务粒度 2470 2471* FFRT管理和调度异步任务执行有调度开销,任务粒度(执行时间)需匹配调度开销 2472* 大量小粒度任务造成FFRT调度开销占比增加,性能下降,解决方法: 2473 * 将多个小粒度任务聚合为大粒度任务一次发送给FFRT异步执行 2474 * 同步方式执行小粒度任务,不发送给FFRT异步执行。需注意和异步任务之间的数据同步问题,在需要同步的地方插入ffrt::wait() 2475 * 下面的例子中,fib_ffrt2会比fib_ffrt1拥有更好的性能 2476 2477 ```{.cpp} 2478 #include "ffrt.h" 2479 void fib_ffrt1(int x, int& y) 2480 { 2481 if (x <= 1) { 2482 y = x; 2483 } else { 2484 int y1, y2; 2485 ffrt::submit([&] {fib_ffrt1(x - 1, y1);}, {&x}, {&y1} ); 2486 ffrt::submit([&] {fib_ffrt1(x - 2, y2);}, {&x}, {&y2} ); 2487 ffrt::submit([&] {y = y1 + y2;}, {&y1, &y2}, {} ); 2488 ffrt::wait(); 2489 } 2490 } 2491 2492 void fib_ffrt2(int x, int& y) 2493 { 2494 if (x <= 1) { 2495 y = x; 2496 } else { 2497 int y1, y2; 2498 ffrt::submit([&] {fib_ffrt2(x - 1, y1);}, {&x}, {&y1} ); 2499 ffrt::submit([&] {fib_ffrt2(x - 2, y2);}, {&x}, {&y2} ); 2500 ffrt::wait({&y1, &y2}); 2501 y = y1 + y2; 2502 } 2503 } 2504 ``` 2505 2506 2507 2508## 建议3: 数据生命周期 2509 2510* FFRT的任务提交和执行是异步的,因此需要确保任务执行时,对任务中涉及的数据的访问是有效的 2511* 常见问题:子任务使用父任务栈数据,当父任务先于子任务执行完成时释放栈数据,子任务产生数据访问错误 2512* 解决方法1:父任务中增加ffrt::wait()等待子任务完成 2513 2514```{.cpp} 2515#include "ffrt.h" 2516void fib_ffrt(int x, int& y) 2517{ 2518 if (x <= 1) { 2519 y = x; 2520 } else { 2521 int y1, y2; 2522 ffrt::submit([&] {fib_ffrt(x - 1, y1);}, {&x}, {&y1} ); 2523 ffrt::submit([&] {fib_ffrt(x - 2, y2);}, {&x}, {&y2} ); 2524 ffrt::submit([&] {y = y1 + y2;}, {&y1, &y2}, {} ); 2525 ffrt::wait(); // 用于保证y1 y2的生命周期 2526 } 2527} 2528``` 2529 2530* 解决方法2:将数据由栈移到堆,手动管理生命周期 2531 2532```{.cpp} 2533#include "ffrt.h" 2534void fib_ffrt(int x, int* y) 2535{ 2536 if (x <= 1) { 2537 *y = x; 2538 } else { 2539 int *y1 = (int*)malloc(sizeof(int)); 2540 int *y2 = (int*)malloc(sizeof(int)); 2541 2542 ffrt::submit([=] {fib_ffrt(x - 1, y1);}, {}, {y1} ); 2543 ffrt::submit([=] {fib_ffrt(x - 2, y2);}, {}, {y2} ); 2544 ffrt::submit([=] {*y = *y1 + *y2; }, {y1, y2}, {} ); 2545 ffrt::wait(); 2546 } 2547} 2548``` 2549 2550 2551 2552## 建议4: 使用FFRT提供的替代API 2553 2554* 禁止在FFRT任务中使用系统线程库API创建线程,使用submit提交任务 2555* 使用FFRT提供的锁,条件变量,睡眠,IO等API代替系统线程库API 2556 * 使用系统线程库API可能造成工作线程阻塞,引起额外性能开销 2557 2558 2559 2560## 建议5: Deadline机制 2561 2562* **必须用于具备周期/重复执行特征的处理流程** 2563* 在有明确时间约束和性能关键的处理流程中使用,避免滥用 2564* 在相对大颗粒度的处理流程中使用,例如具有16.6ms时间约束的帧处理流程 2565 2566 2567 2568## 建议6: 从线程模型迁移 2569 2570* 创建线程替代为创建FFRT任务 2571 * 线程从逻辑上类似无in_deps的任务 2572* 识别线程间的依赖关系,并将其表达在任务的依赖关系in_deps/out_deps上 2573* 线程内计算过程分解为异步任务调用 2574* 通过任务依赖关系和锁机制避免并发任务数据竞争问题 2575 2576 2577 2578# 已知限制 2579 2580## 不支持thread_local变量 2581 2582* Task内部创建或Task间传递的thread_local变量的行为都是不确定的 2583 2584* 原因在于FFRT在编程模型中已经没有thread的概念,只有task的概念 2585* 在C++的语义下,thread_local可以被正常编译,但是使用该thread_local变量的task在哪一个线程上执行时不确定的 2586* 对于使用了FFRT进程中的non-worker,thread_local的行为不受FFRT影响 2587 2588> 类似的,与thread绑定的thread_idx/pthread_specific/递归锁/线程优先级/线程亲和性/递归锁具有相似的问题 2589 2590`建议` 2591 2592* 避免使用这些特性,如必须使用,使用FFRT的task local来替代 2593 2594 2595## 以动态库方式部署FFRT 2596 2597* 只能以动态库方式部署FFRT,静态库部署可能有多实例问题,例如:当多个被同一进程加载的so都以静态库的方式使用FFRT时,FFRT会被实例化成多份,其行为是未知的,这也不是FFRT设计的初衷 2598 2599