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


教训

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

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


推荐阅读
  • 如何使用 CleanMyMac X 2023 激活码解锁完整功能
    本文详细介绍了如何使用 CleanMyMac X 2023 激活码解锁软件的全部功能,并提供了一些优化和清理 Mac 系统的专业建议。 ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 本题探讨了在大数据结构背景下,如何通过整体二分和CDQ分治等高级算法优化处理复杂的时间序列问题。题目设定包括节点数量、查询次数和权重限制,并详细分析了解决方案中的关键步骤。 ... [详细]
  • 本文深入探讨了Memcached的内存管理机制,特别是其采用的Slab Allocator技术。该技术通过预分配不同大小的内存块来有效解决内存碎片问题,并确保高效的数据存储与检索。文中详细描述了Slab Allocator的工作原理、内存分配流程以及相关的优化策略。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 本文将详细介绍多个流行的 Android 视频处理开源框架,包括 ijkplayer、FFmpeg、Vitamio、ExoPlayer 等。每个框架都有其独特的优势和应用场景,帮助开发者更高效地进行视频处理和播放。 ... [详细]
  • ElasticSearch 集群监控与优化
    本文详细介绍了如何有效地监控 ElasticSearch 集群,涵盖了关键性能指标、集群健康状况、统计信息以及内存和垃圾回收的监控方法。 ... [详细]
  • 深入解析RDMA中的队列对(Queue Pair)
    本文将详细探讨RDMA架构中的关键组件——队列对(Queue Pair,简称QP),包括其基本概念、硬件与软件实现、QPC的作用、QPN的分配机制以及用户接口和状态机。通过这些内容,读者可以更全面地理解QP在RDMA通信中的重要性和工作原理。 ... [详细]
  • 本文深入探讨了MySQL中常见的面试问题,包括事务隔离级别、存储引擎选择、索引结构及优化等关键知识点。通过详细解析,帮助读者在面对BAT等大厂面试时更加从容。 ... [详细]
  • 本文详细介绍了如何解决 Microsoft SQL Server 中用户 'sa' 登录失败的问题。错误代码为 18470,提示该帐户已被禁用。我们将通过 Windows 身份验证方式登录,并启用 'sa' 帐户以恢复其访问权限。 ... [详细]
  • 深入剖析JVM垃圾回收机制
    本文详细探讨了Java虚拟机(JVM)中的垃圾回收机制,包括其意义、对象判定方法、引用类型、常见垃圾收集算法以及各种垃圾收集器的特点和工作原理。通过理解这些内容,开发人员可以更好地优化内存管理和程序性能。 ... [详细]
  • 远程过程调用(RPC)是一种允许客户端通过网络请求服务器执行特定功能的技术。它简化了分布式系统的交互,使开发者可以像调用本地函数一样调用远程服务,并获得返回结果。本文将深入探讨RPC的工作原理、发展历程及其在现代技术中的应用。 ... [详细]
  • 在项目中使用 Redis 时,了解其不同架构模式(如单节点、主从复制、哨兵模式和集群)对于确保系统的高可用性和扩展性至关重要。本文将详细探讨这些模式的特点和应用场景。 ... [详细]
  • 问题描述:通过添加最少数量的括号,使得给定的括号序列变为合法,并输出最终的合法序列。数据范围:字符串长度不超过100。涉及算法:区间动态规划(Interval DP)。 ... [详细]
  • Redux入门指南
    本文介绍Redux的基本概念和工作原理,帮助初学者理解如何使用Redux管理应用程序的状态。Redux是一个用于JavaScript应用的状态管理库,特别适用于React项目。 ... [详细]
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社区 版权所有