前两期我们分享了日常工作中前端、移动端开发的相关问题,感兴趣的同学可以在文末推荐阅读跳转查看。本期我们分享三个议题:golang对象池减少gc压力、FFmpeg中的并发控制、paddle的静态图和动态图,希望能为你的技术提升助力。

01golang对象池减少gc压力

sync.Pool是Golang 内置的对象池技术,可用于缓存临时对象,避免因频繁建立临时对象所带来的消耗以及对 GC 造成的压力。sync.Pool 缓存的对象随时可能被无通知的清除,因此不能将 sync.Pool 用于存储持久对象的场景。sync.Pool 不仅是并发安全的,而且通过引入atomic 包中的 CAS 操作实现了 lock free,通过更接近CPU和操作系统级别的原子操作,满足并发场景替代锁的需求。

1.1 使用

sync.Pool 在初始化的时候,需要用户提供一个对象的构造函数 New。用户使用 Get 来从对象池中获取对象,使用 Put 将对象归还给对象池。整个用法还是比较简单的。

1.2 原理

在 GMP 调度模型中,从线程维度来看,在 P 上的逻辑都是单线程执行的,这就为解决P上面协程并发提供了条件。sync.Pool 就是充分利用了 GMP 这一特点。对于同一个 sync.Pool ,每个 P 都有一个自己的本地对象池 poolLocal。每个P都会对应一个自身的本地对象池poolLocal,poolLocal就是存储P本地对象的内存池,每个poolLocal对应一个private和一个poolChain,private很简单是一个接口类型,比poolChain优先写也会被优先读。poolChain是指向若干个ringBuffer组成的链表,采用ringBuffer是因为环形结构方便内存复用,而且ringBuffer是一段连续内存,利于进行CPU Cache。

poolChain存放的是每个ringBuffer的head和tail,head 和 tail 并不是独立的两个变量,只有一个 uint64 的 headTail 变量。这是因为 headTail 变量将 head 和 tail 打包在了一起:其中高 32 位是 head 变量,低 32 位是 tail 变量,这个其实是个非常常见的 lock free 优化手段。对于一个 poolDequeue 来说,可能会被多个 P 同时访问,比如Get 函数中的对象窃取逻辑,这个时候就会带来并发问题。例如:当 ring buffer 空间仅剩一个的时候,即 head - tail = 1。如果多个 P 同时访问 ring buffer,在没有任何并发措施的情况下,两个 P 都可能会拿到对象,这肯定是不符合预期的。在不引入 Mutex 锁的前提下,sync.Pool 利用了 atomic 包中的 CAS 操作。两个 P 都可能会拿到对象,但在最终设置 headTail 的时候,只会有一个 P 调用 CAS 成功,另外一个 CAS 失败。

02FFmpeg中的并发控制

2.1 问题描述

最近业务需要,在一个探索性项目里,要进行视频的拼接与合成,由于原始的视频片段的格式、尺寸、码率等都各不相同,为了得到比较丝滑的拼接效果,需要首先进行视频尺寸的打齐、编码格式以及码率的统一。在对FFmepg命令进行了一番调研,并进行了一系列的转换实验(比如视频裁剪、视频填充、视频缩放等),均得到了符合预期的效果,但当把命令集成到实际业务场景里时,先后遭遇了内存被打爆进程被杀、CPU满负荷导致任务执行时间过长甚至失败,且进一步升级CPU配置问题并没有得到太大改善,于是开启了对FFmpeg命令的线程控制的调研。

2.2 FFmpeg线程控制

FFmpeg作为强大的多媒体处理工具,包含多个功能强大的lib库。FFmpeg处理多媒体文件流程如下:

其中关键的计算步骤为编码、解码以及其中数据修改,同时FFmpeg的线程控制也提供了三个参数进行线程控制,在FFmpeg文档里,对于线程控制相关参数说明如下:

-filter_threads nb_threads (global)

Defines how many threads are used to process a filter pipeline. Each pipeline will produce a thread pool with this many threads available for parallel processing. The default is the number of available CPUs.

