PERF: thaumicadditions 奇点落地后每 tick 把同一 AABB 的两轮实体查询重复 128 次(256 次/秒 变 5120 次) #21

Closed
opened 2026-11-04 09:03:55 +00:00 by layue13 · 2 comments
Owner

调研编号:

现象

奇点实体落地后(inGround)的 onUpdate 每 tick 起一个 128 轮的循环,但循环体里没有任何东西随轮次变化:AxisAlignedBB 每轮都用 posX/posY/posZ ± this.radius 重新构造(radius 是字段、在循环外的 :183/:191 才被改),随后的两次 getEntitiesWithinAABB(Entity.class / EntityLivingBase.class, ent) 因此每轮返回完全相同的结果;循环内也没有 break/return。除此之外每轮还做:对同一批 l2 的 16 次幂等赋值(motion=(target-pos)/10,不改坐标)、以及 3 次 getBlock +(命中时)整段 breakBlock 破坏逻辑。

危害类与优先级

  • PERF(每拍全扫 + 同参数重复求值 + 无局部承接)→ 标签 type/perf
  • p2 每 tick 每只奇点固定 256 次 AABB 实体查询、128×16 次幂等向量赋值、~128×3 次取块;而奇点会自我增殖(:95-107 吃 Alumentum 会 spawnEntityInWorld(new EntitySingularity)),乘数还会继续变大

证据(生效源码)

  • 载体:TDLM/thaumicadditions + 分支 origin/main + pin cded7cd3ee59(source 类仓,无 patch 层,代码可点开)
  • 位置:src/main/java/com/pengu/thaumcraft/additions/entity/EntitySingularity.java:70-149;radius 定义 :22、仅在循环外 :183/:191 修改
  • 关键原文:
// EntitySingularity.java:70-96  每 tick 起 128 轮循环,每轮重建同一个 AABB 并跑同一对实体查询
            for(p = 0; p < 128; ++p) {
               AxisAlignedBB ent = AxisAlignedBB.getBoundingBox(this.posX - (double)this.radius, this.posY - (double)this.radius, this.posZ - (double)this.radius, this.posX + (double)this.radius, this.posY + (double)this.radius, this.posZ + (double)this.radius);
               l = this.worldObj.getEntitiesWithinAABB(Entity.class, ent);
               l2 = this.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, ent);

