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,shared_mutex, condition_variable<br />3. Deadline 调度<br />4. 杂项:sleep,yield<br /> | 2022/09/26 | 13| V0.1.1 | shengxia | 部分描述更新 | 2023/08/24 | 14| V0.1.2 | wanghuachen | 新增串行队列相关接口以及说明,增加规范以避免double free问题 | 2023/10/07 | 15| V0.1.3 | shengxia | 优化串行队列内容描述 | 2024/01/12 | 16 17<br/> 18 19<hr/> 20# 缩写 21 22| 缩略语 | 英文全名 | 中文解释 | 23| ------------- | ------------------------------- | ------------------------------------------------------------ | 24| FFRT | Function Flow Run Time | 软件实现Function Flow运行时用于任务调度和执行 | 25| Function Flow | Function Flow Programming Model | Function Flow编程模型 | 26| Pure Function | Pure Function | 纯函数,注意本文中定义的纯函数指的是通过表达相互间数据依赖即可由调度系统保证正确执行的任务。 | 27 28 29<br/> 30<hr/> 31# 编程模型 32## 两种编程模型 33 34| | 线程编程模型 | FFRT任务编程模型 | 35| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | 36| 并行度挖掘方式 | 程序员通过创建多线程并把任务分配到每个线程中执行来挖掘运行时的并行度 | 程序员(编译器工具或语言特性配合)静态编程时将应用分解成任务及其数据依赖关系,运行时调度器分配任务到工作线程执行 | 37| 谁负责线程创建 | 程序员负责创建线程,线程编程模型无法约束线程的创建,滥用可能造成系统中大量线程 | FFRT运行时负责工作线程池的创建和管理由调度器负责,程序员无法直接创建线程 | 38| 负载均衡 | 程序员静态编程时将任务映射到线程,映射不合理或任务执行时间不确定造成线程负载不均 | FFRT运行时根据线程执行状态调度就绪任务到空闲线程执行,减轻了线程负载不均问题 | 39| 调度开销 | 线程调度由内核态调度器完成,调度开销大 | FFRT运行时在用户态以协程方式调度执行,相比内核线程调度机制更为轻量,减小调度的开销,并可通过硬化调度卸载进一步减小调度开销 | 40| 依赖表达 | 线程创建时即处于可执行状态,执行时与其他线程同步操作,增加线程切换 | FFRT运行时根据任务创建时显式表达的输入依赖和输出依赖关系判断任务可执行状态,当输入依赖不满足时,任务不被调度执行 | 41 42 43 44## Function Flow 任务编程模型 45 46Function Flow编程模型允许开发者通过任务及其依赖关系描述的方式进行应用开发,其主要特性包括`Task-Based` 和 `Data-Driven` 。 47 48### Task-Based 特性 49 50`Task-Based` 指在Function Flow编程模型中开发者以任务方式来组织应用程序表达,运行时以任务粒度执行调度。 51 52任务定义为一种面向开发者的编程线索和面向运行时的执行对象,通常包含一组指令序列及其操作的数据上下文环境。 53 54Function Flow编程模型中的任务包含以下主要特征: 55 56- 任务之间可指定依赖关系,依赖关系通过`Data-Driven`方式表达。 57- 任务可支持嵌套,即任务在执行过程中可生成新的任务下发给运行时,形成父子任务关系。 58- 多任务支持互同步操作,例如等待,锁,条件变量等。 59 60> 注意 61> 62> 任务颗粒度影响应用执行性能,颗粒度过小增加调度开销,颗粒度过大降低并行度。Function Flow编程模型中任务的目标颗粒度最小为100us量级,开发者应注意合理控制任务颗粒度。 63 64### Data-Driven 特性 65 66`Data-Driven`指任务之间的依赖关系通过数据依赖表达。 67 68任务执行过程中对其关联的数据对象进行读写操作。在Function Flow编程模型中,数据对象表达抽象为数据签名,每个数据签名唯一对应一个数据对象。 69 70数据依赖抽象为任务所操作的数据对象的数据签名列表,包括输入数据依赖`in_deps`和输出数据依赖`out_deps`。数据对象的签名出现在一个任务的`in_deps`中时,该任务称为数据对象的消费者任务,消费者任务执行不改变其输入数据对象的内容;数据对象的签名出现在任务的`out_deps`中时,该任务称为数据对象的生产者任务,生产者任务执行改变其输出数据对象的内容,从而生成该数据对象的一个新的版本。 71 72一个数据对象可能存在多个版本,每个版本对应一个生产者任务和零个,一个或多个消费者任务,根据生产者任务和消费者任务的下发顺序定义数据对象的多个版本的顺序以及每个版本所对应的生产者和消费者任务。 73 74数据依赖解除的任务进入就绪状态允许被调度执行,依赖解除状态指任务所有输入数据对象版本的生产者任务执行完成,且所有输出数据对象版本的所有消费者任务执行完成的状态。 75 76通过上述`Data-Driven`的数据依赖表达,FFRT在运行时可动态构建任务之间的基于生产者/消费者的数据依赖关系并遵循任务数据依赖状态执行调度,包括: 77 78- Producer-Consumer 依赖 79 80 一个数据对象版本的生产者任务和该数据对象版本的消费者任务之间形成的依赖关系,也称为Read-after-Write依赖。 81 82- Consumer-Producer 依赖 83 84 一个数据对象版本的消费者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Read依赖。 85 86- Producer-Producer 依赖 87 88 一个数据对象版本的生产者任务和该数据对象的下一个版本的生产者任务之间形成的依赖关系,也称为Write-after-Write依赖。 89 90 91例如,如果有这么一些任务,与数据A的关系表述为: 92```{.cpp} 93task1(OUT A); 94task2(IN A); 95task3(IN A); 96task4(OUT A); 97task5(OUT A); 98``` 99 100<img src="images/image-20220926150341102.png" style="zoom:60%" /> 101 102> 为表述方便,本文中的数据流图均以圆圈表示 Task,方块表示数据。 103 104可以得出以下结论: 105- task1 与task2/task3 构成Producer-Consumer 依赖,即:task2/task3 需要等到task1 写完A之后才能读A 106- task2/task3 与task4 构成Consumer-Producer 依赖,即:task4 需要等到task2/task3 读完A之后才能写A 107- task4 与task5 构成Producer-Producer 依赖,即:task5 需要等到task4 写完A之后才能写A 108 109 110 111# C++ API 112 113> C++ API采用接近C++11的命名风格,以`ffrt`命名空间替代`std`命名空间 114> 需编译使用-std=c++17 115 116## 任务管理 117### submit 118<hr/> 119* 向调度器提交一个task 120* 该接口是异步的,即该接口不等到task完成即可返回,因此,通常与[wait](#wait) 配合使用 121 122* 建议FFRT任务上下文使用ffrt::mutex替代std::mutex 123 * API上,二者仅在命令空间上有差异,可平滑替换(ffrt::mutex可在非ffrt task中调用,效果与普通的锁一致) 124 * ffrt::mutex相对std::mutex开销更小,且不会阻塞FFRT的worker线程(提交到FFRT的任务中大量使用std::mutex有FFRT worker线程被耗尽而死锁的风险) 125 126#### 声明 127 128```{.cpp} 129namespace ffrt { 130void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {}); 131void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps = {}, const std::vector<const void*>& out_deps = {}, const task_attr& attr = {}); 132} 133``` 134 135#### 参数 136 137`func` 138 139* 可被std::function 接收的一切CPU 可执行体,可以为C++ 定义的Lambda 函数闭包,函数指针,甚至是函数对象 140 141`in_deps` 142 143* 该参数是可选的 144* 该参数用于描述该任务的输入依赖,FFRT 通过数据的虚拟地址作为数据的Signature 来建立依赖 145 146`out_deps` 147 148* 该参数是可选的 149* 该参数用于描述该任务的输出依赖 150* `注意`:该依赖值本质上是一个数值,ffrt没办法区分该值是合理的还是不合理的,会假定输入的值是合理的进行处理;但不建议采用NULL,1, 2 等值来建立依赖关系,建议采用实际的内存地址,因为前者使用不当会建立起不必要的依赖,影响并发 151 152`attr` 153 154* 该参数是可选的 155* 该参数用于描述Task 的属性,比如qos 等,详见 [task_attr](#task_attr)章节 156 157#### 返回值 158 159* 不涉及 160 161#### 描述 162* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用 163 164* 该接口支持嵌套调用,即任务中可以继续提交子任务 165 166* 该接口在实现上使用多个重载版本以优化性能(基于移动语义,初始化列表等),用户只需按上述原型使用,编译时会自动选择最佳版本,支持的重载版本有: 167 168 ```{.cpp} 169 void submit(std::function<void()>&& func); 170 void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps); 171 void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps); 172 void submit(std::function<void()>&& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps, const task_attr& attr); 173 174 void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps); 175 void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps); 176 void submit(std::function<void()>&& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps, const task_attr& attr); 177 178 void submit(const std::function<void()>& func); 179 void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps); 180 void submit(const std::function<void()>& func, std::initializer_list<const void*> in_deps, std::initializer_list<const void*> out_deps); 181 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); 182 183 void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps); 184 void submit(const std::function<void()>& func, const std::vector<const void*>& in_deps, const std::vector<const void*>& out_deps); 185 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); 186 ``` 187 188 189#### 样例 190 191**submit and wait** 192 193```{.cpp} 194#include <iostream> 195#include "ffrt.h" 196 197int main(int narg, char** argv) 198{ 199 int i = 0; 200 for (i = 0; i < 3; i++) { 201 ffrt::submit([i] { std::cout << "num: " << i << std::endl; }); 202 } 203 ffrt::wait(); 204 return 0; 205} 206``` 207 208`解析`: 209 2101) 该示例中连续下发了3个Task,Task 使用C++ 11 Lambda 来描述(实际中Task 还可以使用函数指针,函数对象来描述),这些 Task 都会读取i,但是不会写任何变量; 211 2122) ffrt::submit 为异步下发,因此,Task2 并不会等到 Task1 执行完成之后再下发; 213 2143) ffrt::wait 用于实现待所有Task 都执行完成之后 main 函数再退出; 215 2164) 由于3个Task 在数据依赖关系上没有生产者-消费者或生产者-生产者依赖关系,因此3个 Task 是可以并行的,1种可能的输出是: 217 218```{.cpp} 219num: 0 220num: 2 221num: 1 222``` 223 224`注意`: 225 226如果将Lambda 表达式中的值捕获设置成引用捕获(即`[&i] { std::cout << "num: " << i << std::endl; }`),可能得到的输出为: 227 228```{.cpp} 229num: 2 230num: 2 231num: 2 232``` 233 234这是因为FFRT 是异步编程模型,在第一个task 真正开始执行的时候,i 的值可能已经被修改为1或者2 235 236 237 238**data verison** 239 240<img src="images/image-20220926150341102.png" style="zoom:60%" /> 241 242```{.cpp} 243#include <iostream> 244#include "ffrt.h" 245 246int main(int narg, char** argv) 247{ 248 int x = 1; 249 ffrt::submit([&] {x = 100; std::cout << "x:" << x << std::endl;}, {}, {&x}); 250 ffrt::submit([&] {std::cout << "x:" << x << std::endl;}, {&x}, {}); 251 ffrt::submit([&] {std::cout << "x:" << x << std::endl;}, {&x}, {}); 252 ffrt::submit([&] {x++; std::cout << "x:" << x << std::endl;}, {}, {&x}); 253 ffrt::submit([&] {x++; std::cout << "x:" << x << std::endl;}, {}, {&x}); 254 255 ffrt::wait(); 256 return 0; 257} 258``` 259 260 `解析`: 261 2621) 按上一章节[Data-Driven 特性](#Data-Driven 特性)的描述,输出一定为: 263 264```{.cpp} 265x:100 266x:100 267x:100 268x:101 269x:102 270``` 271 272**nested task** 273 274```{.cpp} 275#include <iostream> 276#include "ffrt.h" 277 278int main(int narg, char** argv) 279{ 280 ffrt::submit([&] { 281 std::cout << "task 1" << std::endl; 282 ffrt::submit([&] {std::cout << "nested task 1.1" << std::endl;}, {}, {}); 283 ffrt::submit([&] {std::cout << "nested task 1.2" << std::endl;}, {}, {}); 284 ffrt::wait(); 285 }, {}, {}); 286 287 ffrt::submit([&] { 288 std::cout << "task 2" << std::endl; 289 ffrt::submit([&] {std::cout << "nested task 2.1" << std::endl;}, {}, {}); 290 ffrt::submit([&] {std::cout << "nested task 2.2" << std::endl;}, {}, {}); 291 ffrt::wait(); 292 }, {}, {}); 293 ffrt::wait(); 294 return 0; 295} 296``` 297 298 `解析`: 299 3001) FFRT允许在 Task 内部继续提交多个SubTask,这样 Task 之间可以建立起一颗调用树; 301 3022) Task1 和Task2 可以并行,Task 1.1/1.2/2.1/2.2 之间也可以并行,因此1种可行的输出为: 303 304``` 305task 1 306nested task 1.1 307task 2 308nested task 1.2 309nested task 2.2 310nested task 2.1 311``` 312 313### wait 314<hr/> 315* 同步等待,与[submit](#submit) 配合使用 316* 等待指定的数据被生产完成,或等待当前任务的所有子任务完成,在不满足条件之前,当前的执行上下文被suspend,在满足条件后恢复执行 317 318#### 声明 319 320```{.cpp} 321namespace ffrt { 322void wait(const std::vector<const void*>& deps); 323void wait(); 324} 325``` 326 327#### 参数 328 329`deps` 330 331* 需要等待被生产完成的数据的虚拟地址,这些地址可能作为某些任务在submit 时的out_deps 332 333#### 返回值 334 335* 不涉及 336 337#### 描述 338* wait(deps) 用于等待deps指代的数据被生产完成才能执行后面的代码 339* wait() 用于等待当前上下文提交的所有子任务(`注意:不包括孙子任务`)都完成才能执行后面的代码 340* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用 341* 在FFRT task 外部调用的wait 是OS 能够感知的等待,相对于FFRT task 内部调用的wait 是更加昂贵的,因此我们希望尽可能让更多的wait 发生在FFRT task 内部 ,而不是FFRT task 外部 342 343#### 样例 344 345**recursive fibonacci** 346 347串行版的fibonacci 可以实现为: 348 349```{.cpp} 350#include <iostream> 351 352void fib(int x, int& y) { 353 if (x <= 1) { 354 y = x; 355 } else { 356 int y1, y2; 357 fib(x - 1, y1); 358 fib(x - 2, y2); 359 y = y1 + y2; 360 } 361} 362 363int main(int narg, char** argv) 364{ 365 int r; 366 fib(10, r); 367 std::cout << "fibonacci 10: " << r << std::endl; 368 return 0; 369} 370``` 371 372若要使用 FFRT 实现并行(注,对于单纯的fibonacci,单个 Task 的计算量极小,不具有并行加速的意义,但这种调用pattern 对并行编程模型的灵活性考验是非常高的),其中1种可行的实现为: 373 374```{.cpp} 375#include <iostream> 376 377#include "ffrt.h" 378 379void fib_ffrt(int x, int& y) 380{ 381 if (x <= 1) { 382 y = x; 383 } else { 384 int y1, y2; 385 ffrt::submit([&] {fib_ffrt(x - 1, y1);}, {&x}, {&y1} ); 386 ffrt::submit([&] {fib_ffrt(x - 2, y2);}, {&x}, {&y2} ); 387 ffrt::wait({&y1, &y2}); 388 y = y1 + y2; 389 } 390} 391 392int main(int narg, char** argv) 393{ 394 int r; 395 ffrt::submit([&] { fib_ffrt(10, r); }, {}, {&r}); 396 ffrt::wait({&r}); 397 std::cout << "fibonacci 10: " << r << std::endl; 398 return 0; 399} 400``` 401 402`解析`: 403 4041) 将fibonacci (x-1)和fibonacci (x-2) 作为2个Task 提交给FFRT,在两个Task 完成之后将结果累加; 405 4062) 虽然单个Task 只能拆分成2个SubTask 但是子Task 可以继续拆分,因此,整个计算图的并行度是非常高的,Task 之间在FFRT 内部形成了一颗调用树; 407 408<img src="images/image-20220926152331554.png" style="zoom:100%" /> 409 410 411### task_attr 412<hr/> 413* 定义task 的属性的辅助类,与[submit](#submit) 配合使用 414 415#### 声明 416 417```{.cpp} 418namespace ffrt { 419enum qos { 420 qos_inherit = -1, 421 qos_background, 422 qos_utility, 423 qos_default, 424 qos_user_initiated, 425}; 426 427class task_attr { 428public: 429 task_attr& qos(enum qos qos); // set qos 430 enum qos qos() const; // get qos 431 task_attr& name(const char* name); // set name 432 const char* name() const; // get name 433}; 434} 435``` 436 437#### 参数 438 439`qos` 440 441* qos 设定的枚举类型 442* inherent 是一个qos 设定策略,代表即将submit 的task 的qos 继承当前task 的qos 443 444#### 返回值 445 446* 不涉及 447 448#### 描述 449* 约定 450 * 在submit 时,如果不通过task_attr 设定qos,那么默认该提交的task的qos 为`qos_default` 451 * 在submit 时,如果通过task_attr 设定qos 为`qos_inherent`,表示将该提交的task 的qos 与当前task 的qos 相同,在FFRT task 外部提交的属性为`qos_inherent` 的task,其qos 为`qos_default` 452 * 其他情况下,该提交的task 的qos 被设定为指定的值 453* qos 级别从上到下依次递增,qos_user_interactive拥有最高优先级 454 455#### 样例 456 457```{.cpp} 458#include <iostream> 459#include "ffrt.h" 460 461int main(int narg, char** argv) 462{ 463 ffrt::submit([] { std::cout << "hello ffrt" << std::endl; }, {}, {}, 464 ffrt::task_attr().qos(ffrt::qos_background)); 465 ffrt::wait(); 466 return 0; 467} 468``` 469 470* 提交一个qos 级别为background 的任务 471 472 473 474### submit_h 475 476<hr/> 477 478* 向调度器提交一个task,与[submit](#submit) 的差别在于返回task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 479 480#### 声明 481 482```{.cpp} 483namespace ffrt { 484class task_handle { 485public: 486 task_handle(); 487 task_handle(ffrt_task_handle_t p); 488 489 task_handle(task_handle const&) = delete; 490 void operator=(task_handle const&) = delete; 491 492 task_handle(task_handle&& h); 493 task_handle& operator=(task_handle&& h); 494 495 operator void* () const; 496}; 497 498task_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 = {}); 499task_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 = {}); 500} 501``` 502 503#### 参数 504 505`func` 506 507* 同submit,详见[submit](#submit) 定义 508 509`in_deps` 510 511* 同submit,详见[submit](#submit) 定义 512 513`out_deps` 514 515* 同submit,详见[submit](#submit) 定义 516 517`attr` 518 519* 同submit,详见[submit](#submit) 定义 520 521#### 返回值 522 523* task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 524 525#### 描述 526 527* 该接口与submit 使用基本相同,从性能的角度,在不需要返回task handle 的场景,可以调用submit 接口相对于submit_h 有更好的性能 528* task_handle 可以和其他的数据depends 同时作为某个task 的in_deps,表示该task 的执行依赖task_handle 对应的task 执行完成 529* task_handle 可以和其他的数据depends 同时作为wait 的deps,表示当前任务将被suspend,直到task_handle 对应的task 执行完成后将被恢复 530* task_handle 不建议作为某个task 的out_deps,其行为是未定义的 531 532 533#### 样例 534 535```{.cpp} 536#include <iostream> 537#include "ffrt.h" 538 539int main(int narg, char** argv) 540{ 541 // handle work with submit 542 ffrt::task_handle h = ffrt::submit_h([] { std::cout << "hello "; }); // not need some data in this task 543 int x = 1; 544 ffrt::submit([&] { x++; }, {}, {&x}); 545 ffrt::submit([&] { std::cout << "world, x = " << x << std::endl; }, {&x, h}); // this task depend x and h 546 547 // handle work with wait 548 ffrt::task_handle h2 = ffrt::submit_h([&] { std::cout << "handle wait" << std::endl; x++; }); 549 ffrt::wait({h2}); 550 std::cout << "x = " << x << std::endl; 551 ffrt::wait(); 552 return 0; 553} 554``` 555 556* 预期的输出为 557 558``` 559hello world, x = 2 560handle wait 561x = 3 562``` 563 564### get_id 565 566<hr/> 567 568* 返回当前task的id标识,更多使用用于维测(原因是task name可能重名) 569 570#### 声明 571 572```{.cpp} 573namespace ffrt { 574namespace this_task { 575uint64_t get_id(); 576} 577} 578``` 579 580#### 参数 581 582* 不涉及 583 584#### 返回值 585 586* 当前task的id 587 588#### 描述 589 590* 该接口在task内部调用将返回当前task的id标识,在task外部调用将返回0 591* 可以基于该接口在task外部调用返回0的特性来区分函数是运行在FFRT 工作线程上还是非FFRT工作线程上 592* task id为从1开始编码,每提交一个task便增加1,被设计成64bit,即便是每秒百万次提交,也需要292471.2年才会发生翻转 593 594#### 样例 595 596```{.cpp} 597#include <iostream> 598#include "ffrt.h" 599 600int main(int narg, char** argv) 601{ 602 ffrt::submit([] { std::cout << "task id: " << ffrt::this_task::get_id() << std::endl; }); 603 ffrt::submit([] { std::cout <<"task id: " << ffrt::this_task::get_id() << std::endl; }); 604 ffrt::wait(); 605 std::cout << "task id: " << ffrt::this_task::get_id() << std::endl; 606 return 0; 607} 608``` 609 610* 可能的输出为: 611 612``` 613task id: 1 614task id: 2 615task id: 0 616``` 617 618### update_qos 619 620<hr/> 621 622* 更新当前正在执行的task的优先级 623 624#### 声明 625 626```{.cpp} 627namespace ffrt { 628namespace this_task { 629int update_qos(enum qos qos); 630} 631} 632``` 633 634#### 参数 635 636`qos` 637* 新的qos等级 638 639#### 返回值 640 641* 0表示成功,非0表示失败 642 643#### 描述 644 645* 该接口对当前task的qos调整会立即生效 646* 如果新设定的qos与当前的qos不一致,则会block当前task的执行,再按照新的qos恢复执行 647* 如果新设定的qos与当前的qos一致,则接口会立即返回,不做任何处理 648* **如果在非task内部调用该接口,则返回非0值,用户可以选择忽略或其他处理** 649 650#### 样例 651 652```{.cpp} 653#include <iostream> 654#include <thread> 655#include "ffrt.h" 656 657int main(int narg, char** argv) 658{ 659 ffrt::submit([] { 660 std::cout << "thread id: " << std::this_thread::get_id() << std::endl; 661 std::cout << "return " << ffrt::this_task::update_qos(ffrt::qos_user_initiated) << std::endl; 662 std::cout << "thread id: " << std::this_thread::get_id() << std::endl; 663 }); 664 ffrt::wait(); 665 std::cout << "return " << ffrt::this_task::update_qos(ffrt::qos_user_initiated) << std::endl; 666 return 0; 667} 668``` 669 670* 可能的输出为: 671 672``` 673thread id: 1024 674return 0 675thread id: 2222 676return 1 677``` 678 679 680 681## 串行队列 682<hr /> 683 684串行队列基于FFRT协程调度模型,实现了消息队列功能。串行任务执行在FFRT worker上,用户无需维护一个专用的线程,拥有更轻量级的调度开销。 685 686支持以下基本功能: 687 688* 支持创建队列,创建队列时可指定队列名称和优先级,每个队列在功能上相当于一个单独的线程,队列中的任务相对于用户线程异步执行。 689 690* 支持延时任务,向队列提交任务时支持设置 delay 属性,单位为微秒 us,提交给队列的延时任务,在提交时刻+delay时间后才会被调度执行。 691 692* 支持串行调度,同一个队列中的多个任务按照 uptime (提交时刻+delay时间)升序排列、串行执行,队列中一个任务完成后下一个任务才会开始。 693 694* 支持取消任务,支持根据任务句柄取消单个未执行的任务,如果这个任务已出队(开始执行或已执行完),取消接口返回异常值。 695 696* 支持同步等待,支持根据任务句柄等待指定任务完成,该任务完成,也代表同一队列中uptime在此任务之前的所有任务都已完成。 697 698### queue 699<hr/> 700 701#### 描述 702FFRT串行队列 C++ API,提供提交任务、取消任务、等待任务执行完成等功能 703 704#### 声明 705 706```{.cpp} 707namespace ffrt { 708class queue { 709public: 710 queue(queue const&) = delete; 711 void operator=(queue const&) = delete; 712 713 void submit(const std::function<void()>& func); 714 void submit(const std::function<void()>& func, const task_attr& attr); 715 void submit(std::function<void()>&& func); 716 void submit(std::function<void()>&& func, const task_attr& attr); 717 718 task_handle submit_h(const std::function<void()>& func); 719 task_handle submit_h(const std::function<void()>& func, const task_attr& attr); 720 task_handle submit_h(std::function<void()>&& func); 721 task_handle submit_h(std::function<void()>&& func, const task_attr& attr); 722 723 int cancel(const task_handle& handle); 724 725 void wait(const task_handle& handle); 726}; 727} 728``` 729 730#### 方法 731 732##### submit 733 734```{.cpp} 735namespace ffrt { 736void queue::submit(const std::function<void()>& func); 737void queue::submit(const std::function<void()>& func, const task_attr& attr); 738void queue::submit(std::function<void()>&& func); 739void queue::submit(std::function<void()>&& func, const task_attr& attr); 740} 741``` 742 743* 描述:提交一个任务到队列中调度执行 744 745* 参数: 746 747 `func`:可被std::function接收的一切CPU可执行体,可以为C++定义的Lambda函数闭包,函数指针,甚至时函数对象 748 749 `attr`:该参数时可选的,用于描述task的属性,如qos、delay、timeout等,详见[task_attr](#task_attr)章节 750 751* 返回值:不涉及 752 753##### submit_h 754 755```{.cpp} 756namespace ffrt { 757task_handle queue::submit_h(const std::function<void()>& func); 758task_handle queue::submit_h(const std::function<void()>& func, const task_attr& attr); 759task_handle queue::submit_h(std::function<void()>&& func); 760task_handle queue::submit_h(std::function<void()>&& func, const task_attr& attr); 761} 762``` 763 764* 描述:提交一个任务到队列中调度执行,并返回一个句柄 765 766* 参数: 767 768 `func`:可被std::function接收的一切CPU可执行体,可以为C++定义的Lambda函数闭包,函数指针,甚至时函数对象 769 770 `attr`:该参数时可选的,用于描述task的属性,如qos、delay、timeout等,详见[task_attr](#task_attr)章节 771 772* 返回值: 773 774 `task_handle`:task的句柄,该句柄可以用于建立task之间的依赖 775 776##### cancel 777 778```{.cpp} 779namespace ffrt { 780int queue::cancel(const task_handle& handle); 781} 782``` 783 784* 描述:根据句柄取消对应的任务 785 786* 参数: 787 788 `handle`:任务的句柄 789 790* 返回值: 791 792 若成功返回0,否则返回其他非0值 793 794##### wait 795 796```{.cpp} 797namespace ffrt { 798void queue::wait(const task_handle& handle); 799} 800``` 801 802* 描述:等待句柄对应的任务执行完成 803 804* 参数: 805 806 `handle`:任务的句柄 807 808* 返回值:不涉及 809 810 811#### 样例 812 813```{.cpp} 814#include "ffrt.h" 815 816int main(int narg, char** argv) 817{ 818 // 创建队列,可设置队列优先级,默认为default等级 819 ffrt::queue q("test_queue", ffrt::queue_attr().qos(ffrt::qos_utility)); 820 821 int x = 0; 822 // 提交串行任务 823 q.submit([&x] { x += 10; }); 824 825 // 提交串行任务,并返回任务句柄 826 task_handle t1 = q.submit_h([&x] { x += 10; }); 827 828 // 提交串行任务,设置延时时间1000us,并返回任务句柄 829 task_handle t2 = q.submit_h([&x] { x += 10; }, ffrt::task_attr().delay(1000)); 830 831 // 等待指定任务执行完成 832 q.wait(t1); 833 834 // 取消句柄为t2的任务 835 q.cancel(t2); 836} 837``` 838 839#### 使用约束 840 841* 队列销毁时,会等待正在执行的任务执行完成,队列中还没有开始执行的任务会被取消。 842 843* 任务粒度,串行队列支持任务执行超时检测(默认阈值30s,进程可配),因此队列中的单个任务不应该常驻(如循环任务),超过30s会向DFX上报超时。 844 845* 同步原语,任务中如果使用了 std::mutex/std::shared_mutex/std::condition_variable等std同步原语,会影响协程效率,需修改ffrt同步原语。 846 847 当前FFRT仅支持ffrt::mutex / ffrt::shared_mutex / ffrt::recursive_mutex / ffrt::condition_variable,用法和std相同,在ffrt的任务中使用未支持同步原语可能导致未定义的行为。 848 849* 生命周期,进程结束前需要释放FFRT资源。 850 851 例如SA业务,会在全局变量中管理串行队列。由于进程会先卸载libffrt.so再释放全局变量,如果进程结束时,SA未显式释放持有的队列,队列将随全局变量析构,析构时会访问已释放的ffrt资源,导致Fuzz用例出现use-after-free问题。 852 853* 不允许再串行任务中调用ffrt::submit和ffrt::wait,其行为是未定义的 854 855* 不允许使用ffrt::wait等待一个串行任务 856 857### queue_attr 858<hr/> 859 860#### 描述 861FFRT串行队列 C++ API,提供设置与获取串行队列优先级、设置与获取串行队列任务执行超时时间、设置与获取串行队列超时回调函数等功能 862 863#### 声明 864 865```{.cpp} 866namespace ffrt { 867class queue_attr { 868public: 869 queue_attr(const queue_attr&) = delete; 870 queue_attr& operator=(const queue_attr&) = delete; 871 872 queue_attr& qos(qos qos_); 873 uint64_t timeout() const; 874 875 queue_attr& callback(const std::function<void()>& func); 876 ffrt_function_header_t* callback() const; 877}; 878} 879``` 880 881#### 方法 882 883##### set qos 884 885```{.cpp} 886namespace ffrt { 887queue_attr& queue_attr::qos(qos qos_); 888} 889``` 890 891* 描述:设置队列属性的qos成员 892 893* 参数: 894 895 `qos_`:串行队列的优先级 896 897* 返回值: 898 899 `queue_attr`:串行队列的属性 900 901##### get qos 902 903```{.cpp} 904namespace ffrt { 905int queue_attr::qos() const; 906} 907``` 908 909* 描述:获取队列的优先级 910 911* 参数:不涉及 912 913* 返回值: 914 915 `qos`:串行队列的优先级 916 917##### set timeout 918 919```{.cpp} 920namespace ffrt { 921queue_attr& queue_attr::timeout(uint64_t timeout_us); 922} 923``` 924 925* 描述:设置串行队列任务执行超时时间 926 927* 参数: 928 929 `timeout_us`:串行队列任务执行超时时间,单位为us 930 931* 返回值: 932 933 `queue_attr`:串行队列的属性 934 935##### get timeout 936 937```{.cpp} 938namespace ffrt { 939uint64_t queue_attr::timeout() const; 940} 941``` 942 943* 描述:获取所设的串行队列任务执行超时时间 944 945* 参数:不涉及 946 947* 返回值: 948 949 `timeout`:串行队列任务执行超时时间,单位为us 950 951##### set timeout callback 952 953```{.cpp} 954namespace ffrt { 955queue_attr& callback(std::function<void()>& func); 956} 957``` 958 959* 描述:设置串行队列超时回调函数 960 961* 参数: 962 963 `func`:可被std::function接收的一切CPU可执行体,可以为C++定义的Lambda函数闭包,函数指针,甚至时函数对象 964 965* 返回值: 966 967 `queue_attr`:串行队列的属性 968 969##### get timeout callback 970 971```{.cpp} 972namespace ffrt { 973ffrt_function_header_t* callback() const; 974} 975``` 976 977* 描述:获取所设的串行队列超时回调函数 978 979* 参数:不涉及 980 981* 返回值: 982 983 `ffrt_function_header_t`:任务执行器,描述了该CPU Task如何执行和销毁的函数指针 984 985#### 样例 986```{.cpp} 987#include <stdio.h> 988#include "ffrt.h" 989 990int main(int narg, char** argv) 991{ 992 std::function<void()> callbackFunc = [&x]() { 993 ... 994 }; 995 996 // 创建队列,可设置队列优先级,默认为default等级 997 ffrt::queue q1("test_queue", queue_attr().qos(qos_utility)); 998 999 // 创建队列,可通过设置timeout打开队列任务超时监测,默认不设置(关闭) 1000 // 超时会打印Error日志并执行用户设置的callback(可选) 1001 ffrt::queue q2("test_queue", ffrt::queue_attr().timeout(1000).callback(callbackFunc)); 1002 1003 return 0; 1004} 1005``` 1006 1007 1008## 同步原语 1009 1010### mutex 1011<hr/> 1012* FFRT提供的类似std::mutex 的性能实现 1013 1014#### 声明 1015 1016```{.cpp} 1017namespace ffrt { 1018class mutex { 1019public: 1020 mutex(mutex const &) = delete; 1021 void operator =(mutex const &) = delete; 1022 1023 void lock(); 1024 void unlock(); 1025 bool try_lock(); 1026}; 1027} 1028``` 1029 1030#### 参数 1031 1032* 不涉及 1033 1034#### 返回值 1035 1036* 不涉及 1037 1038#### 描述 1039* 该功能能够避免传统的std::mutex 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能 1040 1041#### 样例 1042 1043```{.cpp} 1044#include <iostream> 1045#include "ffrt.h" 1046 1047void ffrt_mutex_task() 1048{ 1049 int sum = 0; 1050 ffrt::mutex mtx; 1051 for (int i = 0; i < 10; i++) { 1052 ffrt::submit([&sum, i, &mtx] { 1053 mtx.lock(); 1054 sum = sum + i; 1055 mtx.unlock(); 1056 }, {}, {}); 1057 } 1058 ffrt::wait(); 1059 std::cout << "sum = " << sum << std::endl; 1060} 1061 1062int main(int narg, char** argv) 1063{ 1064 int r; 1065 ffrt::submit(ffrt_mutex_task); 1066 ffrt::wait(); 1067 return 0; 1068} 1069``` 1070 1071预期输出为 1072 1073``` 1074sum=45 1075``` 1076 1077* 该例子为功能示例,实际中并不鼓励这样使用 1078 1079### shared_mutex 1080<hr/> 1081* FFRT提供的类似std::shared_mutex 的性能实现 1082 1083#### 声明 1084 1085```{.cpp} 1086namespace ffrt { 1087class shared_mutex { 1088public: 1089 shared_mutex(shared_mutex const &) = delete; 1090 void operator =(shared_mutex const &) = delete; 1091 1092 void lock(); 1093 void unlock(); 1094 bool try_lock(); 1095 1096 void lock_shared(); 1097 void unlock_shared(); 1098 bool try_lock_shared(); 1099}; 1100} 1101``` 1102 1103#### 参数 1104 1105* 不涉及 1106 1107#### 返回值 1108 1109* 不涉及 1110 1111#### 描述 1112* 该功能能够避免传统的std::shared_mutex 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能 1113 1114#### 样例 1115 1116```{.cpp} 1117#include <iostream> 1118#include "ffrt_inner.h" 1119 1120void ffrt_shared_mutex_task() 1121{ 1122 int sum = 0; 1123 ffrt::shared_mutex mtx; 1124 for (int i = 0; i < 10; i++) { 1125 ffrt::submit([&sum, i, &mtx] { 1126 mtx.lock(); 1127 sum = sum + i; 1128 mtx.unlock(); 1129 }, {}, {}); 1130 for (int j = 0; j < 5; j++) { 1131 ffrt::submit([&sum, j, &mtx] { 1132 mtx.lock_shared(); 1133 std::cout << "sum = " << sum << std::endl; 1134 mtx.unlock_shared(); 1135 }, {}, {}); 1136 } 1137 } 1138 ffrt::wait(); 1139 std::cout << "sum = " << sum << std::endl; 1140} 1141 1142int main(int narg, char** argv) 1143{ 1144 int r; 1145 ffrt::submit(ffrt_shared_mutex_task); 1146 ffrt::wait(); 1147 return 0; 1148} 1149``` 1150 1151预期输出为 1152 1153``` 1154sum=45 1155``` 1156 1157* 该例子为功能示例,实际中并不鼓励这样使用 1158 1159### condition_variable 1160<hr/> 1161 1162* FFRT提供的类似std::condition_variable 的性能实现 1163 1164#### 声明 1165 1166```{.cpp} 1167namespace ffrt { 1168enum class cv_status { 1169 no_timeout, 1170 timeout 1171}; 1172 1173class condition_variable { 1174public: 1175 using TimePoint = std::chrono::steady_clock::time_point; 1176 template<typename Clock, typename Duration, typename Pred> 1177 bool wait_until(std::unique_lock<mutex>& lk, 1178 const std::chrono::time_point<Clock, Duration>& tp, 1179 Pred&& pred) noexcept; 1180 1181 template<typename Clock, typename Duration> 1182 cv_status wait_until(std::unique_lock<mutex>& lk, 1183 const std::chrono::time_point<Clock, Duration>& tp) noexcept; 1184 1185 template<typename Rep, typename Period> 1186 cv_status wait_for(std::unique_lock<mutex>& lk, 1187 const std::chrono::duration<Rep, Period>& sleep_time) noexcept; 1188 1189 template<typename Rep, typename Period, typename Pred> 1190 bool wait_for(std::unique_lock<mutex>& lk, 1191 const std::chrono::duration<Rep, Period>& sleepTime, 1192 Pred&& pred) noexcept; 1193 1194 void wait(std::unique_lock<mutex>& lk); 1195 1196 template<typename Pred> 1197 void wait(std::unique_lock<mutex>& lk, Pred&& pred); 1198 1199 void notify_one() noexcept; 1200 1201 void notify_all() noexcept; 1202}; 1203} 1204``` 1205 1206#### 参数 1207 1208`lk` 1209* mutex互斥量 1210`tp` 1211* 等待时间 1212`sleep_time` 1213* 等待时间 1214`pred` 1215* 检查是否等待函数 1216#### 返回值 1217 1218* 不涉及 1219 1220#### 描述 1221* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 1222* 该功能能够避免传统的std::condition_variable 在条件不满足时陷入内核的问题,在使用得当的条件下将会有更好的性能 1223 1224#### 样例 1225 1226```{.cpp} 1227#include <iostream> 1228#include "ffrt.h" 1229 1230void ffrt_cv_task() 1231{ 1232 ffrt::condition_variable cond; 1233 int a = 0; 1234 ffrt::mutex lock_; 1235 ffrt::submit([&] { 1236 std::unique_lock lck(lock_); 1237 cond.wait(lck, [&] { return a == 1; }); 1238 std::cout << "a = " << a << std::endl; 1239 }, {}, {}); 1240 ffrt::submit([&] { 1241 std::unique_lock lck(lock_); 1242 a = 1; 1243 cond.notify_one(); 1244 }, {}, {}); 1245 1246 ffrt::wait(); 1247} 1248 1249int main(int narg, char** argv) 1250{ 1251 int r; 1252 ffrt::submit(ffrt_cv_task); 1253 ffrt::wait(); 1254 return 0; 1255} 1256 1257``` 1258 1259预期输出为: 1260 1261``` 1262a=1 1263``` 1264 1265* 该例子为功能示例,实际中并不鼓励这样使用 1266 1267## 杂项 1268 1269### sleep 1270 1271<hr/> 1272* FFRT提供的类似std::this_thread::sleep_for / std::this_thread::sleep_until 的性能实现 1273 1274#### 声明 1275 1276```{.cpp} 1277namespace ffrt { 1278namespace this_task { 1279template<class _Rep, class _Period> 1280void sleep_for(const std::chrono::duration<_Rep, _Period>& sleep_duration); 1281 1282template<class _Clock, class _Duration> 1283void sleep_until(const std::chrono::time_point<_Clock, _Duration>& sleep_time); 1284} 1285} 1286``` 1287 1288#### 参数 1289 1290`sleep_duration` 1291 1292* 睡眠的时长 1293 1294`sleep_time` 1295 1296* 睡眠到达的时间点 1297 1298#### 返回值 1299 1300* 不涉及 1301 1302#### 描述 1303* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 1304* 该功能能够避免传统的std::this_thread::sleep_for 睡眠时陷入内核的问题,在使用得当的条件下将会有更好的性能 1305* 该接口调用后实际睡眠时长不小于配置值 1306 1307#### 样例 1308 1309```{.cpp} 1310#include <chrono> 1311#include <iostream> 1312#include "ffrt.h" 1313 1314using namespace std::chrono_literals; 1315int main(int narg, char** argv) 1316{ 1317 ffrt::submit([] { 1318 std::cout << "Hello waiter\n" << std::flush; 1319 auto start = std::chrono::high_resolution_clock::now(); 1320 ffrt::this_task::sleep_for(2000ms); 1321 auto end = std::chrono::high_resolution_clock::now(); 1322 std::chrono::duration<double, std::milli> elapsed = end-start; 1323 std::cout << "Waited " << elapsed.count() << " ms\n"; 1324 }); 1325 ffrt::wait(); 1326 return 0; 1327} 1328``` 1329 1330* 预期输出为 1331 1332``` 1333Hello waiter 1334Waited 2000.12 ms 1335``` 1336 1337### yield 1338<hr/> 1339* 当前task 主动让出CPU 执行资源,让其他可以被执行的task 先执行,如果没有其他可被执行的task,yield 无效 1340 1341#### 声明 1342 1343```{.cpp} 1344namespace ffrt { 1345namespace this_task { 1346void yield(); 1347} 1348} 1349``` 1350 1351#### 参数 1352 1353* 不涉及 1354 1355#### 返回值 1356 1357* 不涉及 1358 1359#### 描述 1360* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 1361* 此函数的确切行为取决于实现,特别是使用中的FFRT 调度程序的机制和系统状态 1362 1363#### 样例 1364 1365```{.cpp} 1366#include <chrono> 1367#include "ffrt.h" 1368 1369using namespace std::chrono_literals; 1370// "busy sleep" while suggesting that other tasks run 1371// for a small amount of time 1372void little_sleep(std::chrono::microseconds us) 1373{ 1374 auto start = std::chrono::high_resolution_clock::now(); 1375 auto end = start + us; 1376 do { 1377 ffrt::this_task::yield(); 1378 } while (std::chrono::high_resolution_clock::now() < end); 1379} 1380 1381int main(int narg, char** argv) 1382{ 1383 ffrt::submit([] { little_sleep(200us); }); 1384 ffrt::wait(); 1385 return 0; 1386} 1387``` 1388 1389* 这是一个`busy sleep`,同时允许其他可以被执行的task 插入执行 1390 1391 1392# C API 1393 1394> C API采用接近C11/pthread (https://zh.cppreference.com/w/c) 的命名风格,并冠以`ffrt_`前缀,以`_base`为后缀的API是内部API,通常不被用户直接调用 1395> 1396> **出于易用性方面的考虑,除非必要,强烈建议你使用C++ API(亦满足二进制兼容要求),调用C API将会使你的代码非常臃肿** 1397 1398## 任务管理 1399 1400### ffrt_submit_base 1401 1402* 该接口为ffrt动态库的导出接口,基于此可以封装出不同的C++ API ffrt::submit和C API ffrt_submit,满足二进制兼容 1403 1404#### 声明 1405 1406```{.cpp} 1407const int ffrt_auto_managed_function_storage_size = 64 + sizeof(ffrt_function_header_t); 1408typedef enum { 1409 ffrt_function_kind_general, 1410 ffrt_function_kind_queue 1411} ffrt_function_kind_t; 1412 1413void* ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_t kind); 1414 1415typedef void(*ffrt_function_t)(void*); 1416typedef struct { 1417 ffrt_function_t exec; 1418 ffrt_function_t destroy; 1419 uint64_t reserve[2]; 1420} ffrt_function_header_t; 1421 1422void 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); 1423``` 1424 1425#### 参数 1426 1427`kind` 1428 1429* function子类型,用于优化内部数据结构,默认使用ffrt_function_kind_general类型 1430 1431`func` 1432 1433* CPU Function的指针,该指针执行的数据结构,按照`ffrt_function_header_t`定义的描述了该CPU Task如何执行和销毁的函数指针,FFRT通过这两个函数指针完成Task的执行和销毁 1434 1435`in_deps` 1436 1437* 同ffrt_submit 1438 1439 1440`out_deps` 1441 1442* 同ffrt_submit 1443 1444`attr` 1445 1446* 同ffrt_submit 1447 1448#### 返回值 1449 1450* 不涉及 1451 1452#### 描述 1453 1454* ffrt_submit_base不建议用户直接调用,推荐使用基于此封装的C++接口(亦满足二进制兼容) 1455* **ffrt_submit_base作为底层能力,只有在用户需要自定义task类型时使用,使用时需要满足以下限制:** 1456 * ffrt_submit_base入参中的func指针只能通过ffrt_alloc_auto_managed_function_storage_base申请,且二者的调用需一一对应 1457 * ffrt_alloc_auto_managed_function_storage_base申请的内存为ffrt_auto_managed_function_storage_size字节,其生命周期归ffrt管理,在该task结束时,由FFRT自动释放,用户无需释放 1458* ffrt_function_header_t 中定义了两个函数指针: 1459 * exec:用于描述该Task如何被执行,当FFRT需要执行该Task时由FFRT调用 1460 * destroy:用于描述该Task如何被执行,当FFRT需要执行该Task时由FFRT调用 1461 1462#### 样例 1463 1464* 通过该接口提供C++11 Lambda表达式的支持(该代码已经在ffrr.h中提供,默认支持) 1465 1466```{.cpp} 1467template<class T> 1468struct function { 1469 template<class CT> 1470 function(ffrt_function_header_t h, CT&& c) : header(h), closure(std::forward<CT>(c)) {} 1471 ffrt_function_header_t header; 1472 T closure; 1473}; 1474 1475template<class T> 1476void exec_function_wrapper(void* t) 1477{ 1478 auto f = (function<std::decay_t<T>>*)t; 1479 f->closure(); 1480} 1481 1482template<class T> 1483void destroy_function_wrapper(void* t) 1484{ 1485 auto f = (function<std::decay_t<T>>*)t; 1486 f->closure = nullptr; 1487} 1488 1489template<class T> 1490inline ffrt_function_header_t* create_function_wrapper(T&& func) 1491{ 1492 using function_type = function<std::decay_t<T>>; 1493 static_assert(sizeof(function_type) <= ffrt_auto_managed_function_storage_size, 1494 "size of function must be less than ffrt_auto_managed_function_storage_size"); 1495 1496 auto p = ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1497 auto f = new (p) function_type( 1498 {exec_function_wrapper<T>, destroy_function_wrapper<T>}, 1499 std::forward<T>(func)); 1500 return (ffrt_function_header_t*)f; 1501} 1502 1503static inline void submit(std::function<void()>&& func) 1504{ 1505 return ffrt_submit_base(create_function_wrapper(std::move(func)), NULL, NULL, NULL); 1506} 1507``` 1508 1509### ffrt_wait 1510 1511<hr/> 1512* 同步等待,与ffrt_submit 配合使用 1513* 等待指定的数据被生产完成,或等待当前任务的所有子任务完成,在不满足条件之前,当前的执行上下文被suspend,在满足条件后恢复执行 1514 1515#### 声明 1516 1517```{.cpp} 1518void ffrt_wait_deps(ffrt_deps_t* deps); 1519void ffrt_wait(); 1520``` 1521 1522#### 参数 1523 1524`deps` 1525 1526* 需要等待被生产完成的数据的虚拟地址,这些地址可能作为某些任务在submit 时的out_deps,该依赖的生成见ffrt_deps_t章节,空指针表示无依赖 1527 1528#### 返回值 1529 1530* 不涉及 1531 1532#### 描述 1533* ffrt_wait_deps(deps) 用于等待deps指代的数据被生产完成才能执行后面的代码 1534* ffrt_wait() 用于等待当前上下文提交的所有子任务(`注意:不包括孙任务和下级子任务`)都完成才能执行后面的代码 1535* 该接口支持在FFRT task 内部调用,也支持在FFRT task 外部调用 1536* 在FFRT task 外部调用的wait 是OS 能够感知的等待,相对于FFRT task 内部调用的wait 是更加昂贵的,因此我们希望尽可能让更多的wait 发生在FFRT task 内部 ,而不是FFRT task 外部 1537 1538#### 样例 1539 1540**recursive fibonacci** 1541 1542串行版的fibonacci 可以实现为: 1543 1544```{.c} 1545#include <stdio.h> 1546 1547void fib(int x, int* y) { 1548 if (x <= 1) { 1549 *y = x; 1550 } else { 1551 int y1, y2; 1552 fib(x - 1, &y1); 1553 fib(x - 2, &y2); 1554 *y = y1 + y2; 1555 } 1556} 1557int main(int narg, char** argv) 1558{ 1559 int r; 1560 fib(10, &r); 1561 printf("fibonacci 10: %d\n", r); 1562 return 0; 1563} 1564``` 1565 1566若要使用 FFRT 实现并行(注,对于单纯的fibonacci,单个 Task 的计算量极小,不具有并行加速的意义,但这种调用pattern 对并行编程模型的灵活性考验是非常高的),其中1种可行的实现为: 1567 1568```{.c} 1569#include <stdio.h> 1570#include "ffrt.h" 1571 1572typedef struct { 1573 int x; 1574 int* y; 1575} fib_ffrt_s; 1576 1577typedef struct { 1578 ffrt_function_header_t header; 1579 ffrt_function_t func; 1580 ffrt_function_t after_func; 1581 void* arg; 1582} c_function; 1583 1584static void ffrt_exec_function_wrapper(void* t) 1585{ 1586 c_function* f = (c_function*)t; 1587 if (f->func) { 1588 f->func(f->arg); 1589 } 1590} 1591 1592static void ffrt_destroy_function_wrapper(void* t) 1593{ 1594 c_function* f = (c_function*)t; 1595 if (f->after_func) { 1596 f->after_func(f->arg); 1597 } 1598} 1599 1600#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1601static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1602 const ffrt_function_t after_func, void* arg) 1603{ 1604 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1605 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1606 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1607 f->header.exec = ffrt_exec_function_wrapper; 1608 f->header.destroy = ffrt_destroy_function_wrapper; 1609 f->func = func; 1610 f->after_func = after_func; 1611 f->arg = arg; 1612 return (ffrt_function_header_t*)f; 1613} 1614 1615static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1616 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1617{ 1618 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1619} 1620 1621#define ffrt_deps_define(name, dep1, ...) const void* __v_##name[] = {dep1, ##__VA_ARGS__}; \ 1622 ffrt_deps_t name = {sizeof(__v_##name) / sizeof(void*), __v_##name} 1623 1624void fib_ffrt(void* arg) 1625{ 1626 fib_ffrt_s* p = (fib_ffrt_s*)arg; 1627 int x = p->x; 1628 int* y = p->y; 1629 1630 if (x <= 1) { 1631 *y = x; 1632 } else { 1633 int y1, y2; 1634 fib_ffrt_s s1 = {x - 1, &y1}; 1635 fib_ffrt_s s2 = {x - 2, &y2}; 1636 ffrt_deps_define(dx, &x); 1637 ffrt_deps_define(dy1, &y1); 1638 ffrt_deps_define(dy2, &y2); 1639 ffrt_deps_define(dy12, &y1, &y2); 1640 ffrt_submit_c(fib_ffrt, NULL, &s1, &dx, &dy1, NULL); 1641 ffrt_submit_c(fib_ffrt, NULL, &s2, &dx, &dy2, NULL); 1642 ffrt_wait_deps(&dy12); 1643 *y = y1 + y2; 1644 } 1645} 1646 1647int main(int narg, char** argv) 1648{ 1649 int r; 1650 fib_ffrt_s s = {10, &r}; 1651 ffrt_deps_define(dr, &r); 1652 ffrt_submit_c(fib_ffrt, NULL, &s, NULL, &dr, NULL); 1653 ffrt_wait_deps(&dr); 1654 printf("fibonacci 10: %d\n", r); 1655 return 0; 1656} 1657``` 1658 1659`解析`: 1660 16611) 将fibonacci (x-1)和fibonacci (x-2) 作为2个Task 提交给FFRT,在两个Task 完成之后将结果累加; 1662 16632) 虽然单个Task 只能拆分成2个SubTask 但是子Task 可以继续拆分,因此,整个计算图的并行度是非常高的,Task 之间在FFRT 内部形成了一颗调用树; 1664 1665<img src="images/image-20220926152331554.png" style="zoom:100%" /> 1666 1667> 以上实现,逻辑上虽与C++ API中的实现类似,但是用户显式管理数据生命周期和函数入参打包两个因素将使代码异常复杂 1668 1669 1670 1671### ffrt_deps_t 1672 1673* C API中对依赖数组的抽象,逻辑上等同于C++ API中的`std::vector<void*>` 1674 1675#### 声明 1676 1677```{.cpp} 1678typedef struct { 1679 uint32_t len; 1680 const void* const * items; 1681} ffrt_deps_t; 1682``` 1683 1684#### 参数 1685 1686`len` 1687 1688* 所依赖的Signature的个数,取值大于等于0 1689 1690`item` 1691 1692* len个Signature的起始地址指针 1693 1694#### 返回值 1695 1696* 不涉及 1697 1698#### 描述 1699 1700* item为len个Signature的起始指针,该指针可以指向堆空间,也可以指向栈空间,但是要求分配的空间大于等于len * sizeof(void*) 1701 1702#### 样例 1703 1704* item指向栈空间的ffrt_deps_t 1705 1706```{.c} 1707#include "ffrt.h" 1708 1709int main(int narg, char** argv) 1710{ 1711 int x1 = 1; 1712 int x2 = 2; 1713 1714 void *t[] = {&x1, &x2}; 1715 ffrt_deps_t deps = {2, (const void* const *)&t}; 1716 // some code use deps 1717 return 0; 1718} 1719``` 1720 1721* item指向栈空间的ffrt_deps_t 1722 1723```{.c} 1724#include <stdlib.h> 1725#include "ffrt.h" 1726 1727int main(int narg, char** argv) 1728{ 1729 int x1 = 1; 1730 int x2 = 2; 1731 1732 void** t = (void**)malloc(sizeof(void*) * 2); 1733 t[0]= &x1; 1734 t[1]= &x2; 1735 ffrt_deps_t deps = {2, t}; 1736 1737 // some code use deps 1738 free(t); 1739 return 0; 1740} 1741``` 1742 1743### ffrt_task_attr_t 1744 1745<hr/> 1746* 定义task 的属性的辅助类,与ffrt_submit 配合使用 1747 1748#### 声明 1749 1750```{.c} 1751typedef enum { 1752 ffrt_qos_inherent = -1, 1753 ffrt_qos_background, 1754 ffrt_qos_utility, 1755 ffrt_qos_default, 1756 ffrt_qos_user_initiated, 1757} ffrt_qos_t; 1758 1759typedef struct { 1760 char storage[ffrt_task_attr_storage_size]; 1761} ffrt_task_attr_t; 1762typedef void* ffrt_task_handle_t; 1763 1764int ffrt_task_attr_init(ffrt_task_attr_t* attr); 1765void ffrt_task_attr_destroy(ffrt_task_attr_t* attr); 1766void ffrt_task_attr_set_qos(ffrt_task_attr_t* attr, ffrt_qos_t qos); 1767ffrt_qos_t ffrt_task_attr_get_qos(const ffrt_task_attr_t* attr); 1768void ffrt_task_attr_set_name(ffrt_task_attr_t* attr, const char* name); 1769const char* ffrt_task_attr_get_name(const ffrt_task_attr_t* attr); 1770``` 1771 1772#### 参数 1773 1774`attr` 1775 1776* 创建的tasks属性的句柄 1777 1778`qos` 1779 1780* qos 设定的枚举类型 1781* inherent 是一个qos 设定策略,代表即将ffrt_submit 的task 的qos 继承当前task 的qos 1782 1783#### 返回值 1784 1785* 不涉及 1786 1787#### 描述 1788* `attr`所传递的内容会在ffrt_submit内部完成取存,ffrt_submit返回后用户即可销毁 1789* 约定 1790 * 在submit 时,如果不通过task_attr 设定qos,那么默认该提交的task的qos 为`ffrt_qos_default` 1791 * 在submit 时,如果通过task_attr 设定qos 为`ffrt_qos_inherent`,表示将该提交的task 的qos 与当前task 的qos 相同,在FFRT task 外部提交的属性为`ffrt_qos_inherent` 的task,其qos 为`ffrt_qos_default` 1792 * 其他情况下,该提交的task 的qos 被设定为指定的值 1793* ffrt_task_attr_t对象的置空和销毁由用户完成,对同一个ffrt_task_attr_t仅能调用一次`ffrt_task_attr_destroy`,重复对同一个ffrt_task_attr_t调用`ffrt_task_attr_destroy`,其行为是未定义的 1794* 在`ffrt_task_attr_destroy`之后再对task_attr进行访问,其行为是未定义的 1795 1796#### 样例 1797 1798```{.c} 1799#include <stdio.h> 1800#include "ffrt.h" 1801 1802void my_print(void* arg) 1803{ 1804 printf("hello ffrt\n"); 1805} 1806 1807typedef struct { 1808 ffrt_function_header_t header; 1809 ffrt_function_t func; 1810 ffrt_function_t after_func; 1811 void* arg; 1812} c_function; 1813 1814static void ffrt_exec_function_wrapper(void* t) 1815{ 1816 c_function* f = (c_function*)t; 1817 if (f->func) { 1818 f->func(f->arg); 1819 } 1820} 1821 1822static void ffrt_destroy_function_wrapper(void* t) 1823{ 1824 c_function* f = (c_function*)t; 1825 if (f->after_func) { 1826 f->after_func(f->arg); 1827 } 1828} 1829 1830#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1831static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1832 const ffrt_function_t after_func, void* arg) 1833{ 1834 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1835 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1836 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1837 f->header.exec = ffrt_exec_function_wrapper; 1838 f->header.destroy = ffrt_destroy_function_wrapper; 1839 f->func = func; 1840 f->after_func = after_func; 1841 f->arg = arg; 1842 return (ffrt_function_header_t*)f; 1843} 1844 1845static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1846 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1847{ 1848 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1849} 1850 1851int main(int narg, char** argv) 1852{ 1853 ffrt_task_attr_t attr; 1854 ffrt_task_attr_init(&attr); 1855 ffrt_task_attr_set_qos(&attr, ffrt_qos_background); 1856 ffrt_submit_c(my_print, NULL, NULL, NULL, NULL, &attr); 1857 ffrt_task_attr_destroy(&attr); 1858 ffrt_wait(); 1859 return 0; 1860} 1861``` 1862 1863* 提交一个qos 级别为background 的任务 1864 1865 1866 1867### ffrt_submit_h 1868 1869<hr/> 1870 1871* 向调度器提交一个task,与ffrt_submit 的差别在于返回task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 1872 1873#### 声明 1874 1875```{.cpp} 1876typedef void* ffrt_task_handle_t; 1877 1878ffrt_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); 1879void ffrt_task_handle_destroy(ffrt_task_handle_t handle); 1880``` 1881 1882#### 参数 1883 1884`func` 1885 1886* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1887 1888`in_deps` 1889 1890* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1891 1892`out_deps` 1893 1894* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1895 1896`attr` 1897 1898* 同ffrt_submit,详见[ffrt_submit](#ffrt_submit) 定义 1899 1900#### 返回值 1901 1902* task 的句柄,该句柄可以用于建立task 之间的依赖,或用于在wait 语句中实现同步 1903 1904#### 描述 1905 1906* C API中的ffrt_task_handle_t的使用与C++ API中的ffrt::task_handle相同 1907* **差异在于:C API中的ffrt_task_handle_t需要用户调用`ffrt_task_handle_destroy`显式销毁,而C++ API无需该操作** 1908* C API中的task_handle_t对象的置空和销毁由用户完成,对同一个ffrt_task_handle_t仅能调用一次`ffrt_task_handle_destroy`,重复对同一个ffrt_task_handle_t调用`ffrt_task_handle_destroy`,其行为是未定义的 1909* 在`ffrt_task_handle_destroy`之后再对ffrt_task_handle_t进行访问,其行为是未定义的 1910 1911#### 样例 1912 1913```{.c} 1914#include <stdio.h> 1915#include "ffrt.h" 1916 1917void func0(void* arg) 1918{ 1919 printf("hello "); 1920} 1921 1922void func1(void* arg) 1923{ 1924 (*(int*)arg)++; 1925} 1926 1927void func2(void* arg) 1928{ 1929 printf("world, x = %d\n", *(int*)arg); 1930} 1931 1932void func3(void* arg) 1933{ 1934 printf("handle wait"); 1935 (*(int*)arg)++; 1936} 1937 1938typedef struct { 1939 ffrt_function_header_t header; 1940 ffrt_function_t func; 1941 ffrt_function_t after_func; 1942 void* arg; 1943} c_function; 1944 1945static void ffrt_exec_function_wrapper(void* t) 1946{ 1947 c_function* f = (c_function*)t; 1948 if (f->func) { 1949 f->func(f->arg); 1950 } 1951} 1952 1953static void ffrt_destroy_function_wrapper(void* t) 1954{ 1955 c_function* f = (c_function*)t; 1956 if (f->after_func) { 1957 f->after_func(f->arg); 1958 } 1959} 1960 1961#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 1962static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 1963 const ffrt_function_t after_func, void* arg) 1964{ 1965 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 1966 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 1967 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 1968 f->header.exec = ffrt_exec_function_wrapper; 1969 f->header.destroy = ffrt_destroy_function_wrapper; 1970 f->func = func; 1971 f->after_func = after_func; 1972 f->arg = arg; 1973 return (ffrt_function_header_t*)f; 1974} 1975 1976static inline ffrt_task_handle_t ffrt_submit_h_c(ffrt_function_t func, const ffrt_function_t after_func, 1977 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1978{ 1979 return ffrt_submit_h_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1980} 1981 1982static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 1983 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 1984{ 1985 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 1986} 1987 1988#define ffrt_deps_define(name, dep1, ...) const void* __v_##name[] = {dep1, ##__VA_ARGS__}; \ 1989 ffrt_deps_t name = {sizeof(__v_##name) / sizeof(void*), __v_##name} 1990 1991int main(int narg, char** argv) 1992{ 1993 // handle work with submit 1994 ffrt_task_handle_t h = ffrt_submit_h_c(func0, NULL, NULL, NULL, NULL, NULL); // not need some data in this task 1995 int x = 1; 1996 ffrt_deps_define(d1, &x); 1997 ffrt_deps_define(d2, &x, h); 1998 ffrt_submit_c(func1, NULL, &x, NULL, &d1, NULL); 1999 ffrt_submit_c(func2, NULL, &x, &d2, NULL, NULL); // this task depend x and h 2000 ffrt_task_handle_destroy(h); 2001 2002 // handle work with wait 2003 ffrt_task_handle_t h2 = ffrt_submit_h_c(func3, NULL, &x, NULL, NULL, NULL); 2004 ffrt_deps_define(d3, h2); 2005 ffrt_wait_deps(&d3); 2006 ffrt_task_handle_destroy(h2); 2007 printf("x = %d", x); 2008 ffrt_wait(); 2009 return 0; 2010} 2011``` 2012 2013* 预期的输出为 2014 2015``` 2016hello world, x = 2 2017handle wait 2018x = 3 2019``` 2020 2021 2022 2023### ffrt_this_task_get_id 2024 2025<hr/> 2026 2027* 返回当前task的id标识,更多使用用于维测(原因是task name可能重名) 2028 2029#### 声明 2030 2031```{.c} 2032uint64_t ffrt_this_task_get_id(); 2033``` 2034 2035#### 参数 2036 2037* 不涉及 2038 2039#### 返回值 2040 2041* 当前task的id 2042 2043#### 描述 2044 2045* 该接口在task内部调用将返回当前task的id标识,在task外部调用将返回0 2046* 可以基于该接口在task外部调用返回0的特性来区分函数是运行在FFRT 工作线程上还是非FFRT工作线程上 2047* task id为从1开始编码,每提交一个task便增加1,被设计成64bit,即便是每秒百万次提交,也需要292471.2年才会发生翻转 2048 2049#### 样例 2050 2051* 忽略 2052 2053 2054 2055### ffrt_this_task_update_qos 2056 2057<hr/> 2058 2059* 更新当前正在执行的task的优先级 2060 2061#### 声明 2062 2063```{.cpp} 2064int ffrt_this_task_update_qos(ffrt_qos_t qos); 2065``` 2066 2067#### 参数 2068 2069* `qos` 新的优先级 2070 2071#### 返回值 2072 2073* 0表示成功,非0表示失败 2074 2075#### 描述 2076 2077* 该接口对当前task的qos调整会立即生效 2078* 如果新设定的qos与当前的qos不一致,则会block当前task的执行,再按照新的qos恢复执行 2079* 如果新设定的qos与当前的qos一致,则接口会立即返回0,不做任何处理 2080* **如果在非task内部调用该接口,则返回非0值,用户可以选择忽略或其他处理** 2081 2082#### 样例 2083 2084* 忽略 2085 2086## 串行队列 2087<hr /> 2088 2089基本功能与使用约束见 C++ API 中的串行队列部分 2090 2091### ffrt_queue_t 2092<hr/> 2093 2094#### 描述 2095FFRT串行队列 C API,提供提交任务、取消任务、等待任务执行完成等功能 2096 2097#### 声明 2098 2099```{.cpp} 2100typedef enum { ffrt_queue_serial, ffrt_queue_max } ffrt_queue_type_t; 2101typedef void* ffrt_queue_t; 2102 2103ffrt_queue_t ffrt_queue_create(ffrt_queue_type_t type, const char* name, const ffrt_queue_attr_t* attr); 2104void ffrt_queue_destroy(ffrt_queue_t queue); 2105 2106void ffrt_queue_submit(ffrt_queue_t queue, ffrt_function_header_t* f, const ffrt_task_attr_t* attr); 2107ffrt_task_handle_t ffrt_queue_submit_h( 2108 ffrt_queue_t queue, ffrt_function_header_t* f, const ffrt_task_attr_t* attr); 2109 2110void ffrt_queue_wait(ffrt_task_handle_t handle); 2111 2112int ffrt_queue_cancel(ffrt_task_handle_t handle); 2113``` 2114 2115#### 方法 2116 2117##### ffrt_queue_create 2118 2119```{.cpp} 2120ffrt_queue_t ffrt_queue_create(ffrt_queue_type_t type, const char* name, const ffrt_queue_attr_t* attr); 2121``` 2122 2123* 描述:创建串行队列 2124 2125* 参数: 2126 2127 `type`:用于描述创建的队列类型,串行队列对应 `type` 为 `ffrt_queue_serial` 2128 2129 `name`:用于描述创建的队列名称 2130 2131 `attr`:所创建的queue属性,若未设定则会使用默认值 2132 2133* 返回值:如果成功创建了队列,则返回一个非空的队列句柄;否则返回空指针 2134 2135##### ffrt_queue_destroy 2136 2137```{.cpp} 2138void ffrt_queue_destroy(ffrt_queue_t queue); 2139``` 2140 2141* 描述:销毁串行队列 2142 2143* 参数: 2144 2145 `queue`:想要销毁的队列的句柄 2146 2147* 返回值:不涉及 2148 2149##### ffrt_queue_submit 2150 2151```{.cpp} 2152void ffrt_queue_submit(ffrt_queue_t queue, ffrt_function_header_t* f, const ffrt_task_attr_t* attr); 2153``` 2154 2155* 描述:提交一个任务到队列中调度执行 2156 2157* 参数: 2158 2159 `queue`:串行队列的句柄 2160 2161 `f`:任务执行指针 2162 2163 `attr`:所创建的queue属性 2164 2165* 返回值:不涉及 2166 2167##### ffrt_queue_submit_h 2168 2169```{.cpp} 2170ffrt_task_handle_t ffrt_queue_submit_h( 2171 ffrt_queue_t queue, ffrt_function_header_t* f, const ffrt_task_attr_t* attr); 2172``` 2173 2174* 描述:提交一个任务到队列中调度执行,并返回任务句柄 2175 2176* 参数: 2177 2178 `queue`:串行队列的句柄 2179 2180 `f`:任务执行指针 2181 2182 `attr`:所创建的queue属性 2183 2184* 返回值:如果任务被提交,则返回一个非空的任务句柄;否则返回空指针 2185 2186##### ffrt_queue_wait 2187 2188```{.cpp} 2189void ffrt_queue_wait(ffrt_task_handle_t handle); 2190``` 2191 2192* 描述:等待串行队列中一个任务执行完成 2193 2194* 参数: 2195 2196 `handle`:任务的句柄 2197 2198* 返回值:不涉及 2199 2200##### ffrt_queue_cancel 2201 2202```{.cpp} 2203int ffrt_queue_cancel(ffrt_task_handle_t handle); 2204``` 2205 2206* 描述:取消队列中一个任务。必须使用submit_h后拿到的task_handle,否则会报异常;任务开始执行后则无法取消,仅能成功取消未开始执行的任务 2207 2208* 参数: 2209 2210 `handle`:任务的句柄 2211 2212* 返回值:若成功返回0,否则返回其他非0值 2213 2214#### 样例 2215 2216```{.cpp} 2217#include <stdio.h> 2218#include "ffrt.h" 2219 2220using namespace ffrt; 2221using namespace std; 2222 2223int main(int narg, char** argv) 2224{ 2225 ffrt_queue_attr_t queue_attr; 2226 // 1、初始化队列属性,必需 2227 (void)ffrt_queue_attr_init(&queue_attr); 2228 2229 // 2、创建串行队列,并返回队列句柄queue_handle 2230 ffrt_queue_t queue_handle = ffrt_queue_create(ffrt_queue_serial, "test_queue", &queue_attr); 2231 2232 int result = 0; 2233 std::function<void()>&& basicFunc = [&result]() { result += 1; }; 2234 2235 // 3、提交串行任务 2236 ffrt_queue_submit(queue_handle, create_function_wrapper(basicFunc, ffrt_function_kind_queue), nullptr); 2237 2238 // 4、提交出啊逆行任务,并返回任务句柄 2239 ffrt_task_handle_t t1 = ffrt_queue_submit_h(queue_handle, create_function_wrapper(basicFunc, ffrt_function_kind_queue), nullptr); 2240 // 5、等待指定任务执行完成 2241 ffrt_queue_wait(t1); 2242 2243 ffrt_task_handle_t t2 = ffrt_queue_submit_h(queue_handle, create_function_wrapper(basicFunc, ffrt_function_kind_queue), nullptr); 2244 // 6、取消句柄为t2的任务 2245 int ret = ffrt_queue_cancel(t2); 2246 2247 // 7、销毁提交给串行队列任务的句柄t1和t2,必需 2248 ffrt_task_handle_destroy(t1); 2249 ffrt_task_handle_destroy(t2); 2250 // 8、销毁队列属性,必需 2251 ffrt_queue_attr_destroy(&queue_attr); 2252 // 9、销毁队列句柄,必需 2253 ffrt_queue_destroy(queue_handle); 2254} 2255``` 2256 2257### ffrt_queue_attr_t 2258<hr/> 2259 2260#### 描述 2261FFRT串行队列 C API,提供设置与获取串行队列优先级、设置与获取串行队列任务执行超时时间、设置与获取串行队列超时回调函数等功能 2262 2263#### 声明 2264 2265```{.cpp} 2266typedef struct { 2267 uint32_t storage[(ffrt_queue_attr_storage_size + sizeof(uint32_t) - 1) / sizeof(uint32_t)]; 2268} ffrt_queue_attr_t; 2269 2270int ffrt_queue_attr_init(ffrt_queue_attr_t* attr); 2271void ffrt_queue_attr_destroy(ffrt_queue_attr_t* attr); 2272 2273void ffrt_queue_attr_set_qos(ffrt_queue_attr_t* attr, ffrt_qos_t qos); 2274ffrt_qos_t ffrt_queue_attr_get_qos(const ffrt_queue_attr_t* attr); 2275 2276void ffrt_queue_attr_set_timeout(ffrt_queue_attr_t* attr, uint64_t timeout_us); 2277uint64_t ffrt_queue_attr_get_timeout(const ffrt_queue_attr_t* attr); 2278 2279void ffrt_queue_attr_set_callback(ffrt_queue_attr_t* f); 2280ffrt_function_header_t* ffrt_queue_attr_get_callback(const ffrt_queue_attr_t* attr); 2281``` 2282 2283#### 方法 2284 2285##### ffrt_queue_attr_init 2286```{.cpp} 2287int ffrt_queue_attr_init(ffrt_queue_attr_t* attr); 2288``` 2289 2290* 描述:初始化串行队列的属性 2291 2292* 参数: 2293 2294 `attr`:已初始化的串行队列属性 2295 2296* 返回值:若成功返回0,否则返回-1 2297 2298##### ffrt_queue_attr_destroy 2299```{.cpp} 2300void ffrt_queue_attr_destroy(ffrt_queue_attr_t* attr); 2301``` 2302 2303* 描述:销毁串行队列的属性 2304 2305 ffrt_queue_attr_t对象的置空和销毁由用户完成,对同一个ffrt_queue_t仅能调用一次 `ffrt_queue_attr_destroy` ,重复对同一个ffrt_queue_t调用 `ffrt_queue_attr_destroy` ,其行为是未定义的 2306 2307 在`ffrt_queue_attr_destroy`之后再对ffrt_queue_t进行访问,其行为是未定义的 2308 2309* 参数: 2310 2311 `attr`:所创建的串行队列属性 2312 2313* 返回值:不涉及 2314 2315##### ffrt_queue_attr_set_qos 2316```{.cpp} 2317void ffrt_queue_attr_set_qos(ffrt_queue_attr_t* attr, ffrt_qos_t qos); 2318``` 2319 2320* 描述:设置串行队列qos属性,默认为default等级 2321 2322* 参数: 2323 2324 `attr`:所创建的串行队列属性 2325 2326 `qos`:串行队列优先级 2327 2328* 返回值:不涉及 2329 2330##### ffrt_queue_attr_get_qos 2331```{.cpp} 2332ffrt_qos_t ffrt_queue_attr_get_qos(const ffrt_queue_attr_t* attr); 2333``` 2334 2335* 描述:获取串行队列qos属性 2336 2337* 参数: 2338 2339 `attr`:所创建的串行队列属性 2340 2341* 返回值:所设置的串行队列的qos等级,默认为default等级 2342 2343##### ffrt_queue_attr_set_timeout 2344```{.cpp} 2345void ffrt_queue_attr_set_timeout(ffrt_queue_attr_t* attr, uint64_t timeout_us); 2346``` 2347 2348* 描述:设置串行队列任务执行超时时间 2349 2350* 参数: 2351 2352 `attr`:所创建的串行队列属性 2353 2354 `timeout_us`:串行队列任务执行超时时间,单位为us 2355 2356* 返回值:不涉及 2357 2358##### ffrt_queue_attr_get_timeout 2359```{.cpp} 2360uint64_t ffrt_queue_attr_get_timeout(const ffrt_queue_attr_t* attr); 2361``` 2362 2363* 描述:获取串行队列任务执行超时时间 2364 2365* 参数: 2366 2367 `attr`:所创建的串行队列属性 2368 2369* 返回值:串行队列任务执行超时时间,单位为us 2370 2371##### ffrt_queue_attr_set_callback 2372```{.cpp} 2373void ffrt_queue_attr_set_callback(ffrt_queue_attr_t* f); 2374``` 2375 2376* 描述:设置串行队列超时回调函数 2377 2378* 参数: 2379 2380 `attr`:所创建的串行队列属性 2381 2382 `f`:串行队列超时回调函数 2383 2384* 返回值:不涉及 2385 2386##### ffrt_queue_attr_get_callback 2387```{.cpp} 2388ffrt_function_header_t* ffrt_queue_attr_get_callback(const ffrt_queue_attr_t* attr); 2389``` 2390 2391* 描述:获取串行队列超时回调函数 2392 2393* 参数: 2394 2395 `attr`:所创建的串行队列属性 2396 2397* 返回值:串行队列超时回调函数 2398 2399#### 样例 2400``` 2401#include <stdio.h> 2402#include "ffrt.h" 2403 2404using namespace ffrt; 2405using namespace std; 2406 2407int main(int narg, char** argv) 2408{ 2409 ffrt_queue_attr_t queue_attr; 2410 // 1、初始化串行队列属性,必需 2411 int result = ffrt_queue_attr_init(&queue_attr); 2412 2413 int x = 0; 2414 std::function<void()>&& basicFunc = [&x]() { x += 1; }; 2415 2416 // 2、可设置队列优先级,默认为default等级 2417 ffrt_queue_attr_set_qos(&queue_attr, static_cast<int>(ffrt_qos_utility)); 2418 int qos = ffrt_queue_attr_get_qos(&queue_attr); 2419 2420 // 3、可通过设置timeout打开队列任务超时监测,默认不设置(关闭) 2421 ffrt_queue_attr_set_timeout(&queue_attr, 10000); 2422 uint64_t time = ffrt_queue_attr_get_timeout(&queue_attr); 2423 2424 // 4、超时会打印Error日志并执行用户设置的callback(可选) 2425 ffrt_queue_attr_set_callback(&queue_attr, ffrt::create_function_wrapper(basicFunc, ffrt_function_kind_queue)); 2426 ffrt_function_header_t* func = ffrt_queue_attr_get_callback(&queue_attr); 2427 2428 // 5、销毁串行队列属性,必需 2429 ffrt_queue_attr_destroy(&queue_attr); 2430} 2431``` 2432 2433 2434## 同步原语 2435 2436### ffrt_mutex_t 2437<hr/> 2438* FFRT提供的类似pthread mutex 的性能实现 2439 2440#### 声明 2441 2442```{.cpp} 2443typedef enum { 2444 ffrt_error = -1, 2445 ffrt_success = 0, 2446 ffrt_error_nomem = ENOMEM, 2447 ffrt_error_timedout = ETIMEDOUT, 2448 ffrt_error_busy = EBUSY, 2449 ffrt_error_inval = EINVAL 2450} ffrt_error_t; 2451 2452struct ffrt_mutex_t; 2453 2454struct ffrt_mutexattr_t; 2455 2456typedef enum { 2457 ffrt_mutex_normal = 0, 2458 ffrt_mutex_recursive = 2, 2459 ffrt_mutex_default = ffrt_mutex_normal 2460} ffrt_mutex_type; 2461 2462int ffrt_mutexattr_init(ffrt_mutexattr_t* attr); 2463int ffrt_mutexattr_settype(ffrt_mutexattr_t* attr, int type); 2464int ffrt_mutexattr_gettype(ffrt_mutexattr_t* attr, int* type); 2465int ffrt_mutexattr_destroy(ffrt_mutexattr_t* attr); 2466int ffrt_mutex_init(ffrt_mutex_t* mutex, const ffrt_mutexattr_t* attr); 2467int ffrt_mutex_lock(ffrt_mutex_t* mutex); 2468int ffrt_mutex_unlock(ffrt_mutex_t* mutex); 2469int ffrt_mutex_trylock(ffrt_mutex_t* mutex); 2470int ffrt_mutex_destroy(ffrt_mutex_t* mutex); 2471``` 2472 2473#### 参数 2474`type` 2475 2476* FFRT锁类型,当前仅支持互斥锁ffrt_mutex_normal和递归锁ffrt_mutex_recursive 2477 2478`attr` 2479 2480* FFRT锁属性,attr如果为空指针代表互斥锁mutex 2481 2482`mutex` 2483 2484* 指向所操作的锁指针 2485 2486#### 返回值 2487 2488* 若成功则为 ffrt_success ,否则发生错误 2489 2490#### 描述 2491* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 2492* 该功能能够避免pthread传统的pthread_mutex_t 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能 2493* **注意:目前暂不支持定时功能** 2494* **注意:C API中的ffrt_mutexattr_t需要用户调用`ffrt_mutexattr_init`和`ffrt_mutexattr_destroy`显示创建和销毁,而C++ API无需该操作** 2495* **注意:C API中的ffrt_mutex_t需要用户调用`ffrt_mutex_init`和`ffrt_mutex_destroy`显式创建和销毁,而C++ API无需该操作** 2496* **注意:C API中的ffrt_mutex_t对象的置空和销毁由用户完成,对同一个ffrt_mutex_t仅能调用一次`ffrt_mutex_destroy`,重复对同一个ffrt_mutex_t调用`ffrt_mutex_destroy`,其行为是未定义的** 2497* **注意:C API中的同一个ffrt_mutexattr_t只能调用一次`ffrt_mutexattr_init`和`ffrt_mutexattr_destroy`,重复调用其行为是未定义的** 2498* **注意:用户需要在调用`ffrt_mutex_init`之后和调用`ffrt_mutex_destroy`之前显示调用`ffrt_mutexattr_destroy`** 2499* **注意:在`ffrt_mutex_destroy`之后再对ffrt_mutex_t进行访问,其行为是未定义的** 2500 2501#### 样例 2502 2503```{.c} 2504#include <stdio.h> 2505#include "ffrt.h" 2506 2507typedef struct { 2508 int* sum; 2509 ffrt_mutex_t* mtx; 2510} tuple; 2511 2512void func(void* arg) 2513{ 2514 tuple* t = (tuple*)arg; 2515 2516 int ret = ffrt_mutex_lock(t->mtx); 2517 if (ret != ffrt_success) { 2518 printf("error\n"); 2519 } 2520 (*t->sum)++; 2521 ret = ffrt_mutex_unlock(t->mtx); 2522 if (ret != ffrt_success) { 2523 printf("error\n"); 2524 } 2525} 2526 2527typedef struct { 2528 ffrt_function_header_t header; 2529 ffrt_function_t func; 2530 ffrt_function_t after_func; 2531 void* arg; 2532} c_function; 2533 2534static void ffrt_exec_function_wrapper(void* t) 2535{ 2536 c_function* f = (c_function*)t; 2537 if (f->func) { 2538 f->func(f->arg); 2539 } 2540} 2541 2542static void ffrt_destroy_function_wrapper(void* t) 2543{ 2544 c_function* f = (c_function*)t; 2545 if (f->after_func) { 2546 f->after_func(f->arg); 2547 } 2548} 2549 2550#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 2551static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 2552 const ffrt_function_t after_func, void* arg) 2553{ 2554 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 2555 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 2556 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 2557 f->header.exec = ffrt_exec_function_wrapper; 2558 f->header.destroy = ffrt_destroy_function_wrapper; 2559 f->func = func; 2560 f->after_func = after_func; 2561 f->arg = arg; 2562 return (ffrt_function_header_t*)f; 2563} 2564 2565static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 2566 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 2567{ 2568 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 2569} 2570 2571void ffrt_mutex_task() 2572{ 2573 int sum = 0; 2574 ffrt_mutex_t mtx; 2575 tuple t = {&sum, &mtx}; 2576 int ret = ffrt_mutex_init(&mtx, NULL); 2577 if (ret != ffrt_success) { 2578 printf("error\n"); 2579 } 2580 for (int i = 0; i < 10; i++) { 2581 ffrt_submit_c(func, NULL, &t, NULL, NULL, NULL); 2582 } 2583 ffrt_mutex_destroy(&mtx); 2584 ffrt_wait(); 2585 printf("sum = %d", sum); 2586} 2587 2588void ffrt_recursive_mutex_task() 2589{ 2590 int sum = 0; 2591 int ret = 0; 2592 ffrt_mutexattr_t attr; 2593 ffrt_mutex_t mtx; 2594 ret = ffrt_mutexattr_init(&attr); 2595 if (ret != ffrt_success) { 2596 printf("mutexattr init error\n"); 2597 } 2598 ret = ffrt_mutexattr_settype(&attr, ffrt_mutex_recursive); 2599 if (ret != ffrt_success) { 2600 printf("mutexattr settype error\n"); 2601 } 2602 tuple t = {&sum, &mtx}; 2603 int ret = ffrt_mutex_init(&mtx, &attr); 2604 if (ret != ffrt_success) { 2605 printf("error\n"); 2606 } 2607 for (int i = 0; i < 10; i++) { 2608 ffrt_submit_c(func, NULL, &t, NULL, NULL, NULL); 2609 } 2610 ffrt_mutexattr_destory(&attr); 2611 ffrt_mutex_destroy(&mtx); 2612 ffrt_wait(); 2613 printf("sum = %d", sum); 2614} 2615 2616int main(int narg, char** argv) 2617{ 2618 int r; 2619 /* mutex */ 2620 ffrt_submit_c(ffrt_mutex_task, NULL, NULL, NULL, NULL, NULL); 2621 ffrt_wait(); 2622 /* recursive mutex */ 2623 ffrt_submit_c(ffrt_recursive_mutex_task, NULL, NULL, NULL, NULL, NULL); 2624 ffrt_wait(); 2625 return 0; 2626} 2627``` 2628 2629预期输出为 2630 2631``` 2632sum=10 2633``` 2634 2635* 该例子为功能示例,实际中并不鼓励这样使用 2636 2637### ffrt_rwlock_t 2638<hr/> 2639* FFRT提供的类似pthread rwlock 的性能实现 2640 2641#### 声明 2642 2643```{.cpp} 2644typedef enum { 2645 ffrt_error = -1, 2646 ffrt_success = 0, 2647 ffrt_error_nomem = ENOMEM, 2648 ffrt_error_timedout = ETIMEDOUT, 2649 ffrt_error_busy = EBUSY, 2650 ffrt_error_inval = EINVAL 2651} ffrt_error_t; 2652 2653struct ffrt_rwlock_t; 2654 2655int ffrt_rwlock_init(ffrt_rwlock_t* rwlock, const ffrt_rwlockattr_t* attr); 2656int ffrt_rwlock_wrlock(ffrt_rwlock_t* rwlock); 2657int ffrt_rwlock_trywrlock(ffrt_rwlock_t* rwlock); 2658int ffrt_rwlock_rdlock(ffrt_rwlock_t* rwlock); 2659int ffrt_rwlock_tryrdlock(ffrt_rwlock_t* rwlock); 2660int ffrt_rwlock_unlock(ffrt_rwlock_t* rwlock); 2661int ffrt_rwlock_destroy(ffrt_rwlock_t* rwlock); 2662``` 2663 2664#### 参数 2665 2666`attr` 2667 2668* 当前FFRT只支持基础类型的rwlock,因此attr必须为空指针 2669 2670`rwlock` 2671 2672* 指向所操作的读写锁的指针 2673 2674#### 返回值 2675 2676* 若成功则为 ffrt_success ,否则发生错误 2677 2678#### 描述 2679* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 2680* 该功能能够避免pthread传统的pthread_rwlock_t 在抢不到锁时陷入内核的问题,在使用得当的条件下将会有更好的性能 2681* **注意:目前暂不支持递归和定时功能** 2682* **注意:C API中的ffrt_rwlock_t需要用户调用`ffrt_rwlock_init`和`ffrt_rwlock_destroy`显式创建和销毁,而C++ API无需该操作** 2683* **注意:C API中的ffrt_rwlock_t对象的置空和销毁由用户完成,对同一个ffrt_rwlock_t仅能调用一次`ffrt_rwlock_destroy`,重复对同一个ffrt_rwlock_t调用`ffrt_rwlock_destroy`,其行为是未定义的** 2684* **注意:在`ffrt_rwlock_destroy`之后再对ffrt_rwlock_t进行访问,其行为是未定义的** 2685 2686#### 样例 2687 2688```{.c} 2689#include <stdio.h> 2690#include "ffrt_inner.h" 2691 2692typedef struct { 2693 int* sum; 2694 ffrt_rwlock_t* mtx; 2695} tuple; 2696 2697void func1(void* arg) 2698{ 2699 tuple* t = (tuple*)arg; 2700 2701 int ret = ffrt_rwlock_wrlock(t->mtx); 2702 if (ret != ffrt_success) { 2703 printf("error\n"); 2704 } 2705 (*t->sum)++; 2706 ret = ffrt_rwlock_unlock(t->mtx); 2707 if (ret != ffrt_success) { 2708 printf("error\n"); 2709 } 2710} 2711 2712void func2(void* arg) 2713{ 2714 tuple* t = (tuple*)arg; 2715 2716 int ret = ffrt_rwlock_rdlock(t->mtx); 2717 if (ret != ffrt_success) { 2718 printf("error\n"); 2719 } 2720 printf("sum is %d\n", *t->sum); 2721 ret = ffrt_rwlock_unlock(t->mtx); 2722 if (ret != ffrt_success) { 2723 printf("error\n"); 2724 } 2725} 2726 2727typedef struct { 2728 ffrt_function_header_t header; 2729 ffrt_function_t func; 2730 ffrt_function_t after_func; 2731 void* arg; 2732} c_function; 2733 2734static void ffrt_exec_function_wrapper(void* t) 2735{ 2736 c_function* f = (c_function*)t; 2737 if (f->func) { 2738 f->func(f->arg); 2739 } 2740} 2741 2742static void ffrt_destroy_function_wrapper(void* t) 2743{ 2744 c_function* f = (c_function*)t; 2745 if (f->after_func) { 2746 f->after_func(f->arg); 2747 } 2748} 2749 2750#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 2751static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 2752 const ffrt_function_t after_func, void* arg) 2753{ 2754 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 2755 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 2756 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 2757 f->header.exec = ffrt_exec_function_wrapper; 2758 f->header.destroy = ffrt_destroy_function_wrapper; 2759 f->func = func; 2760 f->after_func = after_func; 2761 f->arg = arg; 2762 return (ffrt_function_header_t*)f; 2763} 2764 2765static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 2766 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 2767{ 2768 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 2769} 2770 2771void ffrt_rwlock_task(void* arg) 2772{ 2773 int sum = 0; 2774 ffrt_rwlock_t mtx; 2775 tuple t = {&sum, &mtx}; 2776 int ret = ffrt_rwlock_init(&mtx, NULL); 2777 if (ret != ffrt_success) { 2778 printf("error\n"); 2779 } 2780 for (int i = 0; i < 10; i++) { 2781 ffrt_submit_c(func1, NULL, &t, NULL, NULL, NULL); 2782 for (int j = 0; j < 5; j++) { 2783 ffrt_submit_c(func2, NULL, &t, NULL, NULL, NULL); 2784 } 2785 } 2786 ffrt_rwlock_destroy(&mtx); 2787 ffrt_wait(); 2788 printf("sum = %d", sum); 2789} 2790 2791int main(int narg, char** argv) 2792{ 2793 int r; 2794 ffrt_submit_c(ffrt_rwlock_task, NULL, NULL, NULL, NULL, NULL); 2795 ffrt_wait(); 2796 return 0; 2797} 2798``` 2799 2800预期输出为 2801 2802``` 2803sum=10 2804``` 2805 2806* 该例子为功能示例,实际中并不鼓励这样使用 2807 2808 2809### ffrt_cond_t 2810<hr/> 2811 2812* FFRT提供的类似pthread 信号量的性能实现 2813 2814#### 声明 2815 2816```{.c} 2817typedef enum { 2818 ffrt_error = -1, 2819 ffrt_success = 0, 2820 ffrt_error_nomem = ENOMEM, 2821 ffrt_error_timedout = ETIMEDOUT, 2822 ffrt_error_busy = EBUSY, 2823 ffrt_error_inval = EINVAL 2824} ffrt_error_t; 2825 2826struct ffrt_cond_t; 2827typedef enum { 2828 ffrt_clock_realtime = CLOCK_REALTIME, 2829 ffrt_clock_monotonic = CLOCK_MONOTONIC 2830} ffrt_clockid_t; 2831 2832int ffrt_condattr_init(ffrt_condattr_t* attr); 2833int ffrt_condattr_destroy(ffrt_condattr_t* attr); 2834int ffrt_condattr_setclock(ffrt_condattr_t* attr, ffrt_clockid_t clock); 2835int ffrt_condattr_getclock(const ffrt_condattr_t* attr, ffrt_clockid_t* clock); 2836 2837int ffrt_cond_init(ffrt_cond_t* cond, const ffrt_condattr_t* attr); 2838int ffrt_cond_signal(ffrt_cond_t* cond); 2839int ffrt_cond_broadcast(ffrt_cond_t* cond); 2840int ffrt_cond_wait(ffrt_cond_t*cond, ffrt_mutex_t* mutex); 2841int ffrt_cond_timedwait(ffrt_cond_t* cond, ffrt_mutex_t* mutex, const struct timespec* time_point); 2842int ffrt_cond_destroy(ffrt_cond_t* cond); 2843``` 2844 2845#### 参数 2846 2847`cond` 2848 2849* 指向所操作的信号量的指针 2850 2851`attr` 2852 2853* 属性设定,空指针表示使用默认属性 2854 2855`mutex` 2856 2857* 指向要在阻塞期间解锁的互斥锁的指针 2858 2859`time_point` 2860 2861* 指向指定等待时限时间的对象的指针 2862 2863 2864#### 返回值 2865 2866* 若成功则为 ffrt_success,若在锁定互斥前抵达时限则为 ffrt_error_timedout 2867 2868#### 描述 2869* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 2870* 该功能能够避免传统的pthread_cond_t在条件不满足时陷入内核的问题,在使用得当的条件下将会有更好的性能 2871* **注意:C API中的ffrt_cond_t需要用户调用`ffrt_cond_init`和`ffrt_cond_destroy`显式创建和销毁,而C++ API中依赖构造和析构自动完成** 2872* **注意:C API中的ffrt_cond_t对象的置空和销毁由用户完成,对同一个ffrt_cond_t仅能调用一次`ffrt_cond_destroy`,重复对同一个ffrt_cond_t调用`ffrt_cond_destroy`,其行为是未定义的** 2873* **注意:在`ffrt_cond_destroy`之后再对ffrt_cond_t进行访问,其行为是未定义的** 2874 2875#### 样例 2876 2877```{.c} 2878#include <stdio.h> 2879#include "ffrt.h" 2880 2881typedef struct { 2882 ffrt_cond_t* cond; 2883 int* a; 2884 ffrt_mutex_t* lock_; 2885} tuple; 2886 2887void func1(void* arg) 2888{ 2889 tuple* t = (tuple*)arg; 2890 int ret = ffrt_mutex_lock(t->lock_); 2891 if (ret != ffrt_success) { 2892 printf("error\n"); 2893 } 2894 while (*t->a != 1) { 2895 ret = ffrt_cond_wait(t->cond, t->lock_); 2896 if (ret != ffrt_success) { 2897 printf("error\n"); 2898 } 2899 } 2900 ret = ffrt_mutex_unlock(t->lock_); 2901 if (ret != ffrt_success) { 2902 printf("error\n"); 2903 } 2904 printf("a = %d", *(t->a)); 2905} 2906 2907void func2(void* arg) 2908{ 2909 tuple* t = (tuple*)arg; 2910 int ret = ffrt_mutex_lock(t->lock_); 2911 if (ret != ffrt_success) { 2912 printf("error\n"); 2913 } 2914 *(t->a) = 1; 2915 ret = ffrt_cond_signal(t->cond); 2916 if (ret != ffrt_success) { 2917 printf("error\n"); 2918 } 2919 ret = ffrt_mutex_unlock(t->lock_); 2920 if (ret != ffrt_success) { 2921 printf("error\n"); 2922 } 2923} 2924 2925typedef struct { 2926 ffrt_function_header_t header; 2927 ffrt_function_t func; 2928 ffrt_function_t after_func; 2929 void* arg; 2930} c_function; 2931 2932static void ffrt_exec_function_wrapper(void* t) 2933{ 2934 c_function* f = (c_function*)t; 2935 if (f->func) { 2936 f->func(f->arg); 2937 } 2938} 2939 2940static void ffrt_destroy_function_wrapper(void* t) 2941{ 2942 c_function* f = (c_function*)t; 2943 if (f->after_func) { 2944 f->after_func(f->arg); 2945 } 2946} 2947 2948#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 2949static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 2950 const ffrt_function_t after_func, void* arg) 2951{ 2952 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 2953 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 2954 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 2955 f->header.exec = ffrt_exec_function_wrapper; 2956 f->header.destroy = ffrt_destroy_function_wrapper; 2957 f->func = func; 2958 f->after_func = after_func; 2959 f->arg = arg; 2960 return (ffrt_function_header_t*)f; 2961} 2962 2963static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 2964 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 2965{ 2966 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 2967} 2968 2969void ffrt_cv_task() 2970{ 2971 ffrt_cond_t cond; 2972 int ret = ffrt_cond_init(&cond, NULL); 2973 if (ret != ffrt_success) { 2974 printf("error\n"); 2975 } 2976 int a = 0; 2977 ffrt_mutex_t lock_; 2978 tuple t = {&cond, &a, &lock_}; 2979 ret = ffrt_mutex_init(&lock_, NULL); 2980 if (ret != ffrt_success) { 2981 printf("error\n"); 2982 } 2983 ffrt_submit_c(func1, NULL, &t, NULL, NULL, NULL); 2984 ffrt_submit_c(func2, NULL, &t, NULL, NULL, NULL); 2985 ffrt_wait(); 2986 ffrt_cond_destroy(&cond); 2987 ffrt_mutex_destroy(&lock_); 2988} 2989 2990int main(int narg, char** argv) 2991{ 2992 ffrt_submit_c(ffrt_cv_task, NULL, NULL, NULL, NULL, NULL); 2993 ffrt_wait(); 2994 return 0; 2995} 2996``` 2997 2998预期输出为: 2999 3000``` 3001a=1 3002``` 3003 3004* 该例子为功能示例,实际中并不鼓励这样使用 3005 3006## 杂项 3007 3008### ffrt_usleep 3009 3010<hr/> 3011 3012* FFRT提供的类似C11 sleep和linux usleep的性能实现 3013 3014#### 声明 3015 3016```{.c} 3017int ffrt_usleep(uint64_t usec); 3018``` 3019 3020#### 参数 3021 3022`usec` 3023 3024* 睡眠的us数 3025 3026#### 返回值 3027 3028* 不涉及 3029 3030#### 描述 3031* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 3032* 该功能能够避免传统的sleep 睡眠时陷入内核的问题,在使用得当的条件下将会有更好的性能 3033 3034#### 样例 3035 3036```{.c} 3037#include <time.h> 3038#include <stdio.h> 3039#include "ffrt.h" 3040 3041void func(void* arg) 3042{ 3043 printf("Time: %s", ctime(&(time_t){time(NULL)})); 3044 ffrt_usleep(2000000); // 睡眠 2 秒 3045 printf("Time: %s", ctime(&(time_t){time(NULL)})); 3046} 3047 3048typedef struct { 3049 ffrt_function_header_t header; 3050 ffrt_function_t func; 3051 ffrt_function_t after_func; 3052 void* arg; 3053} c_function; 3054 3055static void ffrt_exec_function_wrapper(void* t) 3056{ 3057 c_function* f = (c_function*)t; 3058 if (f->func) { 3059 f->func(f->arg); 3060 } 3061} 3062 3063static void ffrt_destroy_function_wrapper(void* t) 3064{ 3065 c_function* f = (c_function*)t; 3066 if (f->after_func) { 3067 f->after_func(f->arg); 3068 } 3069} 3070 3071#define FFRT_STATIC_ASSERT(cond, msg) int x(int static_assertion_##msg[(cond) ? 1 : -1]) 3072static inline ffrt_function_header_t* ffrt_create_function_wrapper(const ffrt_function_t func, 3073 const ffrt_function_t after_func, void* arg) 3074{ 3075 FFRT_STATIC_ASSERT(sizeof(c_function) <= ffrt_auto_managed_function_storage_size, 3076 size_of_function_must_be_less_than_ffrt_auto_managed_function_storage_size); 3077 c_function* f = (c_function*)ffrt_alloc_auto_managed_function_storage_base(ffrt_function_kind_general); 3078 f->header.exec = ffrt_exec_function_wrapper; 3079 f->header.destroy = ffrt_destroy_function_wrapper; 3080 f->func = func; 3081 f->after_func = after_func; 3082 f->arg = arg; 3083 return (ffrt_function_header_t*)f; 3084} 3085 3086static inline void ffrt_submit_c(ffrt_function_t func, const ffrt_function_t after_func, 3087 void* arg, const ffrt_deps_t* in_deps, const ffrt_deps_t* out_deps, const ffrt_task_attr_t* attr) 3088{ 3089 ffrt_submit_base(ffrt_create_function_wrapper(func, after_func, arg), in_deps, out_deps, attr); 3090} 3091 3092int main(int narg, char** argv) 3093{ 3094 ffrt_submit_c(func, NULL, NULL, NULL, NULL, NULL); 3095 ffrt_wait(); 3096 return 0; 3097} 3098``` 3099 3100### ffrt_yield 3101<hr/> 3102 3103* 当前task 主动让出CPU 执行资源,让其他可以被执行的task 先执行,如果没有其他可被执行的task,yield 无效 3104 3105#### 声明 3106 3107```{.cpp} 3108void ffrt_yield(); 3109``` 3110 3111#### 参数 3112 3113* 不涉及 3114 3115#### 返回值 3116 3117* 不涉及 3118 3119#### 描述 3120* 该接口只能在FFRT task 内部调用,在FFRT task 外部调用存在未定义的行为 3121* 此函数的确切行为取决于实现,特别是使用中的FFRT 调度程序的机制和系统状态 3122 3123#### 样例 3124 3125* 省略 3126 3127## 维测 3128 3129### 长耗时任务监测 3130 3131#### 描述 3132* 长耗时任务打印机制 3133 当任务执行时间超过一秒时,会触发一次堆栈打印,后续该任务堆栈打印频率调整为一分钟。连续打印十次后,打印频率调整为十分钟。再触发十次打印后,打印频率固定为三十分钟。 3134* 该机制的堆栈打印调用的是DFX的 `GetBacktraceStringByTid` 接口,该接口会向阻塞线程发送抓栈信号,触发中断并抓取调用栈返回。 3135 3136#### 样例 3137在对应进程日志中搜索 `RecordSymbolAndBacktrace` 关键字,对应的日志示例如下: 3138 3139``` 3140W C01719/ffrt: 60500:RecordSymbolAndBacktrace:159 Tid[16579] function occupies worker for more than [1]s. 3141W C01719/ffrt: 60501:RecordSymbolAndBacktrace:164 Backtrace: 3142W C01719/ffrt: #00 pc 00000000000075f0 /system/lib64/module/file/libhash.z.so 3143W C01719/ffrt: #01 pc 0000000000008758 /system/lib64/module/file/libhash.z.so 3144W C01719/ffrt: #02 pc 0000000000012b98 /system/lib64/module/file/libhash.z.so 3145W C01719/ffrt: #03 pc 000000000002aaa0 /system/lib64/platformsdk/libfilemgmt_libn.z.so 3146W C01719/ffrt: #04 pc 0000000000054b2c /system/lib64/platformsdk/libace_napi.z.so 3147W C01719/ffrt: #05 pc 00000000000133a8 /system/lib64/platformsdk/libuv.so 3148W C01719/ffrt: #06 pc 00000000000461a0 /system/lib64/chipset-sdk/libffrt.so 3149W C01719/ffrt: #07 pc 0000000000046d44 /system/lib64/chipset-sdk/libffrt.so 3150W C01719/ffrt: #08 pc 0000000000046a6c /system/lib64/chipset-sdk/libffrt.so 3151W C01719/ffrt: #09 pc 00000000000467b0 /system/lib64/chipset-sdk/libffrt.so 3152``` 3153该维测会打印出worker上执行时间超过阈值的任务堆栈、worker线程号、执行时间,请自行根据堆栈找对应组件确认阻塞原因。 3154 3155 3156#### 注意事项 3157如果代码中存在 `sleep` 等会被中断唤醒的阻塞,用户需主动接收该阻塞的返回值,并重新调用。 3158示例如下: 3159``` 3160unsigned int leftTime = sleep(10); 3161while (leftTime != 0) { 3162 leftTime = sleep(leftTime); 3163} 3164``` 3165 3166# 部署 3167 3168## 部署方式 3169<img src="images/image-20230120153923679.png" alt="image-20230120153923679" style="zoom:67%;" /> 3170 3171* FFRT的部署依赖FFRT动态库libffrt.so和一组header头文件 3172 3173* FFRT的头文件为`ffrt.h`,内部包含了C++ API,C API和C base API 3174 * ffrt.h 定义为: 3175 ```{.cpp} 3176 #ifndef FFRT_API_FFRT_H 3177 #define FFRT_API_FFRT_H 3178 #ifdef __cplusplus 3179 #include "cpp/task.h" 3180 #include "cpp/deadline.h" 3181 #include "cpp/sys_event.h" 3182 #include "cpp/mutex.h" 3183 #include "cpp/condition_variable.h" 3184 #include "cpp/sleep.h" 3185 #include "cpp/thread.h" 3186 #include "cpp/config.h" 3187 #include "cpp/future.h" 3188 #else 3189 #include "c/task.h" 3190 #include "c/deadline.h" 3191 #include "c/sys_event.h" 3192 #include "c/mutex.h" 3193 #include "c/condition_variable.h" 3194 #include "c/sleep.h" 3195 #include "c/thread.h" 3196 #include "c/config.h" 3197 #endif 3198 #endif 3199 ``` 3200 * C base API定义示例: 3201 ```{.cpp} 3202 void ffrt_submit_base(ffrt_function_header_t* func, ...); 3203 int ffrt_mutex_init(...); 3204 ``` 3205 * C API定义示例: 3206 ```{.cpp} 3207 static inline void ffrt_submit(ffrt_function_t func, void* arg, ...) 3208 { 3209 ffrt_submit_base(ffrt_create_function_wrapper(func, arg), ...); 3210 } 3211 ``` 3212 * C++ API定义示例: 3213 ```{.cpp} 3214 namespace ffrt { 3215 static inline void submit(std::function& func, ...) 3216 { 3217 ffrt_submit_base(ffrt_create_function_wrapper(func), ...); 3218 } 3219 struct mutex { 3220 mutex() { 3221 ffrt_mutex_init(...); 3222 ... 3223 }; 3224 } 3225 ``` 3226 3227* **出于易用性方面的考虑,除非必要,强烈建议你使用C++ API,调用C API将会使你的代码非常臃肿或者更容易产生资源未释放问题** 3228 3229| 需求列表 | 3230| ------------------------------------------------------------ | 3231| 需求1:ABI兼容性,在NDK场景中由于用户的编译环境与FFRT的编译环境不同,使用C++接口可能存在ABI兼容性问题,要有解决方案 | 3232| 需求2:用户的编译环境为纯C编译环境,不想因为引入FFRT而引入C++元素的场景,要有解决方案 | 3233| 需求3:易用性,尽可能让接口简单易用,用户少出错 | 3234 3235* 对于需求1,通过在用户调用的C++接口和FFRT的实现之间增加一个C base API层,并基于头文件方式将API中的C++的元素编译到用户的so,从而解决ABI兼容的问题 3236* 对于需求2,可以通过C Base API解决 3237* 对于需求3,建议用户尽可能使用C++ API,以避免C API固有的资源未初始化/释放、参数冗长等问题,对于不得不使用C API的场景,FFRT仍然支持用户使用C API和C base API 3238 3239 3240 3241<br/> 3242<br/> 3243 3244<hr/> 3245# 实战指南 3246 3247## 步骤1: 分析应用 3248 3249使用 FFRT 并行编程的第一步便是您需要了解你的应用。 3250 3251【建议1】:使用 Task 梳理应用的流程。 3252 3253使用 Task 梳理应用的流程,并且尽可能使用数据来表达 Task 之间的依赖。当然如果两个 Task 之间如无数据依赖,仅存在控制依赖,您也可以创建一个虚拟的(或者逻辑上的)数据依赖。 3254 3255<img src="images/image-20220926152831526.png" style="zoom:70%" /> 3256 3257<center>AIRAW 的数据流图</center> 3258 3259基于数据流图,可以很容易判定出哪些 Task 是可以并发的,比如,Slice0 的 NPU Task 和 Slice1 的 GPU Pre Task 是可以并发的,因为它们没有任何依赖。 3260 3261反过来,如果并发的效果不理想,也可以通过调整数据流图来优化并发。例如,假如上图中GPU Pre Task 执行时间有很大波动,但平均耗时略小于 NPU Task,会出现某些时刻 GPU Pre Task 拖慢整个执行时间。此时,如果将 GPU Pre Task 的输出 Buffer 改成3个(或者更多)的 Buffer ,可以增加 GPU Pre Task 和 NPU Task 的并发机会,将降低波动对总执行时间的影响。 3262 3263 3264 3265【建议2】:这里不用太担心 Task 大或小的问题,因为 FFRT 允许你在 Task 内部继续拆分 SubTask,可以逐步细化。 3266 3267下图中,第一次画数据流图时,可以不将 FaceDirection 和 UpdateExistFaceImageInfo 两个 Task 展开,可以逐步细化。 3268 3269<img src="images/image-20220926153003884.png" style="zoom:70%" /> 3270 3271<center>某拍照业务的数据流图</center> 3272 3273【建议3】:上述流程图或者数据流图不要求是静态图(即 Task 数量和 Task 依赖关系是固定的) 3274 3275FFRT 允许动态提交 Task ,在编程界面上不体现图的概念,FFRT 内部会根据Task 之间的依赖关系动态调整数据流图的节点。 3276 3277 3278【建议4】:尽可能对应用做热点分析 3279 3280如果是对存量代码的 FFRT 化改造,那么,使用 System Trace 这类工具能帮助您聚焦在性能热点上,比如下图可以很容易知道当前的性能Bound,在分析数据流图时,可以重点关注这些热点任务。 3281 3282<img src="images/image-20220926153030993.png" style="zoom:70%" /> 3283 3284<center>某业务的System Trace</center> 3285 3286## 步骤2: 并行化应用 3287 3288【建议1】:不要直接使用线程,使用 FFRT 提交Task。 3289 3290如果应用中有明显的数据依赖关系,那么 FFRT 将会非常适合;最差的情况是应用没有数据依赖或难以并行(如果真的存在),您仍然可以把 FFRT 当做一个高效的进程级线程池、或者协程库去使用它,但非常不建议你继续创建线程。 3291 3292 3293 3294【建议2】:Task 最好被建模为纯函数。 3295 3296纯函数是指其执行没有副作用,例如更新全局数据结构。每个任务都依赖于其输入/输出签名来连接到其他任务。 3297 3298请注意,即使 Task 不是"纯"的,FFRT 仍然适用。只要任务使用的数据依赖或者锁足以保证正确执行,FFRT 就能正常工作。 3299 3300 3301 3302【建议3】:尽可能尝试通过 inDeps/outDeps 表达依赖,而不是使用 ffrt::wait()。 3303 3304这是因为 FFRT 跟踪和处理 inDeps/outDeps 比调用显式 ffrt::wait() 函数更自然、更便宜。 3305 3306 3307 3308【建议4】:注意 Task 粒度 3309 3310以适当的粒度提交任务至关重要:目前每个任务的调度开销约为 10 us。如果 Task 的粒度非常小,那么开销的百分比将会很高。FFRT 会继续基于软硬件的方式优化调度开销。 3311 3312 3313 3314【建议5】:尽可能使用 FFRT 原语 3315 3316如果需要mutex、sleep、异步 I/O,请使用 FFRT 原语,而不是使用OS 提供的版本。因为这些 FFRT 提供的实现在与 FFRT 配合时开销将会更小。 3317 3318 3319 3320【建议6】:在需要时,使用 ffrt::wait() 确保栈变量的生命周期。 3321 3322如果子任务使用驻留在父任务栈上的数据,则父任务应避免在子任务执行完成前返回。在父任务的末尾添加 ffrt::wait() 可以解决这个问题。 3323 3324 3325 3326## 步骤3: 优化应用 3327 3328【建议1】:基于System Trace,分析并行是否符合预期 3329 3330FFRT 已经内置 SysTrace 支持,默认以txx.xx表示,非常有利于分析 Task 粒度和并发度。未来,在性能分析和维测方面将继续增强。 3331 3332<img src="images/image-20220926153209875.png" style="zoom:70%" /> 3333 3334【建议2】:对于耗时的 Task,尝试提交 SubTask,提升应用的并行度 3335 3336 3337【建议3】:在合适的场景,使用 Deadline 调度,实现能效和性能的平衡 3338 3339方案正在验证中,待更新。 3340 3341 3342 3343## 样例: CameraHal QuickThumb 3344 3345### 步骤1: 分析应用 3346 3347<img src="images/image-20220926153255824.png" style="zoom:70%" /> 3348 33491) QuickThumb 是 CameraHal 中实现的对一张图片进行缩小的功能,整体运行时间约30 us; 3350 33512) 在实现上分为两层循环,外层的一次循环输出1行,内层的1次循环输出该行的m列; 3352 33533) 在划分 Task 时,一种简单的做法是1行的处理就是1个Task。 3354 3355### 步骤2: 并行化应用 3356<img src="images/image-20220926153509205.png" style="zoom:100%" /> 3357 3358 1) 根据纯函数的定义,该 Task 的输入输出的数据是非常之多的,因此,这个场景下使用更宽泛的纯函数的定义,只需要考虑在 Task 内部会被写,但是却被定义在 Task 外部的变量即可; 3359 33602) 按照上面的原则,将 py/puv 的定义移到 Task 内部可避免多个 Task 同时写 py/puv 的问题; 3361 33623) s32r 的处理可以有两种方式,都能得到正确的功能:a. 保持定义在Task 外部,但作为Task 的输出依赖;b. 将s32r定义在Task 内部,作为Task的私有变量。显然,b 方案能够获得更好的性能 3363 3364 3365 3366### 步骤3: 优化应用 3367 3368通过System Trace,会发现上述改造方案的Task 粒度较小,大约单个Task 耗时在5us左右,因此,扩大Task的粒度为32行处理,得到最终的并行结果,下图为使用4小核和3中核的结果。 3369 3370<img src="images/image-20220926153603572.png" style="zoom:100%" /> 3371 3372 3373 3374## 样例: Camera AIRAW 3375 3376### 步骤1: 分析应用 3377<img src="images/image-20220926153611121.png" style="zoom:70%" /> 3378 3379AIRAW 的处理包括了3个处理步骤,在数据面上,可以按slice 进行切分,在不考虑pre_outbuf和npu_outbuf 在slice间复用的情况下,数据流图如上图所示。 3380 3381<img src="images/image-20220926152831526.png" style="zoom:70%" /> 3382 3383为了节省运行过程中的内存占用,但不影响整体性能,可以只保留2个pre_outbuf和2个npu_outbuf。 3384 3385`为此付出的代价是:Buffer 的复用产生了slice3 的GPU Pre Task 依赖slice1 的NPU Task 完成,俗称反压,又称生产者依赖关系。但是,如果您使用 FFRT 来实现,将会是非常自然而高效的` 3386 3387### 步骤2: 并行化应用 3388 3389```{.cpp} 3390constexpr uint32_t SLICE_NUM = 24; 3391constexpr uint32_t BUFFER_NUM = 2; 3392 3393int input[SLICE_NUM]; // input is split into SLICE_NUM slices 3394int pre_outbuf[BUFFER_NUM]; // gpu pre task output buffers 3395int npu_outbuf[BUFFER_NUM]; // npu output buffers 3396int output[SLICE_NUM]; // output is split into SLICE_NUM slices 3397 3398for (uint32_t i = 0; i < SLICE_NUM; i++) { 3399 uint32_t buf_id = i % BUFFER_NUM; 3400 ffrt::submit(gpuPreTask, {input + i}, {pre_outbuf + buf_id}); 3401 ffrt::submit(npuTask, {pre_outbuf + buf_id}, {npu_outbuf + buf_id}); 3402 ffrt::submit(gpuPostTask, {npu_outbuf + buf_id}, {output + i}); 3403} 3404 3405ffrt::wait(); 3406``` 3407 3408### 步骤3: 优化应用 3409 3410<img src="images/image-20220926153825527.png" style="zoom:100%" /> 3411 3412基于以上实现,从上面的Trace中我们看到,NPU 的硬件时间被完全用满,系统端到端性能达到最优,而付出的开发代价将会比GCD 或多线程小的多。 3413 3414 3415 3416## 样例: Camera FaceStory 3417 3418### 步骤1: 分析应用 3419 3420<img src="images/image-20220926153003884.png" style="zoom:70%" /> 3421 3422 3423 3424### 步骤2: 并行化应用 3425 3426<img src="images/image-20220926153906692.png" style="zoom:100%" /> 3427 3428代码改造样例 3429 34301) 该场景输出存量代码迁移,只需将原先串行的代码以Task的方式提交即可; 3431 34322) 过程中需要考虑Data Race和数据生命周期; 3433 34343) 先提交大的Task,根据需要逐步拆分SubTask。 3435 3436 3437 3438### 步骤3: 优化应用 3439 3440<img src="images/image-20220926153030993.png" style="zoom:100%" /> 3441 3442<center>原始System Trace</center> 3443 3444<img src="images/image-20220926153925963.png" style="zoom:100%" /> 3445 3446 3447 3448 3449<center>改造后System Trace</center> 3450 3451并行化的收益来自于: 3452 34531) 多分支或循环并发,实现CPU前后处理和NPU的并发 3454 34552) 子任务拆分,进一步提升并行度 3456 34573) 基于数据流图优化CPU L2 Cache Flush频次 3458 34594) NPU Worker Thread实时优先级调整,后续FFRT中考虑独立出XPU调度Worker来保证实时性 3460 34615) 在未来,模型加载使用FFRT submit,模型加载内部也可以使用submit来继续拆分,能够优化整个业务的启动耗时。 3462 3463<br/> 3464<br/> 3465<hr/> 3466 3467 3468 3469# 使用建议 3470 3471## 建议1: 函数化 3472 3473**基本思想:计算过程函数化** 3474 3475* 程序过程各步骤以函数封装表达,函数满足类纯函数特性 3476* 无全局数据访问 3477* 无内部状态保留 3478* 通过ffrt::submit()接口以异步任务方式提交函数执行 3479* 将函数访问的数据对象以及访问方式在ffrt::submit()接口中的in_deps/out_deps参数表达 3480* 程序员通过inDeps/outDeps参数表达任务间依赖关系以保证程序执行的正确性 3481 3482> 做到纯函数的好处在于:1. 能够最大化挖掘并行度,2.避免DataRace和锁的问题 3483 3484 3485 3486**在实际中,可以根据场景放松纯函数的约束,但前提是:** 3487 3488* 确定添加的in_deps/out_deps可确保程序正确执行 3489* 通过FFRT提供的锁机制保护对全局变量的访问 3490 3491 3492 3493## 建议2: 注意任务粒度 3494 3495* FFRT管理和调度异步任务执行有调度开销,任务粒度(执行时间)需匹配调度开销 3496* 大量小粒度任务造成FFRT调度开销占比增加,性能下降,解决方法: 3497 * 将多个小粒度任务聚合为大粒度任务一次发送给FFRT异步执行 3498 * 同步方式执行小粒度任务,不发送给FFRT异步执行。需注意和异步任务之间的数据同步问题,在需要同步的地方插入ffrt::wait() 3499 * 下面的例子中,fib_ffrt2会比fib_ffrt1拥有更好的性能 3500 3501 ```{.cpp} 3502 #include "ffrt.h" 3503 void fib_ffrt1(int x, int& y) 3504 { 3505 if (x <= 1) { 3506 y = x; 3507 } else { 3508 int y1, y2; 3509 ffrt::submit([&] {fib_ffrt1(x - 1, y1);}, {&x}, {&y1} ); 3510 ffrt::submit([&] {fib_ffrt1(x - 2, y2);}, {&x}, {&y2} ); 3511 ffrt::submit([&] {y = y1 + y2;}, {&y1, &y2}, {} ); 3512 ffrt::wait(); 3513 } 3514 } 3515 3516 void fib_ffrt2(int x, int& y) 3517 { 3518 if (x <= 1) { 3519 y = x; 3520 } else { 3521 int y1, y2; 3522 ffrt::submit([&] {fib_ffrt2(x - 1, y1);}, {&x}, {&y1} ); 3523 ffrt::submit([&] {fib_ffrt2(x - 2, y2);}, {&x}, {&y2} ); 3524 ffrt::wait({&y1, &y2}); 3525 y = y1 + y2; 3526 } 3527 } 3528 ``` 3529 3530 3531 3532## 建议3: 数据生命周期 3533 3534* FFRT的任务提交和执行是异步的,因此需要确保任务执行时,对任务中涉及的数据的访问是有效的 3535* 常见问题:子任务使用父任务栈数据,当父任务先于子任务执行完成时释放栈数据,子任务产生数据访问错误 3536* 解决方法1:父任务中增加ffrt::wait()等待子任务完成 3537 3538```{.cpp} 3539#include "ffrt.h" 3540void fib_ffrt(int x, int& y) 3541{ 3542 if (x <= 1) { 3543 y = x; 3544 } else { 3545 int y1, y2; 3546 ffrt::submit([&] {fib_ffrt(x - 1, y1);}, {&x}, {&y1} ); 3547 ffrt::submit([&] {fib_ffrt(x - 2, y2);}, {&x}, {&y2} ); 3548 ffrt::submit([&] {y = y1 + y2;}, {&y1, &y2}, {} ); 3549 ffrt::wait(); // 用于保证y1 y2的生命周期 3550 } 3551} 3552``` 3553 3554* 解决方法2:将数据由栈移到堆,手动管理生命周期 3555 3556```{.cpp} 3557#include "ffrt.h" 3558void fib_ffrt(int x, int* y) 3559{ 3560 if (x <= 1) { 3561 *y = x; 3562 } else { 3563 int *y1 = (int*)malloc(sizeof(int)); 3564 int *y2 = (int*)malloc(sizeof(int)); 3565 3566 ffrt::submit([=] {fib_ffrt(x - 1, y1);}, {}, {y1} ); 3567 ffrt::submit([=] {fib_ffrt(x - 2, y2);}, {}, {y2} ); 3568 ffrt::submit([=] {*y = *y1 + *y2; }, {y1, y2}, {} ); 3569 ffrt::wait(); 3570 } 3571} 3572``` 3573 3574 3575 3576## 建议4: 使用FFRT提供的替代API 3577 3578* 禁止在FFRT任务中使用系统线程库API创建线程,使用submit提交任务 3579* 使用FFRT提供的锁,条件变量,睡眠,IO等API代替系统线程库API 3580 * 使用系统线程库API可能造成工作线程阻塞,引起额外性能开销 3581 3582 3583 3584## 建议5: Deadline机制 3585 3586* **必须用于具备周期/重复执行特征的处理流程** 3587* 在有明确时间约束和性能关键的处理流程中使用,避免滥用 3588* 在相对大颗粒度的处理流程中使用,例如具有16.6ms时间约束的帧处理流程 3589 3590 3591 3592## 建议6: 从线程模型迁移 3593 3594* 创建线程替代为创建FFRT任务 3595 * 线程从逻辑上类似无in_deps的任务 3596* 识别线程间的依赖关系,并将其表达在任务的依赖关系in_deps/out_deps上 3597* 线程内计算过程分解为异步任务调用 3598* 通过任务依赖关系和锁机制避免并发任务数据竞争问题 3599 3600 3601 3602# 已知限制 3603 3604## thread local使用约束 3605* FFRT Task中使用thread local存在风险,说明如下: 3606* thread local变量包括C/C++语言提供的thread_local定义的变量,使用pthread_key_create创建的变量 3607* FFRT支持Task调度,Task调度到哪个线程是随机的,使用thread local是有风险的,这一点和所有支持Task并发调度的框架一样 3608* FFRT的Task默认以协程的方式运行,Task执行过程中可能发生协程退出,恢复执行时,执行该任务的线程可能发生变更 3609 3610## thread绑定类使用约束 3611* FFRT支持Task调度,Task调度到哪个线程是随机的,thread_idx/线程优先级/线程亲和性等与thread绑定的行为禁止在task中使用 3612 3613## recursive mutex使用约束 3614* FFRT Task中使用标准库的recursive mutex可能发生死锁,需要更换为FFRT提供的recursive mutex,说明如下: 3615* recursive mutex在lock()成功时记录调用者"执行栈"作为锁的owner,在后续lock()时会判断调用者是否为当前执行栈,如果是则返回成功,以支持在同一个执行栈中嵌套获取锁。在标准库的实现中,"执行栈"以线程标识表示。 3616* 在FFRT Task中使用标准库的recursive mutex,如果在外层和内层lock()之间,发生Task(协程)退出,Task恢复执行时在不同于首次调用lock()的FFRT Worker上,则判断当前线程不是owner,lock()失败,FFRT Worker挂起,后面的unlock()不会被执行,从而出现死锁。 3617 3618## 不支持用户在fork出的子进程内使用ffrt 3619 3620## 以动态库方式部署FFRT 3621 3622* 只能以动态库方式部署FFRT,静态库部署可能有多实例问题,例如:当多个被同一进程加载的so都以静态库的方式使用FFRT时,FFRT会被实例化成多份,其行为是未知的,这也不是FFRT设计的初衷 3623 3624## C API中初始化ffrt对象后,对象的置空与销毁由用户负责 3625 3626* 为保证较高的性能,ffrt的C API中内部不包含对对象的销毁状态的标记,用户需要合理地进行资源的释放,重复调用各个对象的destroy操作,其结果是未定义的 3627* 错误示例1,重复调用destroy可能造成不可预知的数据损坏 3628 3629```{.cpp} 3630#include "ffrt.h" 3631void abnormal_case_1() 3632{ 3633 ffrt_task_handle_t h = ffrt_submit_h([](){printf("Test task running...\n");}, NULL, NULL, NULL, NULL, NULL); 3634 ... 3635 ffrt_task_handle_destroy(h); 3636 ffrt_task_handle_destroy(h); // double free 3637} 3638``` 3639 3640* 错误示例2,未调用destroy会造成内存泄漏 3641 3642```{.cpp} 3643#include "ffrt.h" 3644void abnormal_case_2() 3645{ 3646 ffrt_task_handle_t h = ffrt_submit_h([](){printf("Test task running...\n");}, NULL, NULL, NULL, NULL, NULL); 3647 ... 3648 // memory leak 3649} 3650``` 3651 3652* 建议示例,仅调用一次destroy,如有必要可进行置空 3653 3654```{.cpp} 3655#include "ffrt.h" 3656void normal_case() 3657{ 3658 ffrt_task_handle_t h = ffrt_submit_h([](){printf("Test task running...\n");}, NULL, NULL, NULL, NULL, NULL); 3659 ... 3660 ffrt_task_handle_destroy(h); 3661 h = nullptr; // if necessary 3662} 3663``` 3664 3665## 输入输出依赖数量的限制 3666 3667* 使用submit接口进行任务提交时,每个任务的输入依赖和输出依赖的数量之和不能超过8个。 3668* 使用submit_h接口进行任务提交时,每个任务的输入依赖和输出依赖的数量之和不能超过7个。 3669* 参数既作为输入依赖又作为输出依赖的时候,统计依赖数量时只统计一次,如输入依赖是{&x},输出依赖也是{&x},实际依赖的数量是1。 3670