游戏引擎学习第113天

ops/2025/2/22 11:08:53/

仓库:https://gitee.com/mrxiao_com/2d_game_2

黑板:优化的基本过程

在游戏编程中,优化是一个非常重要的学习内容,尤其是想要成为专业开发者时。优化的核心是理解代码的执行速度,以及如何提升其性能。在这个阶段,已经开始讨论如何有效地进行优化。

首先,需要收集统计数据,这是优化的第一步。收集数据的目的是为了了解程序运行缓慢的地方,找出具体的瓶颈,并分析这些部分的特点,比如它们处理了多少内容、执行的频率如何等。这些信息将帮助确定哪些代码部分需要被优化。

接下来,基于收集到的统计数据,进行估算。估算的目的是根据现有数据,推测出代码应该达到的理想性能。尽管很难做到准确,但可以通过估算得到一个大致的参考值,了解当前性能与理想性能之间的差距。

然后,进行效率和性能分析。效率和性能是两个不同的概念,效率指的是代码实际做了多少工作,而性能则是指完成这些工作的速度。提高效率意味着减少实际需要做的工作量,而提高性能则是让这些工作通过CPU更快速地完成。

一旦分析了效率和性能的差异,就可以判断出优化的潜力。最后,根据分析结果,开始编写优化代码,并测试其效果,看是否能够提升性能。

总结来说,优化过程包含四个步骤:收集数据、进行估算、分析效率和性能、以及编写优化代码。通过这一过程,可以系统地改进代码的执行效率和速度。

收集统计数据

首先要进行的是收集统计数据,这主要涉及编程的部分。我们需要开始收集一些统计信息。收集统计数据的方式是对代码进行“图表化”,即在代码中添加一些内容,使其能够报告统计数据给我们。虽然有一些商业程序可以分析代码,如 Intel 的 vtune 等,但这里不打算使用这些工具,而是展示如何自己编写并收集统计数据。如果进行深入优化,可能会选择使用一些工具,像 Intel 提供的工具,或者其他地方的工具,它们能提供一些程序运行的内部信息,这些是我们通过简单的仪表化可能无法得到的。

我们将专注于实现一个简单的统计收集方法。首先,需要知道的是,在 CPP 中,游戏的更新和渲染是我们负责的代码部分(即不在 Windows 系统内)。我们想要了解的是:游戏更新和渲染调用总共消耗了多少处理器周期。然后,我们还希望能看到,在其中一些代码片段的执行过程中,分别消耗了多少处理器周期,这样可以帮助我们大致了解哪些部分的代码可能会影响程序的运行速度。

虽然我们已经知道渲染器目前非常慢,因为在进行相关更改时已经观察到它的性能下降,所以此时并不一定需要重新分析渲染器部分的代码。但为了能够跟踪那些性能可能不那么直观的部分,还是决定进行一些通用的仪表化工作。这么做是为了展示如何找出性能瓶颈。实际上,如果只想尽可能快速地完成这个过程,我们可以直接获取游戏代码的总处理器周期数,再获取绘制矩形慢操作的处理器周期数,然后对比两者,直接看出慢的部分。但现在,决定采用稍微复杂一点的方法来实现这个目标。

重新审视 __rdtsc

目标是创建一个简单的系统,能够记录处理器周期并计算两个位置之间所花费的周期数。实际上,这种功能我们已经实现过一次,早在处理 Win32 平台层时就已经有过类似的代码,这段代码被称为 DTSC

DTSC 使用了一个处理器指令,称为 read time stamp counter,这是编译器通过内嵌指令生成的汇编指令。这个指令的作用是提供一个来自处理器的计数器,表示当前处理器在指令流中的位置,换句话说,它表示处理器执行了多少个周期。需要注意的是,“周期”这个词现在有些模糊,因为不同的处理器可能以不同的方式报告周期时间,特别是在启用了“SpeedStep”等技术的处理器上。

不过,现代处理器通常会给出在满速运行下的周期数,即使在启用节能模式(如笔记本的处理器会在不同负载下调整功耗)时,可能也能给出一个相对准确的周期计数。如果处理器频率时常变化,可能需要更小心地处理这些周期数据,但如果处理器在高性能模式下运行,假设 DTSC 提供的周期数与实际周期数接近,可以大致认为数据是准确的。

