作者:Fredrick Odhiambo 和 Dmitry Kazakov
Krita是一款免费、专业的开源绘画程序,开发这款程序的美术师希望所有人都能使用物美价廉的美术工具。
要点综述
“现在我可以用大两倍甚至三倍的画笔来绘图!实际上就相当于性能提高了大约 5-10 倍。”Krita* 美术师 Rad (Igor Wect) 说。
计算机处理需求不断增长,而且更复杂、更密集。软件应用要求使用更多的计算资源,从而出现软件性能瓶颈和不可靠的软件服务,用户体验差,最终导致用户完全拒绝使用应用。多核指封装在单个集成电路板上的单颗物理处理器包含两个以上的处理单元来同时处理多个任务,从而增强性能并降低能耗。最新英特尔酷睿? 博锐? 处理器、英特尔酷睿? 处理器家族、英特尔酷睿? m 处理器家族和英特尔? 至强? 处理器家族支持的英特尔? 超线程技术(英特尔? HT 技术)支持单颗微处理器像两颗单独的处理器一样运行操作系统和应用程序。本白皮书将重点介绍 Krita 应用如何结合多核和英特尔 HT 技术来提升应用性能和整体 Krita 用户体验。Krita 软件开发人员使用 VTune? Amplifier 分析了用户之前认为需要提升性能的 Krita 用例。VTune Amplifier 支持快速识别并发性。通过多线程化和线程同步技巧可大体上解决这些问题。他们构建了进阶版 Krita,并重新测试了上述用例以提升性能。收集的结果显示,不同的系统配置可将性能提升高达 2 倍。系统配置信息可参考下文。
软件开发人员价值定位
多核和英特尔 HT 技术支持软件开发人员编写能够充分利用这些技术的软件应用,并提供卓越的用户体验,从而在让最终用户接受这些应用时拥有极大的竞争优势。此外,通过这些应用,软件开发人员现在能够:
- 在运行多个要求苛严的应用程序的同时保持系统响应能力。
- 在确保系统处于保护中、高效和易于管理的同时把对工作效率的影响降至最低。
- 为未来业务增长和新的解决方案能力提供充足的扩展空间。
- 降低应用能耗,因为相比单个内核,多个内核的运行频率较低,可大幅降低散热。
- 增加可同时处理的交易数量。
- 充分利用现有的 32 位应用技术,同时为未来的 64 位应用技术做好准备。
- 多媒体应用可以在运行后台功能的同时创建、渲染、编辑和编码图形密集型文件,丝毫不影响应用的整体性能。
Krita* 案例研究
方法
Krita 在 https://bugs.kde.org/上使用 Bugzilla 收集关于应用使用方面的抱怨和问题。在线论坛上,美术师和其他用户报告就用户体验而言,使用 Krita 时所遇到的挑战和难题。其中笔刷速度慢最突出,Krita 向问题作者提出其他问题,比如系统设置支持更准确地定义和界定问题。他们从提出的多个问题中优先确定了两种用例:笔刷和动画渲染。
使用 VTune Amplifier 对受影响用例的并发性和热点进行分析,以深入了解性能问题。然后通过重新编写代码、更改代码库、融入英特尔? 高级矢量扩展指令集(英特尔? AVX)以生成融合乘加指令,以及尽量实现用例的多线程话,解决了这些问题。因此开发出了 Krita 版本 4.0.0,充分利用多核和英特尔 HT 技术显著提高应用性能。
Krita 情况
使用 VTune Amplifier 分析 Krita 应用性能。检查深度并发性和基础热点分析,确定两种重要用例的性能问题(刷涂和动画渲染)。确定了以下问题。
渲染用例
几乎每一个线程都按顺序渲染每一帧。经过 VTune Amplifier 初步分析(Concurrency 和 Waits 基础评测),找到了两个有关互斥体使用的问题:
问题 1:方块 (tile) 散列表。Krita 中的所有图像数据都以 256 × 256 像素方块的形式呈现,这些方块保存在散列表中。每个像素层都有一个散列表,包含所有专用于该层的方块,而且每个散列表都有自己的读写锁 (QReadWriteLock) 加以保护。根据 VTune Amplifier 分析结果,长达 40% 的时间都用于等待这些读写锁。
问题 2:Krita 应用内部调度程序也存在瓶颈。调度程序中的任务表示为在 QThreadPool 池上运行的 Qrunnable 对象。基于 Qrunnable 的任务太小,线程极有可能在一个任务完成之后下一任务开始之前进入睡眠状态,因此将任务合成几堆。现在,每个基于 Qrunnable 的任务都可以从调度程序 pull额外任务,从而避免进入睡眠状态。
这两个问题的解决大大改进了 Krita 的渲染引擎,并展现了接近线性的提速效果,多达 6 个物理内核。增加内核数量后,互斥体开始重新出现。为确保 Krita 在增加 CPU 内核数后顺利扩展,有两个选择 — 以无锁的方式重新编写散列表或使用更高级别的多线程方法。他们选择采用第二种方法。
解决方法
在散列表中通过双相锁定 (KisTileHashTableTraits<T>::getTileLazy()) 解决了方块散列表问题。第一种尝试是提取不带任何锁的方块 (KisTileHashTableTraits<T>::getTileMinefieldWalk()),并且只在流程失败的时候提取锁并重新运行流程。这种方法可确保线程安全,原因有两点:散列表中使用线程安全功能指示器(以便在最坏的情况下仅读取过期数据),高级别调度程序确保两个线程永远不尝试读写像素相同的图像(这意味着方块不被删除,同时其他线程仍然可访问)。这种双相锁定大大减少了锁争用现象。
保存动画视频剪辑时,所有帧都需要渲染并保存至单独的文件。从技术上来说,每帧代表同一张图像的不同版本,但包含自己的像素数据。因此无法同时在同一张图像上渲染不同的帧。但可以生成图像副本。如果每个副本传递至自己的线程,渲染过程将完全独立!
实施了制作图像浅副本的可能性。创建浅副本时,大部分像素数据可以在两张图像之间共享。除了用于渲染的临时“投影”外,不需要额外内存。一位专业动画师在大型动画文件上进行了测试,全加载时占用大约 7.2 GB RAM。生成该图像的浅副本仅占用 200MB 额外 RAM。常规用户的文件所需的额外内存更少。即使浅副本不会占用过多额外内存,仍然其创建需要耗费大量时间。考虑折衷:不为每个内核创建专用副本,仅创建少量副本并将每个副本分配给多个内核。例如,如果现在有 6+6 个内核,那么可创建 4 个浅副本并将每个副本分配给 5 个线程。这样可通过生成 12 个线程使 CPU 利用率高达 100% 并独立运行。
注意:进行这种内存/效率折衷时,应特别注意用户所拥有的内存量。经过对真实用户的测试,确定即使用户可以在设置中调整“克隆”数量,但用户不愿意考虑这点。因此实施了自动克隆限制。克隆图像之前,计算投影即将占用的实际内存量,并对比用户允许 Krita 使用的内存量。
刷涂用例
刷涂从本质上来说属于顺序算法。刷笔本身是一系列小圆图(称为 dab),以极小的偏差沿着笔画一个一个涂。但实际情况更为复杂,因为部分 dab 之间存在依赖性:两个 dab 可以使用相同的 base dab,但运用不同的后期处理。
解决方法
这种问题的解决方法包含两个部分:
- dab本身通过主渲染线程以并行、异步的形式准备。这里巧妙的地方在于 — 当 dab 调度至并行处理时,可能已经重新排序且部分新的dab将旧的完成之前做好准备。实施特殊队列 (KisDabRenderingQueue) 后解决了这些问题,将 dab收集在序列堆中,并将它们进一步传递至管线。
- dab渲染过程每隔 20-100 毫秒暂停一次,然后所有准备好的 dab 渲染在画布上。这种渲染过程可以并行实施:工作区分为多个分配给不同线程的小区域。
通过这些渲染内核优化,现在可以确保笔刷顺利扩展至多达 6 个物理内核。
响应最快的笔刷 (Basic_tip_default) 出现了一个有趣的现象,它利用英特尔 AVX 技术进行矢量化计算。所有优化完成后,性能显著提升几乎达到了内存吞吐量的顶点(根据 VTune Amplifier,在 5k 画布上用 1000 px 笔刷涂画)。
Krita 未来优化目标
- 可以确定的是,采用英特尔 AVX 技术进行优化后,渲染速度比未优化的提高了 4-6 倍。笔刷优化的下一步是实施其他笔刷的矢量化版本。
- 充分利用英特尔? 高级矢量扩展指令集 2(英特尔? AVX2)中的特定整数指令进行合成,还可将渲染速度提高 2 倍。
- 实施无锁方块散列表还可为使用超过 6 个物理内核的系统提供部分优势。
结果
实施上述解决方案之后,他们收集了基准性能评测结果,评测的两种系统分别采用以下系统配置:
系统 1:
处理器:英特尔? 酷睿? i7-8550U @ 1.80GHz,四核
物理内存:8GB DDR4 2400MHz
操作系统:Windows 10? Pro(64 位)
系统 2:
处理器:英特尔? 酷睿? i7-8550U @ 3.2GHz,六核
物理内存:32GB DDR4 2400MHz
操作系统:Windows 10? Pro(64 位)
用例 1:动画渲染
以下数据显示了不同内核生成帧(动画渲染准备)的时间(毫秒)。增加设备的内核数可以实现这一目标。帧生成时长决定着使用 Krita 渲染动画的时间。
注:用于测试的动画文件来源于 Wolthera Van Hovell(顶级 Krita 美术师),https://yadi.sk/d/n9a6YRrP3Lc3Ts。
图 1:测试动画截图
图 2:系统 2 的动画渲染结果
注:上图中的内核指物理内核 + 逻辑内核(例如 12 个内核= 6 个物理内核 + 6 个逻辑内核)
用例 2:笔刷
关于可靠性,在上述两种系统不同数量的内核上开发和执行自动化脚本,脚本记录了笔刷速度。下表显示了使用直径为 1000px 的高斯笔刷渲染 16 个 5000-px 长笔画所用的时间。随着内核数量的增强,渲染时间逐步降低(笔刷速度提高)。
图 3:系统 2 的高斯笔刷速度结果
注:上图中的内核指物理内核 + 逻辑内核(例如 12 个内核= 6 个物理内核 + 6 个逻辑内核)
图 4:系统 1 的高斯笔刷速度结果
注:上图中的内核指物理内核 + 逻辑内核(例如 8 个内核= 4 个物理内核 + 4 个逻辑内核)
结论和建议
通过上述两种用例我们可以得知,多核和英特尔 HT 技术可显著提高处理器密集型计算的性能。相比在一个内核上渲染动画,使用 6Core 系统(启用英特尔 HT 技术)中的全部内核渲染动画,可将性能提升 4.37 倍。这种可衡量的性能显著提升意味着可大大缩短 Krita 应用用户的等待时间,进而提高总体效率。
英特尔提供技术和硬件平台满足软件应用提出的越来越多的计算机处理需求。通过编写多线程化软件,独立软件开发商可充分利用这些功能为应用用户提供无与伦比的用户体验。英特尔为软件开发商提供的各种工具可用来确定软件应用性能瓶颈、热点和并发性问题。例如本案例研究中所使用的英特尔 VTune Amplifier。