游戏引擎学习第113天
仓库: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
函数中添加FillPixel
和TestPixel
计数器,可以跟踪每次循环中测试和填充的像素数量。从结果来看,大部分测试的像素确实被填充了,表明并没有浪费太多时间在无效的像素上,这是一个积极的信号。
然而,值得注意的是,虽然FillPixel
和TestPixel
的数量相似,但这也暴露出一个潜在的问题:如果没有进行旋转,理论上应该能精确计算出需要填充的像素。因此,若两者没有完全一致,可能表明边界计算或边缘函数的实现存在问题,这是一个明显的警示信号,需要重新检查边界计算或修正边缘函数。
解读数据
正在讨论填充的像素数量。首先,需要确认实际填充了多少像素,因此检查了这个数字。然后,查看了一个名为“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() 之间的区别,以及何时使用它们?
new
和 malloc
之间的区别在于是否需要使用 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
基本使用:
- 首先,编译程序并启用调试信息:
g++ -g -o performance_test performance_test.cpp
- 然后,使用
perf
来记录程序性能:
perf record ./performance_test
- 程序运行完后,会生成一个
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
是一个较为传统的性能分析工具,可以帮助分析程序的执行性能。
使用步骤:
-
编译程序 时,确保加上
-pg
选项来启用性能分析:g++ -pg -o performance_test performance_test.cpp
-
执行程序,生成性能数据:
./performance_test
-
生成
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
这会生成一个包含函数调用次数、调用图等信息的文件,可以使用 kcachegrind
或 qcachegrind
来可视化分析。
修改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
相关文章:

游戏引擎学习第113天
仓库:https://gitee.com/mrxiao_com/2d_game_2 黑板:优化的基本过程 在游戏编程中,优化是一个非常重要的学习内容,尤其是想要成为专业开发者时。优化的核心是理解代码的执行速度,以及如何提升其性能。在这个阶段,已经…...
token是什么
在自然语言处理(NLP)和机器学习的背景下,token 是指模型在处理文本时的最小单位。通常,这个单位可以是单词、字符,或者词的一部分。具体来说,token 的定义取决于你使用的模型和它的分词方式。 举个例子&am…...

23. AI-大语言模型-DeepSeek赋能开发-Spring AI集成
文章目录 前言一、Spring AI 集成 DeepSeek1. 开发AI程序2. DeepSeek 大模型3. 集成 DeepSeek 大模型1. 接入前准备2. 引入依赖3. 工程配置4. 调用示例5. 小结 4. 集成第三方平台(已集成 DeepSeek 大模型)1. 接入前准备2. POM依赖3. 工程配置4. 调用示例…...
IPv6报头40字节具体怎么分配的?
目录 IPv6报头结构 字段详解 示例代码:IPv6报头的Python实现 输出示例 IPv6协议是为了解决IPv4地址耗尽问题而设计的下一代互联网协议。与IPv4相比,IPv6不仅提供了更大的地址空间,还简化了报头结构,提高了网络设备的处理效率。…...

驱动开发、移植
一、任务明确:把创龙MX8的驱动 按照我们的要求 然后移植到 我们的板子 1.Linux系统启动卡制作, sd卡 先按照 《用户手册—3-2-Linux系统启动卡制作及系统固化》 把创龙的Linux系统刷进去。 2. 把TLIMX8-EVM的板子过一遍 把刚刚烧好系统的sd卡插入 创…...