GAME_UPDATE_AND_RENDER:添加 __rdtsc 循环计数

可以像之前那样,通过记录开始和结束时的 __rdtsc 来计算经过的周期数。具体做法是,在函数的开始和结束处获取时间戳计数器的值,然后通过计算这两个值的差异来得出执行该代码段所花费的周期数。

简单来说,首先在代码段开始处记录一个起始周期计数,使用 __rdtsc 获取初始计数值;然后在代码段结束时记录一个结束周期计数,同样使用 __rdtsc。计算这两个值的差异,即可得到该代码段执行所消耗的处理器周期数。

基本的实现方式是,在代码开始时获取开始周期数,在结束时获取结束周期数,然后计算它们的差值。这样就能够得到该段代码执行所用的总周期数。

然而,在这个过程中存在一个问题。
在这里插入图片描述

在 game_platform.h 中引入 debug_cycle_counter

目前,我们没有办法将任何信息输出到屏幕上。虽然 Win32 代码能够输出内容,但游戏本身并没有输出的能力,因为游戏并没有显示功能,甚至没有相关的输入输出支持。因此,想要将统计数据输出到 Win32 层,需要简单地通过一些基本的方式实现。

为了实现这一点,可以做一些简单的操作。考虑到许多性能计数器的实现有时过于复杂,这里决定采取一个非常简单的办法:创建一个静态表来存储这些计数器。实现过程非常直接,基本上只是创建一个简单的结构来存储周期计数。

在游戏的内存管理部分,新增一个名为 debug cycle counter 的计数器。这个计数器会是一个 uint64_t 类型,用来记录周期数。在调试模式下,会有多个这样的计数器。举个例子,可以在游戏调试内存区域分配 256 个这样的计数器。

这些计数器只是用来调试时使用的,在发布版本时可以去掉。为了避免在发布版本中仍然保留这些调试信息,可以将它们放到一个名为 game_internal 的区域,只有在调试模式下定义 game_internal 时,才会启用这些计数器。这样,就能在开发和发布版本之间灵活切换,确保发布版本没有多余的调试信息。
在这里插入图片描述

支持 Linux 和 Mac 等平台的用户

为了避免对其他平台的支持产生问题,特别是处理器不支持 __rdtsc 指令的情况,可以通过宏来实现这一功能。首先,确定编译器是否支持 __rdtsc,然后在符合条件的情况下,使用宏来处理代码的开始和结束时间记录。

具体做法是,创建一个宏,名为 BEGIN_TIMED_BLOCK,它接受一个 ID,用于标识该时间块。宏的作用是记录代码块执行的开始时间,然后通过 __rdtsc 获取当前的周期数。同样的,创建另一个宏 END_TIMED_BLOCK,它在代码块结束时再次调用 __rdtsc 获取结束时间,然后计算两次周期数的差值,这个差值即为代码执行的周期数。

为了避免在同一作用域中多个时间块可能造成的命名冲突,使用了 C++ 的拼接操作符,将 ID 添加到计数器名称中。这样可以确保每个计时器都有唯一的名称,即使多个计时器位于同一函数内。

计时器的计数值会存储在一个本地变量中,而实际的周期数则会存储在相应的调试内存中。为了方便扩展,可以通过枚举类型定义不同的计数器,如 DebugCycleCounter_Count 等。这样可以使代码更简洁,同时也能避免硬编码。

最终,计时器的使用方式变得非常简单,只需要在代码块开始和结束时调用 BEGIN_TIMED_BLOCK(ID)END_TIMED_BLOCK(ID),并指定对应的 ID,即可自动记录并计算执行该块代码所消耗的处理器周期数。
在这里插入图片描述

编译、清理并运行

在进行编译之前,需要先解决一些错误。首先,调整类型定义,然后确保 time block 的值能够正确累加到 CycleCount 中。还需要解引用该值以确保其正确更新。

