找回密码
 会员注册
查看: 30|回复: 0

火山引擎veImageXFPGAHEIF静图编码服务性能优化_UTF_8

[复制链接]

7

主题

0

回帖

22

积分

新手上路

积分
22
发表于 2024-10-1 21:53:30 | 显示全部楼层 |阅读模式
背景互联网内容的展示离不开图片,通过 CDN 展示分发图片可以提升图片访问速度,但是也需要为带宽付费。HEIF 图片格式有着卓越的压缩性能,相比 WebP 可以节省 30% 的图片码率,由此可以为业务节省相当规模的带宽成本。火山引擎图片服务 veImageX 目前大力推广 HEIF 图片格式,在辅助业务降本方面取得了阶段性进展。HEIF 格式是一把双刃剑,相比其他格式,在提升压缩率的同时,也需要消耗更多 CPU 计算资源。为了降低 HEIF 格式的编码计算成本,veImageX 采用了 FPGA 异构架构,逐步将 HEIF 编码的流量从 CPU 计算集群迁移到 FPGA 计算集群。在流量迁移过程中,最初整体流量较小,FPGA 编码服务看起来很稳定。但随着迁移过程递进,当 FPGA 的单卡 QPS 上涨到一定阈值后,FPGA 卡所在宿主机的性能瓶颈逐渐暴露出来,影响了整体的迁移工作。本文会对迁移过程中遇到的性能瓶颈做分析,并给出优化解决方案。经过这一系列优化措施,整体 CPU 负载从 80% 降低为 30%,相应的服务延时从 140ms 降低为 4ms。架构首先,我们看一下 FPGA HEIF 静图分发链路的整体架构。整体链路分为三块:业务 App、veImageX 分发基础链路、FPGA HEIF 编码服务。业务 App 一般都会集成 veImageX 的图片 SDK。一来可以兼容各类图片格式(自然包括 HEIF),提供了图片的下载、解码、展示功能。二来支持将访问图片过程中产生的指标数据上报,这样可以方便地在控制台查看这些性能指标,比如解码耗时、图片加载成功率等。veImageX 分发基础链路主要解决了图片分发的问题,提供了基础的图片实时处理能力。其中 CDN 缓存了图片请求,提供了加速访问的能力;veImageX 源站服务主要负责访问权限的校验、流量控制、图片资源下载以及静态图片的主体处理流程。对于 HEIF 静图编码场景,veImageX 源站服务则需要和 FPGA HEIF 编码服务互动,协作完成。FPGA HEIF 编码服务自上而下可以分为三层:编码服务层、编码驱动层、编码硬件层。为了解决计算资源异构引入的耦合问题,FPGA HEIF 的编码能力通过 HTTP 服务化的方式提供出来。所有的 FPGA 卡部署于字节跳动自研的 Lambda 计算平台。通过 Lambda 函数+资源虚拟化的方式,将 HEIF 编码功能抽象为上游可直接调用的服务,并能确保将编码请求均衡地调度到各个 FPGA 卡上。物理机上的每一张 FPGA 卡和对应的主机 CPU 和内存资源都被打包,经由 Executor 管理。此外,为了防止 FPGA 卡被突发流量打挂,Executor 内置了一个执行队列,用于控制 FPGA 卡的并发吞吐。编码服务层主要负责解析 HTTP 请求,获取待编码的图片数据。待编码的图片数据一般通过 JPEG 格式传入,因此其中内嵌了一个 JPEG 解码器。此外,veImageX HEIF 支持了自适应编码选项,通过服务层内的自适应模型预测编码所用到的质量参数。服务层中的 HEIF 编码器是一个适配层,屏蔽了底层计算架构的差异,对于 CPU 和 FPGA 都可以提供相同的编码接口,将传入的 RGBA 像素矩阵编码为 HEIF 码流。编码驱动层中的 FBVC1 编码器可以将图片像素序列编码为二进制码流,上层的 HEIF 编码器拿到这个码流后,按照 HEIF 标准格式封装即可。FBVC1 编码过程中,依赖了 FPGA 驱动库和编码硬件层打交道,发送指令,读写 FPGA 设备。优化方向降低线程数在迁移测试FPGA编码服务过程中,遇到了一些性能瓶颈的问题。首当其冲的是,当单机 QPS 达到 2K 时,CPU 负载高达到 60%。通过分析热点,从下图中,我们可以看到问题出现在 onnxruntime 这个库上。从调用的 API 很容易联想到,这是一个线程相关问题。我们都知道,如果没有手动设置线程数的话,默认会使用物理机核数作为线程数,导致整体的调度开销比较严重。因此,需要根据宿主机的CPU配置情况,手动配置线程数,而不要使用默认配置。针对这块的优化,最终将 CPU 负载从 57% 降低到 7%。调优 GOMAXPROCSHEIF 的编码服务层是使用 Golang 实现的。而 Golang 中使用了 GOMAXPROCS 这个环境编码来控制底层并发度。默认情况下,GOMAXPROCS 是和物理机核数相关。所以这里也遇到了和上一个问题相同的根因,需要限制整体的并发度。针对 GOMAXPROCS 做了调优,在单机 QPS 达到 8K 时,CPU 负载下降了 6% 。限制磁盘 IO首先通过 statio 查看磁盘情况,结果如下图。再结合下面的火焰图,图中黑框内有一些明显的磁盘 IO 操作。这里很容易能想到这些磁盘 IO 操作导致了整体延迟的升高。但从数字来看,平均 8ms 还行,不算很高。但 HEIF 编码服务对处理的延迟要求较严格,请求处理过慢会导致请求堆积,此时 FPGA 的计算潜力无法做到完全释放。针对这块定向优化后,相关延时下降至 0.5ms,CPU 负载下降 3%。另外,我们观察到磁盘和 cached memory 较高,不太正常。进一步定位后,确定是编码服务造成的。详细排查后发现,编码驱动层中的 FPGA 驱动程序的部分调试日志未关闭,导致大量的日志写磁盘。当关闭驱动的调试日志后,CPU 负载下降 5% 。合并 CGO 调用编码服务包括两部分的 CGO 调用:自适应编码模型预测:每个请求会有最多 5 次的推理,合并为 Batch,减少为 1 次调用FPGA 编码:直接调用 SDK 需要 6 次 CGO 调用,对这部分实现 C 的封装,减少为 2 次调用这部分优化影响较小,在延迟数据层面不是很明显,模型预测部分可能有几百 us 的优化。减少 GC编码服务每次处理请求都需要获取图像 raw data,因此服务会多次创建 []byte 的图像数据对象,容易导致频繁 GC。一个解决问题的思路是在服务启动前预分配一个固定的对象池,每次请求需要的 []byte 对象直接从对象池里拿。此外,也曾尝试过使用 Golang 标准库中的 sync.Pool,但效果不好,可能的原因是 sync.Pool里依然有一些 GC 相关的策略,不符合我们这个场景。这块优化后,CPU 负载下降了 6% 。均衡中断从系统的监控中(下图),观察到各 CPU 负载不是很均匀。下图是编码过程中发生的中断情况。可以得出结论,FPGA 的相关中断被只绑定到了特定的 CPU 上,没有分布均匀。这个在当时并没有成为瓶颈,所以优化后没有明显提升。加速图片解码从火焰图看到解码时间是服务延时的占比较大部分。对火焰图中黑框内调用栈分析后,观察到有相当部分时间花在了 JPEG 解码上。调查后,发现底层 SDK 解码使用了 libjpeg,整体性能不佳。这里我们替换为使用 SIMD 实现的 libjpeg-turbo 解码库后,CPU 负载降低了 10%,耗时减少 2ms。优化总结基于优化后的版本再次做性能压测,使用 300x400 分辨率的测试图片,当单机 QPS 达到 10K 时,编码服务整体性能指标变化如下:CPU 负载从 80% 降低为 30%服务延时从 140ms 降低为 4ms可以看到,经过我们一套组合拳后,整体编码服务的性能有了巨大的提升。写在最后目前火山引擎 veImageX 已经将上述优化方法形成端到端的解决方案输出给全行业,帮助每一个互联网企业用更低的成本达到更好的图片加载效果。使用更“绿色”的算法提高效率,为行业降本增效提供了一种创新可能性。推荐阅读欢迎关注
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2025-1-12 17:15 , Processed in 0.818506 second(s), 25 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表