filter_threads实现对简单滤镜的线程控制,默认线程数为可用CPU核数

-filter_complex_threads nb_threads (global)

Defines how many threads are used to process a filter_complex graph. Similar to filter_threads but used for -filter_complex graphs only. The default is the number of available CPUs.

filter_complex_threads实现对复杂滤镜的线程控制,默认线程数同样为可用CPU核数

threads integer (decoding/encoding,video)

Set the number of threads to be used, in case the selected codec implementation supports multi-threading.

Possible values:

‘auto, 0’automatically select the number of threads to set

Default value is ‘auto’.

threads实现对编解码器的线程控制,前提是使用的编解码器支持多线程并行,其默认线程数在文档里就一个automatically说明,翻遍全网也没找到对这个参数的具体说明,于是基于业务场景进行了相关数据实验。

使用time命令查看FFmpeg命令的耗时和CPU利用率相关参数,4核机器上试验情况如下:

-i  -filter_complex  -threads 1 -y   4.54s user 0.17s system 110% cpu 4.278 total
-i  -filter_complex  -threads 2 -y   4.61s user 0.29s system 189% cpu 2.581 total
-i  -filter_complex  -threads 4 -y   4.92s user 0.22s system 257% cpu 1.993 total
-i -filter_complex -threads 6 -y 4.73s user 0.21s system 302% cpu 1.634 total
-i -filter_complex -threads 8 -y 4.72s user 0.19s system 315% cpu 1.552 total
-i  -filter_complex  -y   4.72s user 0.22s system 306% cpu 1.614 total
-i  -filter_complex  -y -filter_complex_threads 1 -y   4.63s user 0.13s system 316% cpu 1.504 total
-i  -filter_complex  -y -filter_complex_threads 2 -y   4.62s user 0.20s system 304% cpu 1.583 total
-i  -filter_complex  -y -filter_complex_threads 4 -y   4.58s user 0.27s system 303% cpu 1.599 total

通过试验发现在不加线程控制情况下,对于我的裁剪+缩放+gblur尺寸打平操作来说,几乎没有并行空间,filter_complex_threads增加线程数徒增系统态耗时,对于整体耗时和CPU利用来说几乎没有增益。而对于编解码部分随着线程数的增多,CPU利用率增大且耗时降低,但是整体数据呈现出来的并非线性关系。而对于单条命令而言,线程数设置为2时基本是CPU消耗和耗时相对性价比比较高的配置。

2.3 总结

1. FFmepg作为计算密集型处理工具,对CPU有比较大的需求,且FFmpeg提供了三个并行控制参数分别进行不同类型命令的并发控制,但是具体命令是否可并发与本身的实现原理有关,需要具体问题具体分析;

2. 编解码作为FFmpeg视频处理里关键环节,相对也是比较耗时、耗CPU的环节,用好threads参数能够比较好地加速处理、控制CPU使用率

03paddle的静态图和动态图

静态图和动态图的概念

静态图:类比c++,先编译后运行。因此可以分为compile time和runtime两个阶段。在compiletime,需要预先定义完整的模型,paddle会生成一个programDesc,然后使用transplier对programDesc进行优化。在runtime,executor使用programDesc进行运行。

动态图:类比python,没有编译阶段,所以不用预先定义模型。每写一行网络代码,即可同时获得对应计算结果。

优缺点对比:

静态图:paddle一开始只支持静态图方式,所以相关的支持和文档比较多。在性能方面也较动态图好。但是调试起来会比较麻烦。
动态图:方便调试,可以动态调整模型结构。但是执行效率较低。

问题1:如何判断当前是静态图模式还是动态图模式

  • 静态图模式:程序中存在static模块使用,或者需要构建executor并使用executor.run(program)执行定义好的模型。

  • 动态图模式:程序中存在dygraph模块使用。在paddle2.0开始,默认开启动态图模式。

  • 注意:部分api仅支持静态图/动态图,如涉及variable取值等api,一般仅支持动态图。当出现带imperative/dygraph等报错时,需要确认是否在静态图模式中调用了动态图api。