修改完成后,理论上如果运行游戏,每次游戏代码迭代时,周期计数器的值会逐步增加。接下来,考虑将代码切换到发布模式进行编译,了解为什么要这样做,以便在最终版本中进行优化和性能验证。
在这里插入图片描述

FillGroundChunk:关闭地面块

为了减少构建地面块时的运行时间压力,暂时关闭了填充地面块的功能。在这一过程中,地面块仍然在生成,但它们被填充为黄色,而不是实际内容。这么做可以确保在调试计时器功能时,地面块的构建不会对测试结果产生影响。等到计时器部分正常工作后,再重新启用地面块的填充功能。
在这里插入图片描述

查看计时器值

为了查看计时器的值,需要先确保在调用 game update and render 时,调试计数器中的所有计时器都被清零。可以使用一个类似 Windows Zero Memory 的方法来清除这些计时器。接着,创建一个新的函数来转储计时器信息,以便在每次更新和渲染后输出这些计时器的值。

handleDebugCycleCounters 函数中,首先要确保只有在启用了内部调试时,才执行相关代码,避免在没有调试计数器时出现编译错误。然后,通过一个循环遍历每个计数器,并使用 OutputDebugString 函数将每个计数器的值输出。此输出将包括计数器的索引及其相应的周期计数值。为了正确输出 64 位整数,需要使用合适的格式符号。最终,这些输出将帮助追踪每个计时器的状态并调试游戏性能。

这一过程为调试提供了基础输出,但将来可能会通过自定义调试服务进行更精细的处理。目前,这种方法虽然简单,但足以满足当前的需求。
在这里插入图片描述

在这里插入图片描述

运行游戏并查看调试循环计数

调试输出现在可以显示每帧游戏运行所消耗的周期数。从输出中可以看到,当前每帧的周期数相对一致,但却非常高。具体来说,当前游戏代码本身就已经消耗了debug大约 31 亿个周期,release 1.2亿个周期 而不包括 Windows 操作系统的开销。根据之前的计算,每个 CPU 核心每帧应当消耗的周期数应该低于 1.07 亿,而现在的数字远远超出了这个预期。这表明,当前代码的性能开销较大,需要进一步优化。
debug模式 = 0: 3,289,537,500 三十多亿个周期吗
在这里插入图片描述

release模式 = 0: 119925458
在这里插入图片描述

确认我们知道的情况

从周期计数来看,可以确认目前的帧率远低于期望值,游戏的性能无法达到理想的水平。周期计数显示的数字远远超过了实际发布时所能接受的范围,这证明了当前的代码效率不高,无法支持足够的帧率。因此,接下来的步骤是分析并改进代码,优化性能以提高帧率。

RenderGroupToOutput:添加一个定时块

为了更清楚地了解每个部分消耗的周期,可以通过在渲染和模拟等关键操作中添加计时器来跟踪时间。例如,渲染的处理是通过 RenderGroupToOutput 函数完成的,可以在该函数内部插入一个时间块,标记为 DebugCycleCounter_RenderGroupToOutput。通过这种方式,能够记录并显示渲染过程中花费的周期,从而进一步分析各个部分的性能瓶颈。
在这里插入图片描述

引入 DebugGlobalMemory 以便在不应访问的情况下仍然能够访问这些计时信息

接下来,为了避免频繁传递调试周期计数器,可以使用一个全局变量来存储调试内存,从而让所有地方都能访问到它。这样,虽然通常会避免使用全局变量,但在这种性能分析的情况下,它有助于简化调试流程。这个全局变量 DebugGlobalMemory 会在内部模式下定义,并且只有在内部模式下编译时才会有效,从而避免在发布版本中误用。

此外,确保这些调试宏在其他平台上完全被编译掉,不会影响正常运行。通过在游戏更新和渲染过程中设置这个全局变量,可以保证每次访问时都能获得调试数据,而无需改变程序的架构。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

注意两个循环计数之间的差异

通过查看调试周期计数器的输出,可以清楚地看到游戏的整体性能情况。通过对比不同部分的周期数,可以得出以下结论:整个游戏的运行只消耗了大约 267286次周期,而渲染部分则消耗了剩余的大部分周期,约111686539多次。这一数据确认了之前的观察结果,即游戏本身的计算工作量较少,而渲染过程则是性能瓶颈的主要来源。

