热门标签 | 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。任务完成!第二天我们发布了预览版。


教训

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

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


推荐阅读
  • TCP协议中的可靠传输机制分析
    本文深入探讨了TCP协议如何通过滑动窗口和超时重传来确保数据传输的可靠性,同时介绍了流量控制和拥塞控制的基本原理及其在实际网络通信中的应用。 ... [详细]
  • 软件测试行业深度解析:迈向高薪的必经之路
    本文深入探讨了软件测试行业的发展现状及未来趋势,旨在帮助有志于在该领域取得高薪的技术人员明确职业方向和发展路径。 ... [详细]
  • Windows操作系统提供了Encrypting File System (EFS)作为内置的数据加密工具,特别适用于对NTFS分区上的文件和文件夹进行加密处理。本文将详细介绍如何使用EFS加密文件夹,以及加密过程中的注意事项。 ... [详细]
  • 深入解析WebP图片格式及其应用
    随着互联网技术的发展,无论是PC端还是移动端,图片数据流量占据了很大比重。尤其在高分辨率屏幕普及的背景下,如何在保证图片质量的同时减少文件大小,成为了亟待解决的问题。本文将详细介绍Google推出的WebP图片格式,探讨其在实际项目中的应用及优化策略。 ... [详细]
  • 菜鸟物流用户增长部现正大规模招聘P6及以上级别的JAVA工程师,提供年后入职选项。 ... [详细]
  • 深入解析层次聚类算法
    本文详细介绍了层次聚类算法的基本原理,包括其通过构建层次结构来分类样本的特点,以及自底向上(凝聚)和自顶向下(分裂)两种主要的聚类策略。文章还探讨了不同距离度量方法对聚类效果的影响,并提供了具体的参数设置指导。 ... [详细]
  • 实践指南:使用Express、Create React App与MongoDB搭建React开发环境
    本文详细介绍了如何利用Express、Create React App和MongoDB构建一个高效的React应用开发环境,旨在为开发者提供一套完整的解决方案,包括环境搭建、数据模拟及前后端交互。 ... [详细]
  • 本文详细介绍了在 Python 中如何有效去除浮点数末尾的无意义零及不必要的点,提供多种实现方法,并深入探讨了浮点数在计算机中的表示方式及其可能带来的精度问题。 ... [详细]
  • 高效的JavaScript异步资源加载解决方案
    本文探讨了如何通过异步加载技术处理网页中大型第三方插件的加载问题,避免将大文件打包进主JS文件中导致的加载时间过长,介绍了实现异步加载的具体方法及其优化。 ... [详细]
  • 本文详细记录了腾讯ABS云平台的一次前端开发岗位面试经历,包括面试过程中遇到的JavaScript相关问题、Vue.js等框架的深入探讨以及算法挑战等内容。 ... [详细]
  • 深入解析JVM内存模型与分配机制
    本文详细探讨了JVM内存结构的主要组成部分,包括Java虚拟机栈、Java堆、方法区等,并深入分析了HotSpot虚拟机的分代收集策略及其对不同内存区域的管理方式。 ... [详细]
  • 使用C#构建动态图形界面时钟
    本篇文章将详细介绍如何利用C#语言开发一个具有动态显示功能的图形界面时钟。文章中不仅提供了详细的代码示例,还对可能出现的问题进行了深入分析,并给出了解决方案。 ... [详细]
  • 探讨密码安全的重要性
    近期,多家知名网站如CSDN、人人网、多玩、开心网等的数据库相继被泄露,其中大量用户的账户密码因明文存储而暴露无遗。本文将探讨黑客获取密码的常见手段,网站如何安全存储用户信息,以及用户应如何保护自己的密码。 ... [详细]
  • 本文详细介绍了如何在ARM架构的目标设备上部署SSH服务端,包括必要的软件包下载、交叉编译过程以及最终的服务配置与测试。适合嵌入式开发人员和系统集成工程师参考。 ... [详细]
  • Bootstrap Paginator 分页插件详解与应用
    本文深入探讨了Bootstrap Paginator这款流行的JavaScript分页插件,提供了详细的使用指南和示例代码,旨在帮助开发者更好地理解和利用该工具进行高效的数据展示。 ... [详细]
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社区 版权所有