GlowingTree880 / L4D2_LittlePlugins

L4D2_LittlePlugins
GNU General Public License v3.0
70 stars 7 forks source link

关于Infected_Control_Rework的性能问题 #19

Open Paimon-Kawaii opened 9 months ago

Paimon-Kawaii commented 9 months ago

我发现树树子你在使用PreThink进行针对式的找位刷新,而且为了找位成功写了一套复杂的时钟计算和队列控制机制。
因为代码太过复杂我只是简单的看了看,并且我现在没有机器只在本地的WSL上进行了测试,所以我对插件的性能有很大的疑问。
根据Valve在Wiki中所说:
image 很明显Valve建议Client的Think不宜太过昂贵,并且Think会在每帧调用(对服务器而言应该是每tick)
所以关于这套复杂的时钟刷特机制你有没有在服务端测试过多人多特的表现? 4人10特以及多人28特的性能表现怎么样?
根据我在本地的WSL服务器上的测试性能表现只能说是不太好,tick下降的很剧烈并且甚至造成了choke。
4人10特的tick掉到了80并且有5的choke,sv也有一定的下降,也许是我cpu核心分配的够多,sv大概也就从900多掉到了800;
而多人28特不知道是我配置的问题还是什么问题没有刷满(我知道多人不会到28,我是指没有刷满32个client)并且tick掉的也很多。
这些数据在WSL获得的应该都不是很准,希望树树子你有空可以测试并解决一下。

GlowingTree880 commented 9 months ago

关于这些问题,我在腾讯云服务器上进行了测试,服务器配置为 2核4G5M,CPU 为 AMD EPYC 7K62,使用音理服 2-4 日插件包,得到以下结果

  1. 性能消耗 70% 来自日志记录,非 debug 模式请将 inf_log_level 更改为 1 关闭日志记录或 4 仅打印到服务器终端或 32 仅记录错误日志,在开启日志记录时连续获取 10 次 10 特的特感刷新队列,会掉 5-10tick 与产生 2-4choke,关闭日志记录时仅会掉 1tick 与产生 1choke,连续获取 10 次 28 特的刷新队列也与连续获取 10 次 10 特刷新队列性能差异相仿
  2. 获取特感刷新队列函数最坏时间复杂度为 O(n^3) [Tank 在场时并配置了禁止刷新的特感类型,需要进行特感类型替换],最好为 O(n^2) [Tank 不在场时获取刷新队列],基于 (1) 的测试结论可以假定除了日志记录,大部分性能消耗来自于射线找位
  3. 经过观察发现在射线找位时不仅对每个生还者使用 PreThink 同时,在一次找位中使用射线的循环次数为 Tickrate 次(实际超过大概30次之后就不会被处理),对比 Anne 服仅使用 OnGameframe 进行对一个生还者的找位刷新增加了大量性能消耗。使用 PreThink 的意图是在一次刷新中可以对所有生还者进行找位,使得特感活的不那么集中,在昨天的更新中已经使用 OnGameframe 代替,一个位置刷新完特感后将会更换找位目标,并且射线循环次数更改为 Tickrate / 生还者人数 (最大 10 次)
  4. 经过测试昨天更新的版本,关闭日志 4 人 10 特情况下集中刷新使用射线找位会造成掉 3-5tick,2-5choke,对比电信服刷特插件与 11-28 刷特插件,性能相仿,sv 也没有剧烈波动
  5. 对于无法刷新 28 特的问题,经过测试发现是 inf_queue::getInfectedQueuePositionList 获取特感位置集合时读取每个特感的刷新位置变量 value 长度设置过小 (原 64),导致只能读取到 24 特,后面的配置无法读取,昨天已经修复。其次每种特感的 z_xxx_limit 值的和也需要够 28 特才可以正常刷新,今天测试可以正常刷满 31 个 client (音理插件包 l4dtoolz 上限 31),但是会造成双倍性能消耗 (掉 10-15tick,10+ choke)
  6. 在昨天的更新中新增了两种找位方式,仅使用 L4D_GetRandomPZSpawnPosition 函数与先使用射线判断位置有效且不可见,再增加 Nav 属性 OBSCURED 最后由 L4D_GetRandomPZSpawnPosition 决定刷新位置,如果射线找位性能消耗还是太大可以选用这两种方式
Paimon-Kawaii commented 9 months ago

如果不考虑日志与射线检测带来的性能消耗,那么刷特本身也是十分消耗性能的,因为CreateFakeClient这件事本身就很昂贵,所以在多特刷新时就会消耗大量性能,并且因为client急剧增多(在1tick内创建数十个client),必然导致tick骤降,choke上升。 我的建议是把刷新函数拆分到多个tick去执行,而不是在1个frame内一口气向服务器提交所有的创建client的请求,2次刷新见至少要间隔30tick以上,对刷特瞬间的性能消耗应该能有显著下降的效果

Paimon-Kawaii commented 9 months ago

因为CreateFakeClient这件事本身就很昂贵

而关于这一部分,我其实有一个其他的想法,在已知需要刷新的特感数量的情况下,可以保留一个特感池,在RoundStart时创建好所有特感,但是不去生成,直到对局开始;随后在特感死亡时,取消引擎对其的踢出操作(当然,这可能需要做一个Detour),将其重新放入特感池并重置状态,以备下次刷新时使用。 以此规避每次刷特都要创建client造成的性能消耗。

Paimon-Kawaii commented 8 months ago

因为CreateFakeClient这件事本身就很昂贵

而关于这一部分,我其实有一个其他的想法,在已知需要刷新的特感数量的情况下,可以保留一个特感池,在RoundStart时创建好所有特感,但是不去生成,直到对局开始;随后在特感死亡时,取消引擎对其的踢出操作(当然,这可能需要做一个Detour),将其重新放入特感池并重置状态,以备下次刷新时使用。 以此规避每次刷特都要创建client造成的性能消耗。

我把这个功能做出来了,你可以参考一下:https://forums.alliedmods.net/showthread.php?t=346270