尽管这还不是非常精确的分析,但已经能大致勾画出性能瓶颈的位置,验证了游戏中明显的性能下降正是由于渲染部分的高周期消耗所导致的。接下来,计划继续深入分析并优化这一部分。

0: 111953825 - 1: 111686539 = 267286
在这里插入图片描述

确认 DrawRectangleSlowly 是罪魁祸首

为了进一步确认性能瓶颈所在,决定对 DrawRectangleSlowly 这一函数进行检查。虽然最初的假设是渲染部分可能导致性能问题,但在没有确认之前,不能仅凭直觉做出优化,因为这可能会浪费时间在非关键部分。实际运行时,结果证明了这一假设,几乎所有的时间都花费在了 DrawRectangleSlowly 函数上。

通过对比不同函数的周期消耗,发现大部分时间的消耗确实集中在渲染部分,尤其是在绘制小三角形的过程中。这也证明了渲染环节仍然是性能瓶颈的核心。虽然渲染过程中可能还有一些其他小的耗时操作,但问题的主要根源已经明确,为后续的优化提供了方向。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

引入 HitCount 以了解 DrawRectangleSlowly 是因为本身慢,还是因为调用次数太多

为了进一步确认 rawRectangleSlowly 是否真的因为本身效率低导致性能问题,还是因为其调用频率过高,决定在现有的周期计数系统中加入执行次数的统计。通过为每个函数路径增加一个“命中计数”,可以追踪每个代码块被执行的次数。

这样,除了更新周期计数(DTSC),还可以增加对命中计数的更新,记录函数被调用的次数。为了展示这些数据,扩展了输出,除了显示周期计数外,还增加了每个函数调用的命中次数、周期数和每次调用的平均周期数(周期数除以命中次数)。这有助于确认是否是函数调用过于频繁导致了性能瓶颈。对于命中计数为零的情况,避免除以零的错误,只在有命中次数时进行输出,确保输出的数据有效。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

发现我们调用渲染器 13 次,DrawRectangleSlowly 被调用了 202 次

通过加入命中计数,发现了一个之前未曾注意到的现象:渲染器被调用了13次,原因可能是因为正在刷新地面块。原本认为渲染器只会调用一次,但实际情况并非如此。另外,rawRectangleSlowly 被调用了202次。通过这些数据可以看出,问题并不是矩形绘制过多,而是这些矩形本身足够大且绘制速度较慢,导致每个矩形的绘制消耗了大量时间。

这些结果确认了之前的假设,但更重要的是,这个过程展示了如何通过层级化的调试方法来确认性能瓶颈所在。在不知道时间消耗具体位置的情况下,这种方法非常有效,能够帮助快速定位性能问题。

计算我们正在填充的像素数

为了进一步分析性能,决定临时添加一个计数器来统计在渲染过程中实际填充了多少像素。通过在rawRectangleSlowly函数中添加FillPixelTestPixel计数器,可以跟踪每次循环中测试和填充的像素数量。从结果来看,大部分测试的像素确实被填充了,表明并没有浪费太多时间在无效的像素上,这是一个积极的信号。

然而,值得注意的是,虽然FillPixelTestPixel的数量相似,但这也暴露出一个潜在的问题:如果没有进行旋转,理论上应该能精确计算出需要填充的像素。因此,若两者没有完全一致,可能表明边界计算或边缘函数的实现存在问题,这是一个明显的警示信号,需要重新检查边界计算或修正边缘函数。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

解读数据

正在讨论填充的像素数量。首先,需要确认实际填充了多少像素,因此检查了这个数字。然后,查看了一个名为“scratch buffer”的内容,这显示了已经测试过的像素数量。接着,考虑到当前分辨率,想知道屏幕上实际上有多少像素。分辨率为1204 x 800,计算结果为963200。这一过程被重复进行了确认。
在这里插入图片描述

TestFill -> 1531114h
total -> 963200

注意我们操作的像素数并没有比屏幕上的总像素数多多少