// EntitySingularity.java:112-128  每轮再对同一批 l2 做 16 次幂等赋值(motion = (target - pos)/10,位置不改)
               for(i = 0; i < 16; ++i) {
                  for(var22 = 0; var22 < l2.size(); ++var22) {
                     ...
                     motionX1 = this.posX - rz;
                     ((EntityLivingBase)l2.get(var22)).motionX = motionX1 / 10.0D;
                     ...

// EntitySingularity.java:144-149  每轮再取 6 次随机数与方块
               i = (int)this.posX + this.worldObj.rand.nextInt(this.radius) - this.worldObj.rand.nextInt(this.radius);
               var22 = (int)this.posY + this.worldObj.rand.nextInt(this.radius) - this.worldObj.rand.nextInt(this.radius);
               var23 = (int)this.posZ + this.worldObj.rand.nextInt(this.radius) - this.worldObj.rand.nextInt(this.radius);
               if(this.worldObj.getBlock(i, var22, var23) != Blocks.air && this.worldObj.getBlock(i, var22, var23).getBlockHardness(...) > -1.0F && ... ) {
                  this.worldObj.getBlock(i, var22, var23).breakBlock(...);
  • 计数(单只奇点、单 tick):getEntitiesWithinAABB 256 次(=128 轮 ×2,全部同参)、motion 赋值 128×16=2048 次(幂等)、getBlock 至少 384 次;其中 256 次实体查询与 2048 次赋值在语义上可压缩到 1 次(查询)与 1 次(赋值)
  • 上游状态:[x] 未修(upstream 同形,无 upstream 仓库可链)

触发与复现

  • 触发条件:奇点投出后落地(inGround=true)即每 tick 执行;默认配置可达、无需权限。
  • 静态推演:单只奇点 → 每 tick 256 次 AABB 查询;20 tps ⇒ 5,120 次/秒。场上 5 只奇点 ⇒ 25,600 次/秒,而这 5 只的查询范围与结果几乎完全重合(半径 10、同一世界位置附近),等于把同一份邻居表扫了 640 遍/秒。把 AABB 与两次查询提到 128 循环之外(每 tick 各 1 次)、把 16 次幂等赋值压成 1 次,得到逐 tick 完全相同的运动学结果(位置在这些循环里不被修改,赋值幂等)。
  • 说明:唯一需要保留在循环内的是 :144-149 的随机点破坏(每轮确实取新随机坐标),它与实体查询无关,可独立保留。
  • 包内相关性:模块 thaumicadditions/sides client,server/默认配置可达 是

落点

  • 主仓:TDLM/thaumicadditions → src/main/java/com/pengu/thaumcraft/additions/entity/EntitySingularity.java:70-96(AABB 与两次实体查询)、:112-141(16 次幂等赋值)

范式

  • 命中范式:U1 每拍全扫(每 tick 对固定半径做重复实体扫描)
  • 邻位:F4 帧内不变量重复求值(同参查询无局部承接)、T3 同拍雷群(增殖后多只奇点同拍同范围扫描)

引证

  1. 生效源码 permalink —
    for(p = 0; p < 128; ++p) {
    AxisAlignedBB ent = AxisAlignedBB.getBoundingBox(this.posX - (double)this.radius, this.posY - (double)this.radius, this.posZ - (double)this.radius, this.posX + (double)this.radius, this.posY + (double)this.radius, this.posZ + (double)this.radius);
    l = this.worldObj.getEntitiesWithinAABB(Entity.class, ent);
    l2 = this.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, ent);
    double ry;
    double ey;
    double ez;
    double motionX;
    double motionY;
    double motionZ;
    for(i = 0; i < l.size(); ++i) {
    ry = ((Entity)l.get(i)).posX;
    ey = ((Entity)l.get(i)).posY;
    ez = ((Entity)l.get(i)).posZ;
    motionX = ((Entity)l.get(i)).motionX;
    motionY = ((Entity)l.get(i)).motionY;
    motionZ = ((Entity)l.get(i)).motionZ;
    motionX = this.posX - ry;
    motionY = this.posY - ey + (double)(this.ticksExisted < 400?(float)this.ticksExisted / 400.0F * 4.0F:4.0F);
    motionZ = this.posZ - ez;
    ((Entity)l.get(i)).motionX = motionX / 10.0D;
    ((Entity)l.get(i)).motionY = motionY / 10.0D;
    ((Entity)l.get(i)).motionZ = motionZ / 10.0D;
    if(l.get(i) instanceof EntityAlumentum && !(l.get(i) instanceof EntitySingularity)) {
    ry = ((Entity)l.get(i)).posX;
    ey = ((Entity)l.get(i)).posY;
    ez = ((Entity)l.get(i)).posZ;
    this.worldObj.createExplosion(this, ry, ey, ez, 4.0F, this.worldObj.getGameRules().getGameRuleBooleanValue("mobGriefing"));
    ((EntityAlumentum)l.get(i)).setDead();
    EntitySingularity var24 = new EntitySingularity(this.worldObj);
    var24.posX = ry;
    var24.posY = ey;
    var24.posZ = ez;
    var24.inGround = true;
    this.worldObj.spawnEntityInWorld(var24);
    }
    }
    for(i = 0; i < l2.size(); ++i) {
    ry = ((EntityLivingBase)l2.get(i)).posX;
    ey = ((EntityLivingBase)l2.get(i)).posY;
    ez = ((EntityLivingBase)l2.get(i)).posZ;
    motionX = ((EntityLivingBase)l2.get(i)).motionX;
    motionY = ((EntityLivingBase)l2.get(i)).motionY;
    motionZ = ((EntityLivingBase)l2.get(i)).motionZ;
    motionX = this.posX - ry;
    motionY = this.posY - ey + (double)(this.ticksExisted < 400?(float)this.ticksExisted / 400.0F * 4.0F:4.0F);
    motionZ = this.posZ - ez;
    ((EntityLivingBase)l2.get(i)).motionX = motionX / 10.0D;
    ((EntityLivingBase)l2.get(i)).motionY = motionY / 10.0D;
    ((EntityLivingBase)l2.get(i)).motionZ = motionZ / 10.0D;
    }
    int var22;
    for(i = 0; i < 16; ++i) {
    for(var22 = 0; var22 < l2.size(); ++var22) {
    double rz = ((EntityLivingBase)l2.get(var22)).posX;
    double ey1 = ((EntityLivingBase)l2.get(var22)).posY;
    double ez1 = ((EntityLivingBase)l2.get(var22)).posZ;
    double motionX1 = ((EntityLivingBase)l2.get(var22)).motionX;
    double motionY1 = ((EntityLivingBase)l2.get(var22)).motionY;
    double motionZ1 = ((EntityLivingBase)l2.get(var22)).motionZ;
    motionX1 = this.posX - rz;
    motionY1 = this.posY - ey1 + (double)(this.ticksExisted < 400?(float)this.ticksExisted / 400.0F * 4.0F:4.0F);
    motionZ1 = this.posZ - ez1;
    ((EntityLivingBase)l2.get(var22)).motionX = motionX1 / 10.0D;
    ((EntityLivingBase)l2.get(var22)).motionY = motionY1 / 10.0D;
    ((EntityLivingBase)l2.get(var22)).motionZ = motionZ1 / 10.0D;
    }
    }
    i = (int)this.posX;
    var22 = (int)this.posY;
    int var23 = (int)this.posZ;
    i = (int)this.posX + this.worldObj.rand.nextInt(this.radius) - this.worldObj.rand.nextInt(this.radius);
    var22 = (int)this.posY + this.worldObj.rand.nextInt(this.radius) - this.worldObj.rand.nextInt(this.radius);
    var23 = (int)this.posZ + this.worldObj.rand.nextInt(this.radius) - this.worldObj.rand.nextInt(this.radius);
    if(this.worldObj.getBlock(i, var22, var23) != Blocks.air && this.worldObj.getBlock(i, var22, var23).getBlockHardness(this.worldObj, i, var22, var23) > -1.0F && this.worldObj.getGameRules().getGameRuleBooleanValue("mobGriefing")) {
    this.worldObj.getBlock(i, var22, var23).breakBlock(this.worldObj, i, var22, var23, this.worldObj.getBlock(i, var22, var23), 1);
    this.worldObj.playSoundEffect((double)i, (double)var22, (double)var23, this.worldObj.getBlock(i, var22, var23).stepSound.getBreakSound(), this.worldObj.getBlock(i, var22, var23).stepSound.getVolume(), this.worldObj.getBlock(i, var22, var23).stepSound.getPitch());
    if(!this.worldObj.isRemote) {
    this.worldObj.spawnEntityInWorld(new EntityItem(this.worldObj, (double)i, (double)var22, (double)var23, new ItemStack(this.worldObj.getBlock(i, var22, var23).getItemDropped(0, this.rand, 0), 1, this.worldObj.getBlockMetadata(i, var22, var23))));
    }
    this.worldObj.setBlockToAir(i, var22, var23);

来源

  • 线索类型:内部代码审查(无外部报障)
  • 可解析指针:src/main/java/com/pengu/thaumcraft/additions/entity/EntitySingularity.java:70-149
**调研编号**: ## 现象 奇点实体落地后(`inGround`)的 `onUpdate` 每 tick 起一个 **128 轮**的循环,但循环体里**没有任何东西随轮次变化**:`AxisAlignedBB` 每轮都用 `posX/posY/posZ ± this.radius` 重新构造(`radius` 是字段、在循环外的 `:183/:191` 才被改),随后的两次 `getEntitiesWithinAABB(Entity.class / EntityLivingBase.class, ent)` 因此**每轮返回完全相同的结果**;循环内也没有 `break`/`return`。除此之外每轮还做:对同一批 `l2` 的 16 次幂等赋值(`motion=(target-pos)/10`,不改坐标)、以及 3 次 `getBlock` +(命中时)整段 `breakBlock` 破坏逻辑。 ## 危害类与优先级 - `PERF`(每拍全扫 + 同参数重复求值 + 无局部承接)→ 标签 `type/perf` - `p2` 每 tick 每只奇点固定 256 次 AABB 实体查询、128×16 次幂等向量赋值、~128×3 次取块;而奇点会自我增殖(`:95-107` 吃 Alumentum 会 `spawnEntityInWorld(new EntitySingularity)`),乘数还会继续变大 ## 证据(生效源码) - 载体:`TDLM/thaumicadditions` + 分支 `origin/main` + pin `cded7cd3ee59`(source 类仓,无 patch 层,代码可点开) - 位置:`src/main/java/com/pengu/thaumcraft/additions/entity/EntitySingularity.java:70-149`;`radius` 定义 `:22`、仅在循环外 `:183/:191` 修改 - 关键原文: ```java // EntitySingularity.java:70-96 每 tick 起 128 轮循环,每轮重建同一个 AABB 并跑同一对实体查询 for(p = 0; p < 128; ++p) { AxisAlignedBB ent = AxisAlignedBB.getBoundingBox(this.posX - (double)this.radius, this.posY - (double)this.radius, this.posZ - (double)this.radius, this.posX + (double)this.radius, this.posY + (double)this.radius, this.posZ + (double)this.radius); l = this.worldObj.getEntitiesWithinAABB(Entity.class, ent); l2 = this.worldObj.getEntitiesWithinAABB(EntityLivingBase.class, ent); // EntitySingularity.java:112-128 每轮再对同一批 l2 做 16 次幂等赋值(motion = (target - pos)/10,位置不改) for(i = 0; i < 16; ++i) { for(var22 = 0; var22 < l2.size(); ++var22) { ... motionX1 = this.posX - rz; ((EntityLivingBase)l2.get(var22)).motionX = motionX1 / 10.0D; ... // EntitySingularity.java:144-149 每轮再取 6 次随机数与方块 i = (int)this.posX + this.worldObj.rand.nextInt(this.radius) - this.worldObj.rand.nextInt(this.radius); var22 = (int)this.posY + this.worldObj.rand.nextInt(this.radius) - this.worldObj.rand.nextInt(this.radius); var23 = (int)this.posZ + this.worldObj.rand.nextInt(this.radius) - this.worldObj.rand.nextInt(this.radius); if(this.worldObj.getBlock(i, var22, var23) != Blocks.air && this.worldObj.getBlock(i, var22, var23).getBlockHardness(...) > -1.0F && ... ) { this.worldObj.getBlock(i, var22, var23).breakBlock(...); ``` - 计数(单只奇点、单 tick):`getEntitiesWithinAABB` **256 次**(=128 轮 ×2,全部同参)、`motion` 赋值 **128×16=2048 次**(幂等)、`getBlock` 至少 **384 次**;其中 256 次实体查询与 2048 次赋值在语义上可压缩到 1 次(查询)与 1 次(赋值) - 上游状态:[x] 未修(upstream 同形,无 upstream 仓库可链) ## 触发与复现 - 触发条件:奇点投出后落地(`inGround=true`)即每 tick 执行;默认配置可达、无需权限。 - [x] 静态推演:单只奇点 → 每 tick 256 次 AABB 查询;20 tps ⇒ **5,120 次/秒**。场上 5 只奇点 ⇒ 25,600 次/秒,而这 5 只的查询范围与结果几乎完全重合(半径 10、同一世界位置附近),等于把同一份邻居表扫了 640 遍/秒。把 AABB 与两次查询提到 128 循环之外(每 tick 各 1 次)、把 16 次幂等赋值压成 1 次,得到**逐 tick 完全相同的运动学结果**(位置在这些循环里不被修改,赋值幂等)。 - 说明:唯一需要保留在循环内的是 `:144-149` 的随机点破坏(每轮确实取新随机坐标),它与实体查询无关,可独立保留。 - 包内相关性:模块 `thaumicadditions`/sides `client,server`/默认配置可达 是 ## 落点 - 主仓:`TDLM/thaumicadditions` → `src/main/java/com/pengu/thaumcraft/additions/entity/EntitySingularity.java:70-96`(AABB 与两次实体查询)、`:112-141`(16 次幂等赋值) ## 范式 - 命中范式:`U1 每拍全扫`(每 tick 对固定半径做重复实体扫描) - 邻位:`F4 帧内不变量重复求值`(同参查询无局部承接)、`T3 同拍雷群`(增殖后多只奇点同拍同范围扫描) ## 引证 1. 生效源码 permalink — https://git.potatolab.uk/TDLM/thaumicadditions/src/commit/cded7cd3ee59/src/main/java/com/pengu/thaumcraft/additions/entity/EntitySingularity.java#L70-L155 ## 来源 - 线索类型:内部代码审查(无外部报障) - 可解析指针:`src/main/java/com/pengu/thaumcraft/additions/entity/EntitySingularity.java:70-149`
layue13 added the
priority
p2
type
perf
origin/scanarea/performance
labels 2026-11-04 09:03:55 +00:00
Author
Owner

红队复核(2026-11-04,补充乘数路径):自我增殖会真的把 256 次/tick 复制出去。

// EntitySingularity.java:99-105  吃掉 Alumentum 时生成的「新奇点」直接进 inGround
                     ((EntityAlumentum)l.get(i)).setDead();
                     EntitySingularity var24 = new EntitySingularity(this.worldObj);
                     var24.posX = ry; var24.posY = ey; var24.posZ = ez;
                     var24.inGround = true;                       // ← :104
                     this.worldObj.spawnEntityInWorld(var24);

// EntitySingularity.java:40  而 inGround 正是那 128 轮循环的入口
      if(this.inGround) {

⇒ 新奇点在下一 tick 就进入同一个 128 轮循环,所以「场上 N 只奇点 ⇒ N×256 次 AABB 查询/tick」的乘数成立;且这个增殖是自持的(每吃一个 Alumentum 多一只)。

**红队复核(2026-11-04,补充乘数路径):自我增殖会真的把 256 次/tick 复制出去。** ```java // EntitySingularity.java:99-105 吃掉 Alumentum 时生成的「新奇点」直接进 inGround ((EntityAlumentum)l.get(i)).setDead(); EntitySingularity var24 = new EntitySingularity(this.worldObj); var24.posX = ry; var24.posY = ey; var24.posZ = ez; var24.inGround = true; // ← :104 this.worldObj.spawnEntityInWorld(var24); // EntitySingularity.java:40 而 inGround 正是那 128 轮循环的入口 if(this.inGround) { ``` ⇒ 新奇点在**下一 tick 就进入同一个 128 轮循环**,所以「场上 N 只奇点 ⇒ N×256 次 AABB 查询/tick」的乘数成立;且这个增殖是自持的(每吃一个 Alumentum 多一只)。
Author
Owner

关闭,不改:EntitySingularity(:70-149)提升查询会改变玩法:明矾转化也在 128 轮循环内,提升会改变爆炸与生成奇点的次数;该实体稀少且属终局内容。

关闭,不改:`EntitySingularity`(:70-149)提升查询会改变玩法:明矾转化也在 128 轮循环内,提升会改变爆炸与生成奇点的次数;该实体稀少且属终局内容。
layue13 reopened this issue 2026-11-07 06:10:05 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: TDLM/thaumicadditions#21