import numpy as np
import paddle
import paddle.fluid as fluid
from paddle.fluid.dygraph.base import to_variableprint (paddle.__version__) # 2.1.1#静态图模式
main_program = fluid.Program()
startup_program = fluid.Program()
paddle.enable_static()
with fluid.program_guard(main_program=main_program, startup_program=startup_program):data_x = np.ones([2, 2], np.float32)data_y = np.ones([2, 2], np.float32)# 静态图模式下,构建占位符x = fluid.layers.data(name='x', shape=[2], dtype='float32')y = fluid.layers.data(name='y', shape=[2], dtype='float32')x = fluid.layers.elementwise_add(x, y)print ('In static mode, after calling layers.data, x = ', x)# 这个时候无法打印出运行数值,输出In static mode, after calling layers.data, x =  var elementwise_add_0.tmp_0 : LOD_TENSOR.shape(-1, 2).dtype(float32).stop_gradient(False)place = fluid.CPUPlace()exe = fluid.Executor(place=place)exe.run(fluid.default_startup_program())data_after_run = exe.run(fetch_list=[x], feed={'x': data_x, 'y': data_y})print ('In static mode, data after run:', data_after_run)#In static mode, data after run: [array([[2., 2.],[2., 2.]], dtype=float32)]# 动态图模式
with fluid.dygraph.guard():x = np.ones([2, 2], np.float32)y = np.ones([2, 2], np.float32)# 动态图模式下,将numpy的ndarray类型的数据转换为Variable类型x = fluid.dygraph.to_variable(x)y = fluid.dygraph.to_variable(y)print ('In DyGraph mode, after calling dygraph.to_variable, x = ', x)# In DyGraph mode, after calling dygraph.to_variable, x =  Tensor(shape=[2, 2], dtype=float32, place=CUDAPlace(0), stop_gradient=True,[[1., 1.],[1., 1.]])x = fluid.layers.elementwise_add(x,y)print ('In DyGraph mode, data after run:', x.numpy())#In DyGraph mode, data after run: [[2. 2.] [2. 2.]]

问题2:如何在静态图模式下调试

  • 一般使用fluid.layers.Print(),创建一个打印operator,对正在访问的tensor内容进行打印。

问题3:动态图如何转静态图

  • 基于动态图的优缺点,可以在模型开发阶段使用动态图模式,在训练及推理阶段使用静态图模式。

  • 在需要进行动静转化的函数上,使用 @paddle.jit.to_static 进行装饰。或者使用paddle.jit.to_static()函数对网络整体进行转化。

import numpy as np
import paddle
import paddle.fluid as fluid
from paddle.jit import to_staticclass MyNet(paddle.nn.Layer):def __init__(self):super(MyNet, self).__init__()self.fc = fluid.dygraph.Linear(input_dim=4, output_dim=2, act="relu")@to_staticdef forward(self, x, y):x = fluid.dygraph.to_variable(x)x = self.fc(x)y = fluid.dygraph.to_variable(y)loss = fluid.layers.cross_entropy(input=x, label=y)return lossnet = MyNet()
x = np.ones([16, 4], np.float32)
y = np.ones([16, 1], np.int64)
net.eval()
out = net(x, y)

推荐阅读:

百度程序员开发避坑指南(移动端篇)

百度程序员开发避坑指南(前端篇)

百度工程师教你快速提升研发效率小技巧

百度一线工程师浅谈日新月异的云原生

【技术加油站】揭秘百度智能测试规模化落地

【技术加油站】浅谈百度智能测试的三个阶段