通过比较当前填充的像素数量与屏幕上总像素数,可以看出,实际上填充的像素并没有超过总像素的太多,这意味着在过度绘制(overdraw)方面的表现还算不错。过度绘制是指在渲染过程中,同一个像素被多次填充的情况,当前的情况表明并没有出现过多的无效绘制。

黑板:过度绘制

过度绘制(overdraw)是指在渲染过程中,像素被多次触及的情况。理想的渲染器应该只触及每个像素一次,并且准确地给每个像素上色。理想情况下,渲染器的操作数应该等于屏幕上总像素的数量,这意味着每个像素仅被绘制一次。过度绘制衡量的是渲染器的效率,表示渲染器在渲染过程中需要重新绘制的像素次数。过度绘制的增加意味着渲染效率低下,因为每次覆盖之前的像素都浪费了工作。因此,过度绘制与渲染效率密切相关。

目前的测试结果显示,测试像素的数量与屏幕上的像素数量差距不大,这表明过度绘制的情况并不严重,渲染效率还算不错。然而,当前屏幕上没有太多内容,因此随着内容的增加,过度绘制的数量可能会上升,需要通过创建一些测试场景来故意增加过度绘制,以便进一步优化这一指标。

黑板:进度报告

经过这些步骤,现在已经了解了一些关键的性能特点。首先,已经确定了性能瓶颈的具体位置,并且了解了一些关于该瓶颈的特征。目前,并没有出现显著的过度绘制问题,渲染效率还可以。然而,存在较大的速度问题。从周期数来看,每个像素的处理时间约为160个周期。通过将像素数量与所消耗的周期数相除,可以得出每个像素大约需要160个周期的处理时间。
在这里插入图片描述

关闭 NormalMap

目前,每个像素的处理时间大约是160个周期,这为进一步评估性能提供了一些信息。接下来,通过不进行法线贴图合成的实验,可以进一步了解渲染速度的变化。在没有法线贴图的情况下,渲染速度的差距相对较小,处理周期也有所减少。这表明法线贴图合成对当前性能的影响不小。
在这里插入图片描述

理解大致的时间估算

目前的周期计数只是一个大概的估算,不能准确反映执行时间。即使游戏状态没有变化,渲染的内容也相同,周期数依然会波动。这是因为现代处理器复杂,存在内存访问延迟、缓存命中、任务切换等因素,这些都会引入不确定性,导致每次周期计数不同。为了获得更准确的周期计数,可以通过多次运行同一个操作,取最低周期数来减少这些变动,这样可以排除外部干扰并尽量将操作保持在缓存中,从而更精确地测量最优周期数。然而,目前所看到的周期计数只能作为粗略的参考,不能视为绝对准确的数值。在查看分析结果时,必须理解这些数字的含义以及它们的准确性。

进行估算

为了估算每个像素所需的操作,首先需要分析渲染过程中的各种操作步骤。需要进行的操作包括变量重命名、减法、点积运算、取反、比较、屏幕空间坐标计算、乘法和采样等。一些步骤(如旋转、颜色空间转换和分解)可能需要额外的乘法、加法、减法和位移操作,特别是在进行采样时。对于内存查找部分,涉及的操作会更复杂,因此需要考虑到的指令总数大致为96条。

通过粗略估算,如果每个指令需要2个周期,那么每个像素可能需要200个周期。若能够优化处理,使得每个周期可并行处理四个像素,那么每个像素的周期数将降到50个周期。假设通过优化将操作降到这种程度,填充当前屏幕的像素可能需要4200万个周期。基于当前的测试像素数,如果能够减少到100个周期以下,则可能达到预期的性能目标。

最终,优化的目标是尽量将每个像素的周期数降低到100周期以内,同时考虑到法线映射等额外开销。这一过程需要在未来进一步细化和验证,以确保渲染效率符合需求。
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

AVX-512 热议

如果使用像AVX2或即将推出的AVX-512处理器,就可以显著提高效率。相比每次处理4个像素,新的处理器能够每次处理16个像素,这将大大提高渲染速度,带来显著的性能提升。因此,考虑到这种硬件进展,可能会在未来优化渲染性能时发挥巨大作用。

