游戏引擎学习第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 黑板:优化的基本过程 在游戏编程中,优化是一个非常重要的学习内容,尤其是想要成为专业开发者时。优化的核心是理解代码的执行速度,以及如何提升其性能。在这个阶段,已经…...
【EB-02】TC397 Tresos 最小工程配置
TC397 Tresos 最小工程配置 1. 新建demo 工程2. 配置消除错误2.1 ResourceM 设置2.2 McalLib模块配置3. 生成代码3.1 校验工程3.2 生成代码1. 新建demo 工程 新建工程 设置工程名称 选择芯片型号 选择添加模块 得到最小工程需求模块 2. 配置消除错误 2.1 ResourceM 设置 设置芯…...
深入理解WebSocket接口:如何使用C++实现行情接口
在现代网络应用中,实时数据传输变得越来越重要。通过WebSocket,我们可以建立一个持久连接,让服务器和客户端之间进行双向通信。这种技术不仅可以提供更快的响应速度,还可以减少不必要的网络流量。本文将详细介绍如何使用C来实现We…...
前端面试题
以下是一些前端面试题: 一、HTML/CSS部分 如何实现一个元素的背景颜色渐变效果,并且在不同浏览器中保持兼容性? 答案: 对于现代浏览器,可以使用标准的CSS渐变语法。 线性渐变示例(从左到右,红色到蓝色):background: linear - gradient(to right, red, blue);径向渐变…...
Win11 24h2 不能正常使用ensp的问题(已解决)
因为Win11 24h2的内核大小更改,目前virtualbox在7.1.4中更新解决了。所以Win11 24H2系统版本无法使用 5.x.xx的virtualbox版本,virtualbox对于这个5.x.xx版本早已停止维护,所以这个以后不会有调整。 对应的报错代码是 virtualbox错误代码&…...
Hyper-V初探
听说window自带虚拟机,小窃喜了一下,这样就不用下载第三方虚拟机软件了:VMware或者Oracle VirtualBox,但是本地搜索一看,发现没有安装,百度了一下说家庭中文版是个阉割版的系统,只有教育版&…...
IP协议
IP协议介绍 IP地址=目标网络+目标主机 IP协议是网络层协议 IP报头格式 [IP报头图片] IP报头解析 4 位版本号(version): 指定 IP 协议的版本, 对于 IPv4 来说, 就是 4. 4 位头部长度(header length): IP 头部的长度是多少个 32bit, 也就是 length 4…...
Django Admin: 实现基于数据库实际值的动态过滤器
在 Django Admin 中,我们经常需要使用 list_filter 来为管理界面添加过滤功能。然而,有时我们希望过滤器能够动态地反映数据库中的实际值,而不是依赖于预定义的选项。本文将介绍如何实现一个基于数据库实际值的动态过滤器,以 ECR 仓库的区域过滤为例。 问题背景 在管理 E…...
overflow-x: auto 使用鼠标实现横向滚动,区分触摸板和鼠标滚动事件的方法
假设一个 div 的滚动只设置了 overflow-x: auto 我们发现使用鼠标的滚轮是无法左右滚动的,但是使用笔记本电脑的触摸板,或者在移动设备上是可以滚动的。所以我们需要兼容一下鼠标的横向滚动功能。 我们可以监控 wheel 事件,然后根据位置来计…...
DPVS-2:单臂负载均衡测试
上一篇编译安装了DPVS,这一篇开启DPVS的负载均衡测试 : 单臂 FULL NAT模式 拓扑-单臂 单臂模式 DPVS 单独物理机 CLINET,和两个RS都是另一个物理机的虚拟机,它们网卡都绑定在一个桥上br0 , 二层互通。 启动DPVS …...
宇树科技13家核心零部件供应商梳理!
2025年2月6日,摩根士丹利(Morgan Stanley)发布最新人形机器人研报:Humanoid 100: Mapping the Humanoid Robot Value Chain(人形机器人100:全球人形机器人产业链梳理)。 Humanoid 100清单清单中…...
sqli-labs之Kali搭建靶场环境
背景: SQL注入是一种常见的Web安全漏洞,攻击者可以通过该漏洞在应用程序中执行任意的SQL命令。为了帮助开发者和安全研究人员更好地理解和防范SQL注入攻击,sqli-labs应运而生。它是一个开源项目,提供了一系列的SQL注入练习环境&a…...
Kafka在Windows系统使用delete命令删除Topic时出现的问题
在使用Windows的Kafka时,想要删除某一个主题,发现使用了delete之后会一直报警告。下面是我发现错误之后重新实测的Bug 先创建2个topic kafka-topics.bat --bootstrap-server localhost:9092 --topic test1 --createkafka-topics.bat --bootstrap-serve…...
JVM 面试题相关总结
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
动态记忆网络 DeepMind的MEMO架构允许在推理时动态读写记忆矩阵,记忆容量提升40倍
为了更深入地理解 MEMO 架构的意义,我来详细解读一下,并探讨它在实际应用中的潜力: MEMO 架构的核心思想 MEMO (Memorizing over Memorized) 架构的核心思想是 “层叠记忆”。 传统的记忆网络通常只有一个外部记忆模块,而 MEMO …...
go 并发 gorouting chan channel select Mutex sync.One
goroutine // head: 前缀 index:是一个int的指针 func print(head string, index *int) {for i : 0; i < 5; i {// 指针对应的int *indexfmt.Println(*index, head, i)// 暂停1stime.Sleep(1 * time.Second)} }/* Go 允许使用 go 语句开启一个新的运…...
【STM32 基于PID的闭环电机控制系统】
STM32 基于PID的闭环电机控制系统 目录 STM32 基于PID的闭环电机控制系统一、PID算法在STM32F103C8T6中的实现思路二、代码实现与解释三、PID算法的调试与优化四、总结 一、PID算法在STM32F103C8T6中的实现思路 基本概念 • 目标 :通过PID算法调节电机的转速&#…...
Linux命令后双减号符(--)的含义
个人博客地址:Linux命令后双减号符(--)的含义 | 一张假钞的真实世界 Unix/Linux下各种命令的参数,都是以减号符(-)后面跟单字符参数,比如-r)或者双减号符(--)…...
QT闲记-工具栏
工具栏通常用来放置常用的操作按钮,如QPushButton,QAction等。可以放置在顶部,底部,左侧,右侧,并且支持拖曳,浮动。 1、创建工具栏 通常通过QMainWindow 提供的addToolBar()来创建,它跟菜单栏一样,如果需要工具栏,一般情况下,我们设置这个类的基类为QMainWindow。 …...
Linux中ps -ef命令详解
ps -ef 是一个常用的 Unix/Linux 命令,用于显示当前系统中所有进程的详细信息。具体来说,ps 是 "process status" 的缩写,用于查看进程的状态。-ef 是 ps 命令的选项组合,用于指定输出的格式和内容。 选项解释…...
【JavaScript】《JavaScript高级程序设计 (第4版) 》笔记-Chapter20-JavaScript API
二十、JavaScript API JavaScript API 随着 Web 浏览器能力的增加,其复杂性也在迅速增加。从很多方面看,现代 Web 浏览器已经成为构建于诸多规范之上、集不同 API 于一身的“瑞士军刀”。浏览器规范的生态在某种程度上是混乱而无序的。一些规范如 HTML5&…...
详解分布式ID实践
引言 分布式ID,所谓的分布式ID,就是针对整个系统而言,任何时刻获取一个ID,无论系统处于何种情况,该值不会与之前产生的值重复,之后获取分布式ID时,也不会再获取到与其相同的值,它是…...
Linux 高级篇 日志管理、定制自己的Linux系统、备份与恢复
一、日志管理 (1)基本介绍 日志文件是重要的系统信息文件,记录了如用户登录、系统启动、系统安全、邮件及各种服务等相关重要系统事件在安全方面,日志也至关重要,它能记录系统日常发生的各类事情,可用于检…...
uniapp中@input输入事件在修改值只有第一次有效的问题解决
在uniapp中使用输入框,要求输入不超过7个字,所以需要监听输入事件,当每次输入文字的时候,就把输入的值截断,取前7个值。但是在input事件中,重新赋值的值发生了变化,但是页面上的还是没有变&…...
单片机 code RO-data RW-data ZI-data以及OTA学习
带着问题去学习:这些数据是什么?分别放在哪里, 是什么:我个人的理解 code 和RO-data 分别是代码和只读数据,RW-data以及ZI-data分别是读写数据和初始化数据。 codeRO-data的大小正好是所占用ROM的大小,RO…...
Jenkins 视图(View)
Jenkins 视图(View) 一、视图是什么 Jenkins 视图(View) 如下图中 All、Apps 都是 Jenkisn 中的 View 左侧如果有 New View 或者 点击 All 这一行最右侧的 号,都可以创建视图 二、视图(View)的作用 点击最左侧的 All 可以看到所有的任务 随着项目不断发展&am…...
【Deepseek+Dify】wsl2+docker+Deepseek+Dify部署本地大模型知识库问题总结
wsl2dockerDeepseekDify部署本地大模型知识库问题总结 基于ollama部署本地文本模型和嵌入模型 部署教程 DeepSeekdify 本地知识库:真的太香了 问题贴:启动wsl中docker中的dify相关的容器 发现postgre服务和daemon服务一直在重启,导致前端加…...
实战解析传统发电站智能化改造-第二期(带图带教程)
传统发电站的智能化改造,如同为老工匠配备高科技工具,提升效率与精准度。改造后的发电站兼具传统与智能,更高效、环保,适应现代能源需求。智慧系统搭建平台是连接感知层和应用层的桥梁,将原始数据转化为有价值的信息&a…...
鸿蒙5.0实战案例:基于自定义注解和代码生成实现路由框架
往期推文全新看点(文中附带全新鸿蒙5.0全栈学习笔录) ✏️ 鸿蒙(HarmonyOS)北向开发知识点记录~ ✏️ 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~ ✏️ 鸿蒙应用开发与鸿蒙系统开发哪个更有前景&#…...
项目设置内网 IP 访问实现方案
在我们平常的开发工作中,项目开发、测试完成后进行部署上线。比如电商网站、新闻网站、社交网站等,通常对访问不会进行限制。但是像企业内部网站、内部管理系统等,这种系统一般都需要限制访问,比如内网才能访问等。那么一个网站应…...
单片机 Bootloade与二进制文件的生成
单片机的 Bootloader 是一种特殊的程序,负责在单片机上电后初始化硬件、更新用户应用程序(固件),并将控制权移交给用户程序。以下是其运行机制和关键流程的详细说明: 1、单片机 Bootloader 的核心作用 固件更新&…...
open webui 部署 以及解决,首屏加载缓慢,nginx反向代理访问404,WebSocket后端服务器链接失败等问题
项目地址:GitHub - open-webui/open-webui: User-friendly AI Interface (Supports Ollama, OpenAI API, ...) 选择了docker部署 如果 Ollama 在您的计算机上,请使用以下命令 docker run -d -p 3000:8080 --add-hosthost.docker.internal:host-gatewa…...
深度学习每周学习总结Y1(Yolov5 调用官方权重进行检测 )
🍨 本文为🔗365天深度学习训练营 中的学习记录博客Y1中的内容 🍖 原作者:K同学啊 | 接辅导、项目定制 ** 注意该训练营出现故意不退押金,恶意揣测偷懒用假的结果冒充真实打卡记录,在提出能够拿到视频录像…...
HTML项目一键打包工具:HTML2EXE 最新版
HTML2EXE 工具可以一键打包生成EXE可执行文件。可以打包任意HTML项目或者是一个网址为单个EXE文件,直接打开即可运行。支持KRPano全景VR项目、WebGL游戏项目、视频播放、,课件打包、网址打包等。 一、功能特点 类别序号功能标题1支持程序图标自定义(支持…...
网络工程师 (43)IP数据报
前言 IP数据报是互联网传输控制协议(Internet Protocol,IP)的数据报格式,由首部和数据两部分组成。 一、首部 IP数据报的首部是控制部分,包含了数据报传输和处理所需的各种信息。首部可以分为固定部分和可变部分。 固定…...
springboot-自定义注解
1.注解的概念 注解是一种能被添加到java代码中的【元数据,类、方法、变量、参数和包】都可以用注解来修饰。用来定义一个类、属性或一些方法,以便程序能被捕译处理。 相当于一个说明文件,告诉应用程序某个被注解的类或属性是什么,…...
Pytorch实现之特征损失与残差结构稳定GAN训练,并训练自己的数据集
简介 简介:生成器和鉴别器分别采用了4个新颖设计的残差结构实现,同时在损失中结合了鉴别器层的特征损失来提高模型性能。 论文题目:Image Generation by Residual Block Based Generative Adversarial Networks(基于残留块的生成对抗网络产生图像) 会议:2022 IEEE Int…...
微信小程序模仿快播标签云滚动特效
说到快播,故事肯定就不少。用过的人都知道快播首页有个标签云的特效效果,就是渐隐渐显外加上下滚动,其实还挺好看的。至于其他故事嘛,因为没有酒,所以,还是来说说代码吧~ 一开始不是做这个特效需求…...
XUnity.AutoTranslator-deepseek——调用腾讯的DeepSeek V3 API,实现Unity游戏中日文文本的自动翻译
XUnity.AutoTranslator-deepseek 本项目通过调用腾讯的DeepSeek V3 API,实现Unity游戏中日文文本的自动翻译。 准备工作 1. 获取API密钥 访问腾讯云API控制台申请DeepSeek的API密钥(限时免费)。也可以使用其他平台提供的DeepSeek API。 …...
对比机器学习揭示了跨物种共享与特异性的脑功能结构|文献速递-医学影像人工智能进展
Title 题目 Contrastive machine learning reveals species -shared and -specific brainfunctional architecture 对比机器学习揭示了跨物种共享与特异性的脑功能结构 01 文献速递介绍 猕猴作为人类的动物模型,广泛用于研究大脑和行为的关键方面(G…...
Vue 和 React 响应式的区别
React 和 Vue 在响应式机制上的核心区别主要体现在数据变化侦测方式、更新触发逻辑和设计理念上,具体如下: 一、数据变化侦测方式 Vue 的响应式 原理:通过 Proxy(Vue3)或 Object.defineProperty(Vue2&#…...
MySQL主从架构
MySQL主从架构 MySQL REPLICATION 在实际生产环境中,如果对数据库的读和写都在一个数据库服务器中操作。无论是在安全性、高可用性,还是高并发等各个方面都是完全不能满足实际需求的,因此,一般来说都是通过主从复制(…...
基于ros2与gazebo的导航仿真案例
文章目录 前言操作1、创建docker容器2、安装ROS23、Gazebo安装4、Nav2安装5、测试 前言 导航的入门小案例 参考: Ubuntu24.04 ROS2 Jazzy Gazebo Harmonic安装教程Docs / Gazebo Harmonic 注意选择版本 ROS 2 documentation 操作 1、创建docker容器 sudo docke…...
《Python实战进阶》专栏 No.3:Django 项目结构解析与入门DEMO
《Python实战进阶》专栏 第3集:Django 项目结构解析与入门DEMO 在本集中,我们将深入探讨 Django 的项目结构,并实际配置并运行一个入门DEMO博客网站,帮助你在 Web 开发中更高效地使用 Django。Django 是一个功能强大的 Python Web…...
基于WebGIS技术的校园地图导航系统架构与核心功能设计
本文专为IT技术人员、地理信息系统(GIS)开发者、智慧校园解决方案架构师及相关领域的专业人士撰写。本文提出了一套基于WebGIS技术的校园地图导航系统构建与优化方案,旨在为用户提供高效、智能、个性化的导航体验。如需获取校园地图导航系统技…...
开源且免费的CMS系统有哪几个可以放心用?
既开源又免费的两全其美的CMS不多见,不过总会存在一些个例,给用户们带来更具有建设性的选择,以下是一些开源免费且值得信赖的CMS系统,可以根据你的需求选择合适的平台: 1、WordPress ▷ 特点:全球最流行的…...
逻辑架构与软件架构在PREEvision中的设计关系
1 Introduction 在如今汽车电子系统的开发过程中,系统架构设计是至关重要的环节。无论是汽车控制系统、信息娱乐系统,还是电动驱动系统,架构设计都决定了整个系统的功能、性能以及后期的可维护性和可扩展性。 在往期文章中,我们…...
DeepSeek vs ChatGPT:AI 领域的华山论剑,谁主沉浮?
一、引言 在当今科技飞速发展的时代,人工智能(AI)已然成为推动各领域变革的核心力量。而在人工智能的众多分支中,自然语言处理(NLP)因其与人类日常交流和信息处理的紧密联系,成为了最受瞩目的领…...
现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能
现场可以通过手机或者pad实时拍照上传到大屏幕的照片墙现场大屏电子照片墙功能,每个人都可以通过手机实时拍照上传到大屏幕上,同时还可以发布留言内容,屏幕上会同步滚动播放展示所有人的照片和留言。相比校传统的照片直播功能更加灵活方便,而…...
AF3 _process_single_hit 函数解读
AlphaFold3 中templates模块的_process_single_hit函数处理单个 HHsearch 比对的模板 TemplateHit,并从相应的 mmCIF 文件中提取模板特征,返回包含模板位置信息、比对质量等特征的 SingleHitResult 对象。它是 AlphaFold3 在模板模块中生成模板特征结构输入的重要步骤。 源代…...