百度程序员开发避坑指南(3)相关推荐

  1. Python程序员的“避坑”指南

    结合我最近这些年的Python学习.开发经验,发现90%的人在学Python时都会遇到下面这些问题: 1. 没什么经验根本不知道从何学起,而且应用方向太多了根本不知道该选择什么方向... 2. 基础入 ...

  2. HarmonyOS 开发避坑指南

    Harmony OS 开发避坑指南--源码下载和编译 本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(Hi3516,Hi3518和Hi3861)的编译环境,以及如何将源码编译为三个 ...

  3. FlyFish|前端数据可视化开发避坑指南(二)

    FlyFish是云智慧开源的一款数据可视化编排平台.通过配置数据模型为用户提供上百种可视化图形组件,零编码即可实现符合自己业务需求的炫酷可视化大屏. 同时,FlyFish也提供了灵活的拓展能力,支持组 ...

  4. 小程序进阶之路:跨平台开发避坑指南

    阿里妹导读:小程序的开发不可避免的会面临跨平台开发的问题.各小程序平台有哪些特点?如何处理各平台的差异?本文分享淘票票在跨平台开发上的经验总结,包含了技术演进及差异控制策略,希望能帮助同学们提前避坑. ...

  5. 抖音开放平台基础开发避坑指南

    抖音小程序开发基础避坑 自定义组件路径,引用到具体的组件上 //相对路径引用到具体的组件上"usingComponents": {"intro-box":&qu ...

  6. ae导出json_关于AE转json动画开发避坑指南

    本篇文章是给一定基础的UI设计写的 Lottie 是Airbnb开源的一个面向Android. iOS.React Native .Web的动画库,能分析 Adobe After Effects 导出 ...

  7. 五种糟糕的代码实践,程序员注意避坑

    本文将向你展示五种糟糕的代码实践,它们足以让所有程序员深恶痛绝. 1将变量命名变成解谜游戏 图译:parseDBMXML 代指什么:A.解析 DBM XML .B.解析 DB MXML.C.解析 DB ...

  8. JAVA程序员M1踩坑指南 2021-08-29

    以旧换新 本人于今年1月份以旧换新购买了一台内存8G,存储 512GB的M1,旧机是2015内存8G,存储121GB的,主板进过水,维修过,而且电池也是充不进去电了,还抵扣了2000,大家可以评论我这 ...

  9. java类中的代码块,Java开发避坑指南!

    一.阿里 (会员中台) 1. Java 重写hashcode()是否需要重写equals(),不重写会有什么后果 2. 并发 自旋锁和阻塞锁的区别 公平锁和非公平锁的区别 jdk中哪种数据结构或工具可 ...

最新文章

  1. python保存到固定文件夹的存储路径不能直接复制!
  2. java 异常的捕获及处理
  3. POJ - 2115 C Looooops(扩展欧几里得)
  4. 怎么把mysql表里的时间往后推移_Mysql实战45讲笔记:2、更新语句的执行以及日志...
  5. ubuntu下mysql-python模块的安装
  6. python chardet_Python - chardet
  7. Roling in the deep
  8. MongoDB(一)——简介
  9. 技巧分享篇---如何从GitHub上下载某个项目中单个文件的方法
  10. 网络工程师之网络规划
  11. 塞班系统微信连接不上服务器,一个时代的结束!塞班手机已无法登陆微信QQ
  12. Linux 网络设置(ifconfig、route、traceroute、netstat、ss、nslookup、dig、ping状态返回分析)
  13. SSH Tunneling (SSH隧道)远程连接服务器
  14. 微服务启动成功无法注册到服务注册中心
  15. Redis高可用集群-哨兵模式(Redis-Sentinel)搭建配置教程【Windows环境】
  16. Java小游戏——赛马
  17. 数据库唯一性约束(Unique Constraint)
  18. Hawk-数据抓取工具:简明教程
  19. 完全背包问题(f m)
  20. 3D分析之可见性分析工具

热门文章

  1. C语言/C++常见习题问答集锦(八十一)之学生信息与排序
  2. Mongodb字段更新操作$inc
  3. 外置存储权限在哪打开_中兴手机怎么打开读取外置存储权限
  4. DNA to Face,会是寻人缉凶的一颗“银弹”吗?
  5. 李玉婷MYSQL进阶06-连接查询
  6. 中国移动什么时间关闭2G网络?
  7. U盘误删的文件应该怎么找回 怎么找回U盘误删的文件
  8. linux下python怎么换行,python如何输出换行
  9. 逆天了,谷歌无人车识别交警手势,没信号灯也能从容通过
  10. 物流批量查询,在电脑上查询最简单的方法推荐