热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

如何找到并加速缓慢的代码,提高性能

本文讲述了如何找到并加速缓慢的代码,提高系统性能。作者通过优化内部循环、利用多处理器运行内部循环以及减少内部循环运行次数等方式来提高代码的执行效率。以一个游戏开发中的案例为例,作者介绍了如何通过调查和排查问题,找到导致帧率下降的设计缺陷,并进行修复。文章总结了优化代码的关键点,并提供了一些实用的方法和技巧。

https://img8.php1.cn/3cdc5/24726/807/d78c401fae030c7c.jpeg

前言

当性能是一项功能时,缓慢就是一个漏洞。找到缓慢的源头就像是追踪一个漏洞,但是当你找到缓慢代码时,通常有三种方式去加速代码:

· 让内部循环运行更快。这包括了缓存优化、减少分支和优化SIMD。

·用更多的处理器运行内部循环。在多处理器内核以及/或是多台机器上实行并行。

·减少内部循环运行次数。这包括了从早期测试到算法革新的一系列手段来提高计算复杂性(big-O)。

  故事是关于内部循环已经高度优化,并且也同步运行了,但是一些列严重的设计缺陷使得系统运行远慢于预期。


背景

2014年初,我在箭头游戏工作室工作,当时我们打算重新启动经典的街头游戏Gauntlet。事件发生在我们打算首次公布该游戏的前一周。我们想要让玩家可以任意选择游戏关卡,最后一周里大家都忙于修复漏洞、改善游戏设置,以及提升视觉效果。看起来一切顺利,但是最后一周出现了一些情况:帧率从预期的60FPS下降到了20-25FPS的龟速。这引起了极大的恐慌。哪里出错了?我们还有时间修复吗?

https://img8.php1.cn/3cdc5/24726/807/667631309ab8184e.jpeg

调查

我们马上把怀疑方向转向了lighting接口。近期刚增加了大量的全方位阴影投射灯,而且价格昂贵。

Gauntlet的开发利用了第三方BitSquid引擎(原名叫StingRay)。我们用阴影贴图来使用一个延期的遮光管道。这篇博客就不详细阐述阴影贴图的工作原理了,不过这里是一个简短的概述,可以看出在BitSquid中如何完成全方面的阴影映射:

每个全向光模拟为六个聚光灯走向的正负x、y、z轴。每个这种“虚拟聚焦”引擎会把附近的几何图形呈现在阴影贴图上,这是一个屏幕外的缓冲区,包含了从灯光到最近几何图形的距离。这些阴影贴图之后会被用来确定场景中的各个像素是否需要被阴影投射灯挡住。

关掉阴影上的所有投射灯之后确实让帧率重回60FPSl ,但是这也破坏了这个游戏的整体感觉。在最后的两天理,我决定跟踪找到问题的来源。

我开始尝试改动阴影贴图设置。尤其是,我强力否定了他们的提议,从1024² 到16²。这不会对帧率速度造成任何影响。这一点提醒了我,有一些地方出了大错。在加强游戏内部探查器之后我发现了罪魁祸首:阴影投射的剔除。


问题

在绘制阴影贴图的时候,我们不能只是简单地把所有的几何图形发送给渲染器,因为这可能会导致阴影贴图渲染速度非常低。相反的,渲染器会首先剔除掉几何图形。就是这种剔除过程耗时太长。事实上,这会耗费长达25毫秒!我们为一个稳定的60FPS做的预算是16毫秒。这16毫秒里需要做所有事:游戏逻辑、物理模拟、阴影渲染、场景渲染、场景照明和后期处理等。16毫秒来做这一切,而现在光做一件事就需要25毫秒。该死的!

这是在周三发现的。周五我们就要让最终版本开始运行。在周四早上我做出了一个大胆的承诺:今天结束的时候,帧率会加倍。然后我就去上班了。

值得庆幸的是,我们拥有BitSquid的源代码许可证,我习惯于优化引擎并修复漏洞。看了这些代码之后发现BitSquid单纯地把所有的几何图形在整体水平上进行剔除,在每一次的阴影投射聚光灯和每六次的全方位灯光时进行。此外,这种剔除是通过昂贵的OBB(面向定界框)和锥测试的斗争中完成的。这就意味着在水平上有N个几何图形和L个全方位灯,就会有N*L*6个OBB-锥测试。就是这些测试花费了25毫秒。很显然,BitSquid已经有人意识到这部分代码可能变成一个瓶颈,因为这样的OBB-锥测试是SIMD优化的,并且在数个工作路线中并行处理。这也意味着我不可以让这些代码运行的更快,或是用更多的处理器去运行它。所以我只剩下了唯一一条路:让代码少运行几次。


解决方案

我只剩下一天时间来提高性能了,所以我采取的方法都对引擎(我只是大概了解的)产生了最小的改变。


早期

BitSquid的所有几何图形都有一个已经预先计算好的OBB包围盒,但是OBB测试总是很慢。我决定在每一个几何图形和接口上增加一个很粗糙、但是运行更快的原始定界框:一个球体。测试彼此排斥的两个球体要便宜得多,这在很大程度上让我们免于昂贵的OBB测试,也节省了大量时间。然而,我还没有时间去修改引擎和所有的工具,来为每个几何图形增加一个预先计算好的最小包围球。相反的,我决定来计算从OBB出来的最小包围半径。这个长宽高分别为W、H、D的包围盒外包围球的最小半径是 √((W/2)² + (H/2)² + (D/2)²), 但是这个平方根有点大。所以我决定让这个包围球稍微大一点,计算后得出半径为√3/2 * max(W, H, D)( 其中√3/2可以重复使用)。

  我还计算出所有灯光的包围球,这对于全方位投影灯来说当然微不足道,但是对聚光灯来说这也稍显复杂,但是我想到了一个快速逼近的办法,不过还没有想出具体的细节。