为什么计数器上没有文本标签?

在计数器上没有文本标签的原因是,目前并不需要它们。虽然可以考虑以后加上,但目前并不觉得有必要。

对不起,如果这是你之前讲过的内容,但能否解释一下 C++ 中 new 和 malloc() 之间的区别,以及何时使用它们?

newmalloc 之间的区别在于是否需要使用 C++ 的特性。malloc 仅分配内存,而 new 不仅分配内存,还会调用对象的构造函数。因此,使用带有构造函数的对象时,必须使用 new,除非打算手动调用构造函数。对于没有构造函数的对象,new 并不会做任何额外的事情,malloc 就足够了。类似地,对于带有析构函数的对象,需要使用 delete 来释放内存,而对于没有析构函数的对象,可以直接使用 free 来释放内存。

你是否可以通过做更多的工作来省略一些指令,例如 d - XAxis,接着 d - XAxis - YAxis。那应该只需要 2 条指令吗?

通过对之前的计算结果进行复用来减少指令的数量是完全可行的。例如,可以通过先计算 D - XAxis,然后再计算 d - XAxis - YAxis 来节省一些计算。虽然编译器已经开启优化,可能会自动去除一些冗余的操作,但依然会尽量手动进行优化,避免多余的计算。

你认为我们会在软件渲染器中使用多线程吗?

在软件渲染中,确实会考虑使用多线程。计划将屏幕分成四个部分,并在不同的线程中分别渲染每一部分。这种方式能够提高渲染效率,并利用多核处理器的优势。

是否可以每次操作都做四倍优化?

处理器如何发布指令以及如何管理不同指令的执行。处理器的工作方式是,它只能在有足够空闲单元的情况下发布指令。可以将处理器看作由多个较小的单元组成,每个单元负责不同的任务,例如加法或位移操作。如果两个指令是独立的,可以在同一周期内分别发给不同的单元执行。如果两个指令相同且依赖于相同的资源(如同为位移指令),则无法在同一周期执行。

优化指令发布的过程涉及深入了解处理器的结构,例如每个单元的数量和处理器是否能够在一个周期内同时执行多个指令。这个过程是复杂的,需要了解处理器的每个细节,并考虑它的乱序执行窗口。对于x64处理器来说,由于其复杂性和多样性,这类优化特别困难。尽管如此,在某些情况下,确实有必要深入了解处理器,以实现最大化的性能优化。

我的意思是,把它放入宽指令(SIMD)中

关于是否可以在指令中进行四倍(quad pump)操作的问题,回答是否定的。虽然浮点和整数运算通常可以进行四倍泵,但内存访问操作(例如纹理获取和计算需要加载的纹素位置)是无法进行的。这是因为SSE2、AVX及其之前的指令集在内存访问方面有局限,无法处理宽操作(wide operations)。虽然AVX-512可能解决了这个问题,因为Larrabee指令集就包含了这些功能,并且AVX-512已经将这些功能整合到主流指令集中了,但在目前的指令集下,内存访问仍然是一个瓶颈。

“Quad pump” 这个术语通常指的是在一个时钟周期内并行执行四个操作或指令的能力。它是在处理器架构中,特别是与SIMD(单指令多数据)指令集相关的一个术语,比如AVX(高级向量扩展)系列。

具体来说,“quad pump” 的意思是在一次时钟周期内,通过使用宽度较大的指令集(例如 AVX-512),能够并行处理更多的数据,通常是每个时钟周期处理四倍于常规操作的数据量。

例如,在一个支持 AVX 的处理器上,如果能够处理四个浮点数或者四个整数数据项,就可以称其为“quad pumping”。这使得处理器在执行某些任务时,能够更高效地同时处理多个数据项,显著提高性能,尤其在图形处理、科学计算等需要大量并行运算的领域。

linux 下面perf

在Linux中执行CPU profiling通常可以使用多种工具,以下是几种常见的方法:

performance_test.cpp