BFS与Flood Fill:算法原理、实现细节与复杂度分析
目录 1. 概述 2. BFS 的基本原理 3. Flood Fill 算法 4. BFS 实现 Flood Fill 的步骤 5. C 实现 6. 代码解析 7. 复杂度分析 8. 应用场景 总结 1. 概述 Flood Fill 算法是一种用于填充封闭区域的算法,常用于图像处理、绘图工具和游戏开发中。BFS(…...

计算机网络基础杂谈(局域网、ip、子网掩码、网关、DNS)
目录 1. 简单局域网的构成 2. IP 地址 3. 子网掩码 4. IP地址详解自定义IP 5. IP 地址详解 6. 网关 7. DNS 域名解析 8. ping 1. 简单局域网的构成 交换机是组建局域网最重要的设备,换句话说,没有交换机就没法搭建局域网 交换机不能让局域网连…...

雷龙CS SD NAND(贴片式TF卡)测评体验
一、产品概述 近期获赠雷龙科技(Longsto)推出的CS系列贴片式SD NAND存储解决方案,包含两片工业级贴片式NAND芯片(CSNP16GCR01-AOW)及全兼容转接板。该方案支持TF卡形态扩展,实现高可靠性嵌入式存储应用。 …...

【Alertmanager】alertmanager告警系统原理剖析与应用实战,应有尽有非常全面
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全…...
Java——权限修饰符
一、权限修饰符的继承访问规则 以下按访问范围从宽到窄排序: 修饰符同包同类同包子类同包非子类跨包子类跨包非子类public✔️✔️✔️✔️✔️protected✔️✔️✔️✔️❌默认(包级)✔️✔️✔️❌❌private✔️❌❌❌❌ 关键点…...
一周学会Flask3 Python Web开发-redirect重定向
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 前面我们学过渲染到模板页面,这个其实是一种内部的转发,浏览器地址栏地址没有变化。如果我们想重定向…...
python面向对象:方法
1. 实例方法 实例方法用于操作实例变量,必须包含 self 参数。 class Person:def __init__(self, name):self.name namedef greet(self):print(f"Hello, my name is {self.name}")person1 Person("Alice") person1.greet() # 输出ÿ…...

物联网简介集合
物联网(IoT)指的是物理设备(如电器和车辆)之间的互联互通。这些设备嵌入了软件、传感器和连接功能,使其能够相互连接并交换数据。这项技术实现了从庞大的设备网络中收集和共享数据,为打造更高效、自动化的系…...
centos下使用pyenv管理python版本
在 CentOS 上安装 pyenv 和 pyenv-virtualenv,可以按照以下步骤进行操作: ps: centos7 最高适配到3.9.* 步骤 1:安装依赖 首先,确保你的系统中安装了必需的依赖项。你可以使用以下命令安装它们: [root ~]# yum gro…...

C++:类与对象,定义类和构造函数
#define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> using namespace std; //如何让定义一个类 // 封装 // 1、将数据和方法定义到一起。 // 2、把想给你看的数据给你看,不想给你看的封装起来。 通过访问限定符来实现 class Stack { public: //1.成…...

【Java消息队列】应对消息丢失、重复、顺序与积压的全面策略
应对消息丢失、重复、顺序与积压的全面策略 引言kafka消息丢失生产者消费者重复消费顺序消费消息积压生产者消费者其他RabbitMQ消息丢失生产者事务机制,保证生产者发送消息到 RabbitMQ Server发送方确认机制,保证消息能从交换机路由到指定队列保证消息在 RabbitMQ Server 中的…...

解锁机器学习核心算法|神经网络:AI 领域的 “超级引擎”
一、神经网络:AI 领域的 “超级引擎” 在机器学习的庞大算法体系中,有十种算法被广泛认为是最具代表性和实用性的,它们犹如机器学习领域的 “十大神器”,各自发挥着独特的作用。这十大算法包括线性回归、逻辑回归、决策树、随机森…...
Android14(13)添加墨水屏手写API
软件平台:Android14 硬件平台:QCS6115 需求:特殊品类的产品墨水屏实现手写的功能,本来Android自带的Input这一套可以实现实时展示笔迹,但是由于墨水屏特性,达不到正常的彩屏刷新的帧率,因此使用…...
flyway的ignoreMigrationPatterns
1、概述 ignoreMigrationPatterns 是 Flyway 中的一个配置选项,用于指定在迁移过程中可以忽略的迁移脚本的模式。这个选项通常用于在特定情况下跳过某些迁移脚本的执行,例如在开发环境中跳过某些测试数据脚本,或者在特定条件下忽略某些已经不…...

25年2月通信基础知识补充:多普勒频移与多普勒扩展、3GPP TDL信道模型
看文献过程中不断发现有太多不懂的基础知识,故长期更新这类blog不断补充在这过程中学到的知识。由于这些内容与我的研究方向并不一定强相关,故记录不会很深入请见谅。 【通信基础知识补充7】25年2月通信基础知识补充1 一、多普勒频移与多普勒扩展傻傻分不…...

Day131 | 灵神 | 回溯算法 | 子集型 子集
Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣(LeetCode) 思路: 笔者写过很多次这道题了,不想写题解了,大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...
STM32+rt-thread判断是否联网
一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

vscode(仍待补充)
写于2025 6.9 主包将加入vscode这个更权威的圈子 vscode的基本使用 侧边栏 vscode还能连接ssh? debug时使用的launch文件 1.task.json {"tasks": [{"type": "cppbuild","label": "C/C: gcc.exe 生成活动文件"…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...

Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...

处理vxe-table 表尾数据是单独一个接口,表格tableData数据更新后,需要点击两下,表尾才是正确的
修改bug思路: 分别把 tabledata 和 表尾相关数据 console.log() 发现 更新数据先后顺序不对 settimeout延迟查询表格接口 ——测试可行 升级↑:async await 等接口返回后再开始下一个接口查询 ________________________________________________________…...

力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...

【Veristand】Veristand环境安装教程-Linux RT / Windows
首先声明,此教程是针对Simulink编译模型并导入Veristand中编写的,同时需要注意的是老用户编译可能用的是Veristand Model Framework,那个是历史版本,且NI不会再维护,新版本编译支持为VeriStand Model Generation Suppo…...

spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...
面试高频问题
文章目录 🚀 消息队列核心技术揭秘:从入门到秒杀面试官1️⃣ Kafka为何能"吞云吐雾"?性能背后的秘密1.1 顺序写入与零拷贝:性能的双引擎1.2 分区并行:数据的"八车道高速公路"1.3 页缓存与批量处理…...