https://img8.php1.cn/3cdc5/24726/807/a5f053f5f2087a2d.gif

预先计算一组潜在的阴影投射

把遥远的几何图形送去剔除完全是在浪费时间。大多数的游戏引擎都在某种形式的层次结构(如BSP)中存储游戏关卡,这使得引擎能大段大段地远距离剔除。BitSquid却没有这样的功能,我也没有时间在一天之内加上这样一个结构。所以我在帧的开头添加了额外的步骤,这样我就可以在所有相交的相机阴影投射灯周围加上边界框。这形成了一个包含一切的边界框,这就可以在我们的视野范围内投射一个接口。然后,我就可以预先剔除拥有这个边界框场景中的一切,从而选出一组潜在的阴影投射。这就意味着,在之后的剔除过程中,我们只需要测试该场景中的几何图形,而不是所有的。

https://img8.php1.cn/3cdc5/24726/807/b7d66df09e8b31e1.gif

把全向光看作一个整体

如前所述,BitSquid把每个全向光看做六个聚光灯,并且单独为每个聚光灯进行剔除。我增加了一个预先通过,在那我剔除潜在阴影投射中每一个有包围球的泛光灯,从而挑选出靠近泛光灯的几何图形。只有通过这项粗略测试之后,我才会把这些几何图形发送去进一步测试,也就是使用原始的OBB-锥测试代码对六个虚拟聚光灯进行的测试。


结果

在引擎上加上所有这些步骤之后,我成功把剔除过程从25毫秒减到了2毫秒左右,我们的帧率也顺利稳定在了60FPS。任务完成!第二天我们发布了预览版。


教训

· 拥有你现在正在使用的所有中间软件的源代码是十分必要的。它不仅会帮助你发现问题所在,你也可以通过它来修复问题。

· 如果你想要加速代码运行,首先退一步,想一想这个代码是否可以不运行。


推荐阅读
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 本文详细介绍了Java编程语言中的核心概念和常见面试问题,包括集合类、数据结构、线程处理、Java虚拟机(JVM)、HTTP协议以及Git操作等方面的内容。通过深入分析每个主题,帮助读者更好地理解Java的关键特性和最佳实践。 ... [详细]
  • 网络攻防实战:从HTTP到HTTPS的演变
    本文通过一系列日记记录了从发现漏洞到逐步加强安全措施的过程,探讨了如何应对网络攻击并最终实现全面的安全防护。 ... [详细]
  • 360SRC安全应急响应:从漏洞提交到修复的全过程
    本文详细介绍了360SRC平台处理一起关键安全事件的过程,涵盖从漏洞提交、验证、排查到最终修复的各个环节。通过这一案例,展示了360在安全应急响应方面的专业能力和严谨态度。 ... [详细]
  • 本文探讨了如何在给定整数N的情况下,找到两个不同的整数a和b,使得它们的和最大,并且满足特定的数学条件。 ... [详细]
  • 本教程涵盖OpenGL基础操作及直线光栅化技术,包括点的绘制、简单图形绘制、直线绘制以及DDA和中点画线算法。通过逐步实践,帮助读者掌握OpenGL的基本使用方法。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 深入剖析 DEX 赛道:从 60 大头部项目看五大趋势
    本文通过分析 60 大头部去中心化交易平台(DEX),揭示了当前 DEX 赛道的五大发展趋势,包括市场集中度、跨链协议、AMM+NFT 结合、新公链崛起以及稳定币和衍生品交易的增长潜力。 ... [详细]
  • 深入解析:手把手教你构建决策树算法
    本文详细介绍了机器学习中广泛应用的决策树算法,通过天气数据集的实例演示了ID3和CART算法的手动推导过程。文章长度约2000字,建议阅读时间5分钟。 ... [详细]
  • 在金融和会计领域,准确无误地填写票据和结算凭证至关重要。这些文件不仅是支付结算和现金收付的重要依据,还直接关系到交易的安全性和准确性。本文介绍了一种使用C语言实现小写金额转换为大写金额的方法,确保数据的标准化和规范化。 ... [详细]
  • 在给定的数组中,除了一个数字外,其他所有数字都是相同的。任务是找到这个唯一的不同数字。例如,findUniq([1, 1, 1, 2, 1, 1]) 返回 2,findUniq([0, 0, 0.55, 0, 0]) 返回 0.55。 ... [详细]
  • 本文探讨了卷积神经网络(CNN)中感受野的概念及其与锚框(anchor box)的关系。感受野定义了特征图上每个像素点对应的输入图像区域大小,而锚框则是在每个像素中心生成的多个不同尺寸和宽高比的边界框。两者在目标检测任务中起到关键作用。 ... [详细]
  • 本文深入探讨了Linux系统中网卡绑定(bonding)的七种工作模式。网卡绑定技术通过将多个物理网卡组合成一个逻辑网卡,实现网络冗余、带宽聚合和负载均衡,在生产环境中广泛应用。文章详细介绍了每种模式的特点、适用场景及配置方法。 ... [详细]
  • 随着网络安全威胁的不断演变,电子邮件系统成为攻击者频繁利用的目标。本文详细探讨了电子邮件系统中的常见漏洞及其潜在风险,并提供了专业的防护建议。 ... [详细]
author-avatar
mis安小米
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有