#include <iostream>
#include <vector>
#include <cmath>
#include <chrono>// 模拟一个计算密集型函数
void compute_heavy_task(int size) {std::vector<double> vec(size, 0.0);for (int i = 0; i < size; ++i) {for (int j = 0; j < size; ++j) {vec[i] += sqrt(i * j);}}
}int main() {int size = 1000;  // 调整为较大的数值,增加计算密集性// 记录开始时间auto start = std::chrono::high_resolution_clock::now();// 执行计算密集型任务compute_heavy_task(size);// 记录结束时间并计算花费的时间auto end = std::chrono::high_resolution_clock::now();std::chrono::duration<double> duration = end - start;std::cout << "Task completed in " << duration.count() << " seconds." << std::endl;return 0;
}

1. 使用 perf 工具

perf 是一个强大的性能分析工具,可以用于收集CPU性能数据,检查程序瓶颈。

安装 perf(如果尚未安装):

sudo apt-get install linux-tools-common linux-tools-generic

在这里插入图片描述

基本使用:

  1. 首先,编译程序并启用调试信息:
g++ -g -o performance_test performance_test.cpp

在这里插入图片描述

  1. 然后,使用 perf 来记录程序性能:
perf record ./performance_test

在这里插入图片描述

  1. 程序运行完后,会生成一个 perf.data 文件。可以使用以下命令查看性能报告:
perf report

在这里插入图片描述

采样特定的事件:
你可以通过 perf 来捕获特定的事件,比如 CPU cycles 或缓存命中等:

perf record -e cycles -p <pid>

这会捕获指定进程(PID)的 CPU 周期事件。

rm 删掉除.cpp 的其他文件

要删除当前目录下除了 .cpp 文件之外的其他文件,可以使用 find 命令结合 rm 来实现。以下是一个示例:

find . -type f ! -name "*.cpp" -exec rm -f {} \;

在这里插入图片描述

解释:

  • find .:从当前目录开始查找。
  • -type f:只查找文件。
  • ! -name "*.cpp":排除 .cpp 文件,只删除不以 .cpp 结尾的文件。
  • -exec rm -f {} \;:对找到的每个文件执行 rm -f 命令,删除文件。

注意:

  • 确保你在正确的目录中执行命令,避免删除了不该删除的文件。
  • 如果不确定,可以先使用 find 命令列出将被删除的文件,例如:
    find . -type f ! -name "*.cpp"
    
    这将列出所有不以 .cpp 结尾的文件,你可以确认后再执行删除操作。
    在这里插入图片描述

2. 使用 gprof 工具

gprof 是一个较为传统的性能分析工具,可以帮助分析程序的执行性能。

使用步骤:

  1. 编译程序 时,确保加上 -pg 选项来启用性能分析:

    g++ -pg -o performance_test performance_test.cpp
    
  2. 执行程序,生成性能数据:

    ./performance_test
    

在这里插入图片描述

  1. 生成 gmon.out 文件后,使用 gprof 查看性能报告:

    gprof ./performance_test gmon.out > analysis.txt
    

    在这里插入图片描述

    在这里插入图片描述

3. 使用 valgrind(callgrind 模式)

valgrind 是一个用于内存调试的工具,但其 callgrind 模式可以用于进行性能分析,尤其适用于 CPU 性能分析。

安装 valgrind

sudo apt-get install valgrind

在这里插入图片描述

使用 callgrind

valgrind --tool=callgrind ./performance_test

在这里插入图片描述

在这里插入图片描述

这会生成一个包含函数调用次数、调用图等信息的文件,可以使用 kcachegrindqcachegrind 来可视化分析。
在这里插入图片描述

修改WSL 界面的字体大小

1. 安装字体

sudo apt update
sudo apt install fontconfig

在这里插入图片描述

2. 安装 x11-xserver-utils

xrdb 工具通常包含在 x11-xserver-utils 包中。在 WSL 中执行以下命令来安装它:

sudo apt update
sudo apt install x11-xserver-utils

在这里插入图片描述

安装完成后,您应该能够使用 xrdb 命令。

3. 接下来,创建一个名为.Xresources的文件并在其中添加以下内容:

Xft.dpi: 220

这将把Xft的dpi设置为220,从而放大字体大小。
在这里插入图片描述

3. 再次尝试执行 xrdb

安装完 x11-xserver-utils 后,您可以重新尝试运行 xrdb

xrdb -merge ~/.Xresources

在这里插入图片描述

如果没有错误提示,并且没有显示任何输出,说明 X 资源文件已经成功合并。
在这里插入图片描述

在这里插入图片描述

总结

  • 如果 xrdb 找不到,安装 x11-xserver-utils 包。
  • 确保配置的 ~/.Xresources 文件格式正确。
  • 使用 xrdb -merge ~/.Xresources 来合并配置文件。

valgrind doc: https://valgrind.org/docs/manual/index.html


http://www.ppmy.cn/ops/160494.html

相关文章

LLaMA-Factory|微调大语言模型初探索(3),qlora微调deepseek记录

前言 上篇文章记录了使用lora微调llama-1b,微调成功,但是微调llama-8b显存爆炸,这次尝试使用qlora来尝试微调参数体量更大的大语言模型,看看64G显存的极限在哪里。 1.Why QLora? QLoRA 在模型加载阶段通过 4-bit 量化大幅减少了模型权重的显存占用。QLoRA 通过 反量化到 …

oppo,汤臣倍健,康冠科技,高途教育25届春招内推

oppo&#xff0c;汤臣倍健&#xff0c;康冠科技&#xff0c;高途教育25届春招内推 ①康冠科技 【职位】算法、软件、硬件、技术&#xff0c;结构设计&#xff0c;供应链&#xff0c;产品&#xff0c;职能&#xff0c;商务 【一键内推】https://sourl.cn/2Mm9Lk 【内推码】EVBM8…

【Java学习】多态

目录 一、方法相同 二、方法重写 1.概念 2.条件 三、向上转型 1.概念 2.方式 四、方法绑定 五、多态 一、方法相同 方法相同要求方法名相同、参数列表相同、返回值类型相同(与两方法修饰的访问限定符相不相同、静态非静态状态相不相同无关)&#xff0c;而且在子类与父…

Linux基础 -- 中断子系统之级联中断

Linux 级联中断 (irq_set_chained_handler_and_data) 详解 1. irq_set_chained_handler_and_data() 介绍 1.1. API 定义 void irq_set_chained_handler_and_data(unsigned int irq, irq_flow_handler_t handler, void *data);参数 irq&#xff1a;需要绑定处理函数的中断号&…

批量给文本中的每行内容加入一行空行

我这里有一个txt的文本内容要处理。需要每6行加入一行空行。 我们可以考虑用python脚本去实现。但是python脚本还要下载python。然后安装以后用cmd去运行python脚本。对不是技术党来说不友好。 我们考虑用现成的notepad工具去实现。首先下载一个notepad的软件。 然后。 用no…

ICRA2024:CoLRIO,用于机器人群体的激光雷达测距-惯性集中状态估计

文章目录 摘要I. 引言II. 相关工作A. 激光雷达惯性里程计B. 多机器人定位和映射 III. 相对状态估计框架A. 单个机器人前端B. 群体定位 IV. 实验V. 结论 摘要 摘要 —— 使用不同异构传感器进行协作状态估计对于在无GPS环境中运行的机器人群体来说是一个基本前提&#xff0c;这…

Windows使用docker部署fastgpt出现的一些问题

文章目录 Windows使用docker部署FastGPT出现的一些问题1.docker部署pg一直重启的问题2.重启MongoDB之后一直出现“Waiting for MongoDB to start...”3.oneapi启动不了failed to get gpt-3.5-turbo token encoder Windows使用docker部署FastGPT出现的一些问题 1.docker部署pg一…

如何设计提示词让AI以思维链方式回答问题

什么是思维链&#xff08;Chain of Thought, CoT&#xff09;&#xff1f; 思维链 (CoT) 是一种提示技术&#xff0c;它引导语言模型逐步思考问题&#xff0c;模拟人类的思考过程。与直接给出答案不同&#xff0c;CoT 提示会让模型在给出最终答案之前&#xff0c;先展示一系列…