页面加载中
博客快捷键
按住 Shift 键查看可用快捷键
ShiftK
开启/关闭快捷键功能
ShiftA
打开/关闭中控台
ShiftD
深色/浅色显示模式
ShiftS
站内搜索
ShiftR
随机访问
ShiftH
返回首页
ShiftL
友链页面
ShiftP
关于本站
ShiftI
原版/本站右键菜单
松开 Shift 键或点击外部区域关闭
互动
最近评论
暂无评论
标签
寻找感兴趣的领域
暂无标签
    0
    文章
    0
    标签
    8
    分类
    10
    评论
    128
    功能
    深色模式
    标签
    JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
    互动
    最近评论
    暂无评论
    标签
    寻找感兴趣的领域
    暂无标签
      0
      文章
      0
      标签
      8
      分类
      10
      评论
      128
      功能
      深色模式
      标签
      JavaScript12TypeScript8React15Next.js6Vue10Node.js7CSS5前端20
      随便逛逛
      博客分类
      文章标签
      复制地址
      深色模式
      AnHeYuAnHeYu
      Search⌘K
      博客
        暂无其他文档

        Iris 网络复制系统技术分析 - 第十五部分:调试与性能分析

        December 31, 202537 分钟 阅读197 次阅读

        🔧 Iris 网络复制系统技术分析 - 第十五部分:调试与性能分析

        📖 本章导读:想象你是一位汽车修理工,面对一辆"跑不动"的汽车。你需要先诊断问题(是油没了?轮胎漏气?还是发动机故障?),然后才能修复它。Iris 的调试与性能分析系统就是你的"汽车诊断仪"——它能告诉你网络复制系统哪里出了问题、哪里可以优化。本章将教你如何成为一名优秀的"网络复制修理工"!🔧


        🎯 15.1 为什么需要调试与性能分析?

        💡 15.1.1 网络游戏的"隐形杀手"

        PLAINTEXT
        🎮 日常类比:餐厅服务质量
        
        想象你经营一家餐厅,顾客抱怨"上菜太慢":
        
        ❓ 问题可能出在哪里?
        ┌────────────────────────────────────────────────────────────────────────┐
        │                                                                        │
        │   🍳 厨房问题?          🏃 服务员问题?         📝 点单系统问题?        │
        │   ├── 厨师太少           ├── 人手不足            ├── 系统卡顿            │
        │   ├── 食材准备慢         ├── 路线不合理          ├── 订单丢失            │
        │   └── 设备故障           └── 托盘太小            └── 优先级混乱          │
        │                                                                        │
        │   🎯 没有诊断工具,你只能瞎猜!                                          │
        │   ✅ 有了监控系统,问题一目了然!                                        │
        │                                                                        │
        └────────────────────────────────────────────────────────────────────────┘
        
        网络复制也是如此:
        - 🍳 厨房 = 服务器(生成数据)- 🏃 服务员 = 网络(传输数据)- 📝 点单系统 = Iris(管理复制)- 👨‍🍳 顾客 = 客户端(接收数据)

        🔍 15.1.2 常见的网络复制问题

        问题类型

        现象

        可能原因

        😱 对象不同步

        服务器和客户端位置不一致

        未注册、被过滤、属性未标记

        😱 延迟过高

        动作要等很久才显示

        优先级低、带宽不足

        😱 带宽爆炸

        网络流量突然暴增

        过滤失效、增量压缩未生效

        😱 CPU 过高

        服务器帧率下降

        对象过多、序列化效率低

        📂 15.1.3 关键源文件索引(UE5.5 源码真实路径✅)

        文件

        位置(Engine/Source/Runtime/Experimental/Iris)

        你该去哪里下断点/找逻辑

        Iris/Core/IrisDebugging.h

        Core/Public/Iris/Core/IrisDebugging.h

        UE::Net::IrisDebugHelper:断点条件、调试器可调用导出函数

        Iris/Core/IrisDebugging.cpp

        Core/Private/Iris/Core/IrisDebugging.cpp

        Net.Iris.DebugName/DebugRPCName/... 注册处

        Iris/Core/IrisLog.h/.cpp

        Core/Public/Iris/Core/IrisLog.h / Core/Private/Iris/Core/IrisLog.cpp

        LogIris / LogIrisFiltering 等日志类别

        ReplicationSystem/ObjectReplicationBridgeDebugging.cpp

        Core/Private/Iris/ReplicationSystem/ObjectReplicationBridgeDebugging.cpp

        Net.Iris.PrintReplicatedObjects 等控制台命令注册处

        ReplicationSystem/ObjectReplicationBridge.cpp

        Core/Private/Iris/ReplicationSystem/ObjectReplicationBridge.cpp

        net.Iris.UseVerboseIrisCsvStats、ConnectionId= 参数解析

        Iris/Core/IrisProfiler.h/.cpp

        Core/Public/Iris/Core/IrisProfiler.h / Core/Private/Iris/Core/IrisProfiler.cpp

        IRIS_PROFILER_SCOPE + 客户端详细 CSV 开关

        Iris/Core/IrisCsv.h

        Core/Public/Iris/Core/IrisCsv.h

        IRIS_CSV_PROFILER_SCOPE(CSV “记账”宏)

        Stats/NetStats.h/.cpp

        Core/Private/Iris/Stats/NetStats.h / Core/Private/Iris/Stats/NetStats.cpp

        FNetSendStats:全局发送统计 + CSV 上报

        Stats/NetStatsContext.h

        Core/Private/Iris/Stats/NetStatsContext.h

        每类/每对象的计时/bit 统计怎么“攒账”

        Iris/Core/IrisMemoryTracker.h/.cpp

        Core/Public/Iris/Core/IrisMemoryTracker.h / Core/Private/Iris/Core/IrisMemoryTracker.cpp

        Iris 的 LLM Tag(内存分科目)


        📝 15.2 日志系统:网络复制的"黑匣子"

        💡 15.2.1 什么是日志系统?

        PLAINTEXT
        🎮 日常类比:飞机黑匣子
        
        飞机上有两个黑匣子:
        📦 飞行数据记录器(FDR):记录飞机的各种参数
        🎙️ 驾驶舱语音记录器(CVR):记录飞行员的对话
        
        当飞机出事时,黑匣子能告诉调查员发生了什么。
        
        Iris 日志系统 = 网络复制的"黑匣子"
        - 📝 记录复制事件- ⚠️ 记录错误警告- 🔍 事后分析排查
        
        🎯 出问题时,日志是你最好的朋友!

        📊 15.2.2 Iris 日志类别(源码里到底有哪些?)

        先说结论:Iris 的日志类别不是“想当然的一串”,而是分散在不同模块头文件里声明。你看到的某个 UE_LOG(LogXXX, ...),大概率能在对应模块的头文件/.cpp 顶部找到 DECLARE_... / DEFINE_...。

        ✅ 核心总开关:LogIris / LogIrisFiltering

        • 定义位置:

          • Engine/Source/Runtime/Experimental/Iris/Core/Public/Iris/Core/IrisLog.h

          • Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/Core/IrisLog.cpp

        CPP
        // IrisLog.h(节选)IRISCORE_API DECLARE_LOG_CATEGORY_EXTERN(LogIris, Log, All);
        IRISCORE_API DECLARE_LOG_CATEGORY_EXTERN(LogIrisFiltering, Log, All);
        
        // IrisLog.cpp(节选)DEFINE_LOG_CATEGORY(LogIris);
        DEFINE_LOG_CATEGORY(LogIrisFiltering);

        ✅ 复制桥接层专用:LogIrisBridge

        • 你什么时候会看到它:打印“桥接层做了什么”(注册对象、销毁、RPC flush、各种调试打印)。

        • 定义位置:

          • Engine/Source/Runtime/Experimental/Iris/Core/Public/Iris/ReplicationSystem/ReplicationBridge.h

          • Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ReplicationBridge.cpp

        CPP
        // ReplicationBridge.h(节选)IRISCORE_API DECLARE_LOG_CATEGORY_EXTERN(LogIrisBridge, Log, All);
        
        // ReplicationBridge.cpp(节选)DEFINE_LOG_CATEGORY(LogIrisBridge)

        ✅ 过滤配置专用:LogIrisFilterConfig

        • 你什么时候会看到它:当你调整类到过滤器的映射、动态过滤器配置时(很适合排查“为什么对象突然不相关/不复制了”)。

        • 定义位置:

          • Engine/Source/Runtime/Experimental/Iris/Core/Public/Iris/ReplicationSystem/ObjectReplicationBridge.h

          • Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ObjectReplicationBridge.cpp

        CPP
        // ObjectReplicationBridge.h(节选)DECLARE_LOG_CATEGORY_EXTERN(LogIrisFilterConfig, Log, All);
        
        // ObjectReplicationBridge.cpp(节选)DEFINE_LOG_CATEGORY(LogIrisFilterConfig)

        🧠 小白友好理解:把 LogIris 当成“总经理广播”,把 LogIrisFiltering 当成“安检口广播”,把 LogIrisBridge 当成“调度室广播”,把 LogIrisFilterConfig 当成“规则配置变更广播”。你要查哪类问题,就把哪路广播开大声一点。🔊

        ⚙️ 15.2.3 日志级别配置

        PLAINTEXT
        🎮 日常类比:消息通知设置
        
        想象你手机的通知设置:
        📵 静音:什么都不通知
        🔔 重要:只通知重要消息
        🔔🔔 全部:所有消息都通知

        级别

        名称

        说明

        适用场景

        Fatal

        💀 致命

        直接崩溃/断言级别

        必须修复

        Error

        ❌ 错误

        功能异常但不崩溃

        需要关注

        Warning

        ⚠️ 警告

        潜在问题

        建议检查

        Display

        📢 提示

        比 Log 更“显眼”的提示

        看关键流程

        Log

        📝 日志

        一般信息

        开发调试

        Verbose

        📖 详细

        详细信息

        深度调试

        VeryVerbose

        🧾 超详细

        很吵、很细

        只在定位疑难杂症时开

        配置方法:

        INI
        ; 方式 1:在 DefaultEngine.ini 中配置[Core.Log]LogIris=Verbose                    ; Iris 主日志设为详细LogIrisFiltering=Warning           ; 过滤系统只显示警告以上
        CPP
        // 方式 2:通过控制台命令// Log LogIris Verbose// Log LogIrisFiltering Warning

        🔍 15.2.4 日志分析实战案例

        PLAINTEXT
        📋 问题:玩家报告"敌人突然消失"
        
        🔍 第一步:开启过滤日志
        控制台输入:Log LogIrisFiltering Verbose
        
        🔍 第二步:复现问题,查看日志
        [LogIrisFiltering] Object filtered out: BP_Enemy_C_0 by GridFilter
        [LogIrisFiltering] GridFilter: Object outside view range
        [LogIrisFiltering]   ObjectPos=(5000, 3000, 0)
        [LogIrisFiltering]   ViewPos=(0, 0, 0)
        [LogIrisFiltering]   Distance=5831, MaxDistance=5000
        
        🎯 第三步:定位问题
        原因:敌人距离玩家 5831 单位,超过了 GridFilter 的 5000 单位限制
        
        ✅ 第四步:解决方案
        • 增加 GridFilter 的裁剪距离
        • 或者为重要敌人设置更高的优先级

        🔧 15.3 调试工具:网络复制的"X光机"

        💡 15.3.1 IrisDebugging 到底是什么?(更像“调试器外挂”🧰)

        很多同学以为 IrisDebugging 是一套“打印对象信息”的通用 API,但在 UE5.5 的 Iris 里:

        • Iris/Core/IrisDebugging.h 更偏向“给调试器/断点用”的 Helper:比如“命中某对象名就断下”、以及提供 extern "C" 的函数方便你在 VS 的 Watch/Immediate Window 里直接调用。

        • 而你常见的“打印对象列表/相关性/裁剪距离”等 控制台命令,主要在 ObjectReplicationBridgeDebugging.cpp。

        PLAINTEXT
        🎮 日常类比:游戏里的“作弊码菜单”(仅开发环境)
        
        - 你想“锁血/透视”不是为了正式玩,而是为了定位 bug 更快。
        - `IrisDebugging` 也是这个定位:让你在关键时刻一脚刹车(断点),或者直接把内部状态吐出来。

        🔎 源码摘录:断点条件 + Watch/Immediate Window 可调用函数

        • 定义位置:Engine/Source/Runtime/Experimental/Iris/Core/Public/Iris/Core/IrisDebugging.h

        CPP
        // IrisDebugging.h(节选)namespace UE::Net::IrisDebugHelper
        {
            IRISCORE_API bool BreakOnObjectName(UObject* Object);
            IRISCORE_API bool BreakOnNetRefHandle(FNetRefHandle NetRefHandle);
            IRISCORE_API bool BreakOnRPCName(FName RPCName);
        
            extern "C" IRISCORE_API void DebugOutputNetObjectState(uint64 NetRefHandleId, uint32 ReplicationSystemId);
            extern "C" IRISCORE_API const TCHAR* DebugNetObjectStateToString(uint32 NetRefHandleId, uint32 ReplicationSystemId);
        }

        🧨 源码摘录:控制台变量/命令如何把“断点条件”装进引擎?

        • 定义位置:Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/Core/IrisDebugging.cpp

        CPP
        // IrisDebugging.cpp(节选)FAutoConsoleVariableRef NetIrisDebugName(
            TEXT("Net.Iris.DebugName"),
            GIrisDebugName,
            TEXT("Set a class name or object name to break on."),
            ECVF_Default);
        
        static FAutoConsoleCommand NetIrisDebugNetRefHandle(
            TEXT("Net.Iris.DebugNetRefHandle"),
            TEXT("Specify a NetRefHandle ID that we will break on (or none to turn off)."),
            FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray<FString>& Args){ /*...*/ }));

        ✅ 小白可操作理解:你把 Net.Iris.DebugName 设成 MyBoss,然后当 Iris 处理到名字包含 MyBoss 的对象时,代码会 UE_DEBUG_BREAK() —— 你就能在“案发现场”看调用栈,而不是事后猜。🕵️‍♂️

        🖥️ 15.3.2 控制台调试命令大全(按源码校准版✅)

        很多“命令速查表”会把 Iris 写得像一个全家桶,但在 UE5.5 的源码里:

        • 大量真实存在的 Iris 调试命令前缀是 Net.Iris.(注意 N 大写)。

        • 其中最实用的一批,集中在 ObjectReplicationBridgeDebugging.cpp。

        📌 命令在哪注册?

        • Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/ReplicationSystem/ObjectReplicationBridgeDebugging.cpp

        🧾 命令清单(真实存在)

        目标

        命令

        你拿它来解决什么?

        看所有注册对象

        Net.Iris.PrintReplicatedObjects

        “对象到底有没有进 Iris?”

        看对任意连接相关

        Net.Iris.PrintRelevantObjects

        “为什么某些对象没人看得见?”

        看全员永远相关

        Net.Iris.PrintAlwaysRelevantObjects

        “AlwaysRelevant 配置是否生效?”

        看指定连接相关

        Net.Iris.PrintRelevantObjectsToConnection

        “这个玩家为啥看不到那个对象?”(可带过滤信息)

        看动态过滤配置

        Net.Iris.PrintDynamicFilterClassConfig

        “类到过滤器的映射到底是什么?”

        看裁剪距离

        Net.Iris.PrintNetCullDistances

        “敌人消失/闪烁,是不是距离裁剪?”

        看 PushBased 状态

        Net.Iris.PrintPushBasedStatuses

        “属性不同步,是不是没 push-based/没标脏?”

        🧩 通用参数(源码支持)

        • RepSystemId=X:PIE 多实例时指定某个复制系统

        • ConnectionId=1 或 ConnectionId=1,5,7:只看指定连接(ObjectReplicationBridge.h 注释里给了示例)

        • WithSubObjects:把子对象也打印出来

        • SortByClass / SortByNetRefHandle:排序方式

        另外还有命令自己的参数,例如:

        • Net.Iris.PrintRelevantObjectsToConnection 支持 WithFilter(源码注释里写了)

        • Net.Iris.PrintNetCullDistances 支持 NumClasses=X(限制输出前 N 个类)

        🔎 源码摘录:命令注册长这样

        CPP
        // ObjectReplicationBridgeDebugging.cpp(节选)FAutoConsoleCommand ObjectBridgePrintReplicatedObjects(
            TEXT("Net.Iris.PrintReplicatedObjects"),
            TEXT("Prints the list of replicated objects registered for replication in Iris"),
            FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray<FString>& Args)
        {
            // ... FindReplicationSystemFromArg(Args) 支持 RepSystemId=
            // ... FindPrintTraitsFromArgs(Args) 支持 WithSubObjects/SortBy...
            // ... ObjectBridge->PrintReplicatedObjects(...)
        }));

        ✅ 小白实战建议:当你怀疑“对象没复制”,第一条就跑 Net.Iris.PrintReplicatedObjects;如果它根本不在列表里,后面所有优化都是白忙活。

        📋 15.3.3 对象信息输出示例

        PLAINTEXT
        ═══════════════════════════════════════════════════════════════════Object Replication Info: BP_PlayerCharacter_C_0═══════════════════════════════════════════════════════════════════
        
        📌 Basic Info:├── NetRefHandle: 0x00010042├── Class: BP_PlayerCharacter_C├── Owner: PlayerController_0└── NetRole: ROLE_Authority
        
        📊 Replication State:├── IsReplicating: Yes├── IsDirty: No├── LastReplicatedFrame: 12345└── PollPeriod: 0 (every frame)
        
        🎯 Filter Status:├── GridFilter: Passed ✅├── ConnectionFilter: Passed ✅└── GroupFilter: Passed ✅
        
        ⭐ Priority Info:├── StaticPriority: 1.0├── Prioritizer: SphereNetObjectPrioritizer└── CurrentPriority: 0.85
        
        🔗 Connections Relevancy:├── Connection 1: Relevant ✅ (Owner)├── Connection 2: Relevant ✅ (Distance: 500)└── Connection 3: Not Relevant ❌ (Distance: 8000)

        🎨 15.3.4 可视化调试工具

        PLAINTEXT
        🎮 日常类比:汽车仪表盘
        
        开车时,仪表盘能让你一眼看到:
        ⛽ 油量、🌡️ 水温、🏎️ 速度、📍 导航
        
        Iris 可视化调试 = 网络复制的"仪表盘"
        
        > ⚠️ 源码事实:在 UE5.5 的 `Runtime/Experimental/Iris` 目录里,我没有找到 `Net.Iris.Draw...` 之类的“场景内画线/画圈”命令注册;**Iris 更偏向用 `Net.Iris.Print...` 系列打印 + Insights 时间线来“可视化”**。下面这张图是帮你快速建立直觉的示意图,真正排查还是以 15.3.2/15.4 为准。
        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────────────┐
        │                    🎨 可视化调试效果图解                                 │
        ├────────────────────────────────────────────────────────────────────────┤
        │                                                                        │
        │                        游戏场景俯视图                                   │
        │   ┌──────────────────────────────────────────────────────────────┐    │
        │   │                                                              │    │
        │   │     🟢 = 正常复制的对象                                       │    │
        │   │     🔴 = 脏对象(需要同步)                                    │    │
        │   │     ⚪ = 被过滤的对象                                         │    │
        │   │     🔵 = 玩家视图范围                                         │    │
        │   │                                                              │    │
        │   │              ⚪ ⚪                                            │    │
        │   │           ⚪      ⚪                                          │    │
        │   │        ⚪    ┌────────┐    ⚪                                 │    │
        │   │             │  🔵    │                                       │    │
        │   │      ⚪     │ 🟢 🔴  │     ⚪                                 │    │
        │   │             │  🟢 🟢 │                                       │    │
        │   │        ⚪   │   👤   │   ⚪                                   │    │
        │   │             └────────┘                                       │    │
        │   │           ⚪      ⚪                                          │    │
        │   └──────────────────────────────────────────────────────────────┘    │
        │                                                                        │
        │   💡 通过颜色可以快速判断对象复制状态和过滤效果                          │
        │                                                                        │
        └────────────────────────────────────────────────────────────────────────┘

        📈 15.4 性能分析:找出网络复制的"瓶颈"

        💡 15.4.1 性能分析的重要性

        PLAINTEXT
        🎮 日常类比:体检报告
        
        每年体检,医生会给你一份详细的报告:
        📊 血压:120/80 ✅ 正常
        📊 血糖:5.6 ✅ 正常
        📊 胆固醇:6.2 ⚠️ 偏高
        📊 尿酸:480 ❌ 超标
        
        有了这份报告,你就知道该关注什么、改善什么。
        
        性能分析 = 给网络复制系统做"体检"

        📊 15.4.2 关键性能指标 (KPIs)

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────────────┐
        │                    📊 Iris 关键性能指标                                  │
        ├────────────────────────────────────────────────────────────────────────┤
        │                                                                        │
        │   ⏱️ 时间指标(越小越好)                                               │
        │   ├── NetUpdateTime       │ < 2ms ✅  │ 2-5ms ⚠️  │ > 5ms ❌           │
        │   ├── FilteringTime       │ < 0.5ms   │ 0.5-1ms   │ > 1ms              │
        │   ├── PrioritizationTime  │ < 0.3ms   │ 0.3-0.8ms │ > 0.8ms            │
        │   └── SerializationTime   │ < 1ms     │ 1-3ms     │ > 3ms              │
        │                                                                        │
        │   📦 数量指标                                                           │
        │   ├── ReplicatedObjects   │ < 1000 建议 │ 每连接复制的对象数            │
        │   ├── DirtyObjects        │ < 100 建议  │ 每帧脏对象数                  │
        │   └── FilteredObjects     │ 越多越好    │ 被过滤掉的对象数              │
        │                                                                        │
        │   📡 带宽指标                                                           │
        │   ├── BandwidthUsage      │ < 80% limit │ 带宽使用率                   │
        │   ├── AvgPacketSize       │ 500-1200    │ 平均包大小(字节)             │
        │   └── PacketsPerSecond    │ 30-60       │ 每秒数据包数                 │
        │                                                                        │
        └────────────────────────────────────────────────────────────────────────┘

        📊 15.4.3 IrisProfiler:不是“一个类”,而是一套“打点开关”🎯(源码版)

        在 UE5.5 的 Iris 里,“Profiler”更多是宏 + 编译开关,你在代码里会看到类似:IRIS_PROFILER_SCOPE(Xxx),它会在录制 Trace 时产生 CPU 事件。

        🔎 源码摘录:IRIS_PROFILER_SCOPE 的真身

        • 定义位置:Engine/Source/Runtime/Experimental/Iris/Core/Public/Iris/Core/IrisProfiler.h

        CPP
        // IrisProfiler.h(节选)#ifndef IRIS_PROFILER_ENABLE#	if (UE_BUILD_SHIPPING)#		define IRIS_PROFILER_ENABLE 0#	else#		define IRIS_PROFILER_ENABLE 1#	endif#endif
        
        #if IRIS_PROFILER_ENABLE#	include "ProfilingDebugging/CpuProfilerTrace.h"#	define IRIS_PROFILER_SCOPE(x) TRACE_CPUPROFILER_EVENT_SCOPE(x)#	define IRIS_PROFILER_SCOPE_TEXT(X) TRACE_CPUPROFILER_EVENT_SCOPE_TEXT(X)#else#	define IRIS_PROFILER_SCOPE(x)#	define IRIS_PROFILER_SCOPE_TEXT(X)#endif

        🧠 小白理解:这就像你给快递员贴“行程记录仪”。平时不开就不记录;一旦你开始录制 Trace,它就把“每一步花了多少时间”记下来。

        📊 Iris 的 CSV 统计:IRIS_CSV_PROFILER_SCOPE

        • 定义位置:Engine/Source/Runtime/Experimental/Iris/Core/Public/Iris/Core/IrisCsv.h

        CPP
        // IrisCsv.h(节选)CSV_DECLARE_CATEGORY_EXTERN(Iris);
        
        #if UE_NET_IRIS_CSV_STATS#	define IRIS_CSV_PROFILER_SCOPE(CsvCategory, x) \
        		CSV_SCOPED_TIMING_STAT(CsvCategory, x); \
        		IRIS_PROFILER_SCOPE(x)#else#	define IRIS_CSV_PROFILER_SCOPE(CsvCategory, x) IRIS_PROFILER_SCOPE(x)#endif

        🧩 小白理解:IRIS_PROFILER_SCOPE 更像“录像”(Insights 时间线);CSV_SCOPED_TIMING_STAT 更像“记账”(一列一列的统计表)。

        🧑‍💻 客户端细粒度 CSV:用 net.Iris.EnableDetailedClientProfiler 开关

        • 定义位置:Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/Core/IrisProfiler.cpp

        CPP
        // IrisProfiler.cpp(节选)static FAutoConsoleVariableRef CVarEnableDetailedClientProfilerRef(
            TEXT("net.Iris.EnableDetailedClientProfiler"),
            bEnableDetailedClientProfiler,
            TEXT("Generates detailed CSV Iris stats (client only)."),
            ECVF_Default);

        🔍 15.4.4 使用 Unreal Insights 分析:Iris 的“时间线录像机”⏱️(源码对得上)

        在 UE5.5 的 Iris 里,很多“时间线上的 Iris 事件”来自 IRIS_PROFILER_SCOPE(...) —— 而它在源码里最终会落到 TRACE_CPUPROFILER_EVENT_SCOPE(...)(见 15.4.3)。

        ✅ 你至少需要开 cpu(Iris 并没有单独的 iris TraceChannel)

        PLAINTEXT
        // 命令行参数(推荐:最稳)
        -trace=cpu,net
        
        // 或控制台命令(运行时开启)
        Trace.Start cpu,net
        ...复现问题...
        Trace.Stop

        🧠 小白理解:CPU Trace 就像“把每一帧的耗时都拍成录像”。Iris 在关键流程里打了 IRIS_PROFILER_SCOPE,所以你能在时间线上看到“它到底卡在哪一步”。

        📌 你在时间线上会看到什么?(示意图)

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────────────┐
        │                    📊 Unreal Insights 时间线视图(示意)                │
        ├────────────────────────────────────────────────────────────────────────┤
        │                                                                        │
        │   Frame 1234                                                           │
        │   │                                                                    │
        │   │ GameThread ═══════════════════════════════════════════════════    │
        │   │ ├─ Tick ──────────────────────────────────────────────────────    │
        │   │ │  ├─ Physics ████                                                │
        │   │ │  ├─ Animation ██████                                            │
        │   │ │  └─ Iris_* ████████████████████                                 │
        │   │ │     ├─ ...(Filtering / Poll / Quantize / Write 等)             │
        │   │ │     └─ ...                                                      │
        │   │                                                                    │
        │   │ 0ms        5ms        10ms       15ms       16.67ms (60fps)       │
        │                                                                        │
        └────────────────────────────────────────────────────────────────────────┘

        🔎 实战小技巧:怎么把“时间线峰值”对回到 Iris 的“对象规模”?

        • 时间线发现 Iris 突然变粗:先别急着猜“序列化慢”。

        • 立刻跑两条命令:

          • Net.Iris.PrintRelevantObjects RepSystemId=0

          • Net.Iris.PrintReplicatedObjects RepSystemId=0 WithSubObjects

        ✅ 思路:时间线告诉你“哪一段耗时高”,而这两条命令告诉你“是不是对象/子对象规模突然膨胀”。两者合起来,定位会快很多。

        📊 15.4.5 CSV 统计输出:Iris 真的在记哪些“账”?(源码版)

        先记住一句话:Iris 的很多“性能统计”不是靠 PrintStats 命令打印出来的,而是直接上报到 UE 的 CSV Profiler。

        🔎 源码摘录:Iris 在 NetStats.cpp 里定义了大量 CSV 分类

        • 定义位置:Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/Stats/NetStats.cpp

        CPP
        // NetStats.cpp(节选)static FAutoConsoleVariableRef CShouldIncludeSubObjectWithRoot(
            TEXT("net.Iris.Stats.ShouldIncludeSubObjectWithRoot"),
            bCVARShouldIncludeSubObjectWithRoot,
            TEXT("If enabled SubObjects will reports stats with RootObject..."));
        
        CSV_DEFINE_CATEGORY(IrisPreUpdateMS, WITH_SERVER_CODE);
        CSV_DEFINE_CATEGORY(IrisPollMS, WITH_SERVER_CODE);
        CSV_DEFINE_CATEGORY(IrisQuantizeMS, WITH_SERVER_CODE);
        CSV_DEFINE_CATEGORY(IrisWriteMS, WITH_SERVER_CODE);
        CSV_DEFINE_CATEGORY(IrisWriteKBytes, WITH_SERVER_CODE);

        🧠 小白理解:这相当于 Iris 把“体检项目”提前定好了:预处理、轮询、量化、写入、写入字节数……你录制 CSV 时就能看到这些列。

        🔎 源码摘录:Iris 会把统计值写进 CSV(例如 CSV_CUSTOM_STAT)

        CPP
        // NetStats.cpp(节选)CSV_CUSTOM_STAT(Iris, ReplicatingConnectionCount, Stats.ReplicatingConnectionCount, ECsvCustomStatOp::Set);
        CSV_CUSTOM_STAT(Iris, HugeObjectWaitingForAckTimeInSeconds, Stats.HugeObjectWaitingForAckTimeInSeconds, ECsvCustomStatOp::Set);

        🧾 一个“长得像 CSV”的示例(帮助你理解列是什么)

        CSV
        Frame,IrisPreUpdateMS,IrisPollMS,IrisQuantizeMS,IrisWriteMS,IrisWriteKBytes
        1,0.18,0.42,0.31,0.55,32.4
        2,0.20,0.47,0.28,0.62,35.1
        3,0.19,0.40,0.29,0.50,29.8

        ✅ 实战建议:如果你在 CSV 里看到 IrisPollMS 暴涨,优先怀疑“轮询了太多对象/太频繁”;如果 IrisWriteKBytes 暴涨,优先怀疑“过滤/条件没挡住,写出去的东西太多”。

        📊 15.4.6 NetStats / NetStatsContext:Iris 的“分账本”怎么记出来的?📒(源码版)

        很多人看 CSV 只看“列名”,但真正关键是:这些数是谁在什么时机攒出来的。

        ① 全局发送统计:FNetSendStats(更像“总账”)

        • 位置:Core/Private/Iris/Stats/NetStats.h

        CPP
        // NetStats.h(节选)/**
         * Send stats for Iris replication reported to the CSV profiler.
         * ... intended use is to do thread local tracking ... and then use Accumulate ...
         */class FNetSendStats
        {
        public:
        	IRISCORE_API void Accumulate(const FNetSendStats& Stats);
        	IRISCORE_API void Reset();
        	IRISCORE_API void ReportCsvStats();
        	// ... AddNumberOfReplicatedObjects / AddHugeObjectWaitingTime 等
        };

        🧠 小白理解:FNetSendStats 是“今天一共送了多少单、堵车等了多久”这种总账,通常更关心服务器侧。

        ② 每类/每对象统计:FNetStatsContext + 一组宏(更像“明细账”)

        • 位置:Core/Private/Iris/Stats/NetStatsContext.h

        CPP
        // NetStatsContext.h(节选)struct FNetTypeStatsData
        {
        	enum EStatsIndex : unsigned { PreUpdate, Poll, PollWaste, Quantize, Write, WriteWaste, WriteCreationInfo, WriteExports, Count };
        	struct FStatsValue { uint64 Time = 0; uint32 Bits = 0; uint32 Count = 0; };
        	FStatsValue Values[EStatsIndex::Count];
        };
        
        #if UE_NET_IRIS_CSV_STATS#	define UE_NET_IRIS_STATS_TIMER(TimerName, NetStatsContext) UE::Net::Private::FNetStatsTimer TimerName(NetStatsContext);#	define UE_NET_IRIS_STATS_ADD_TIME_AND_COUNT_FOR_OBJECT(Timer, StatName, ObjectIndex) /*...*/#	define UE_NET_IRIS_STATS_ADD_BITS_WRITTEN_AND_COUNT_FOR_OBJECT(NetStatsContext, BitCount, StatName, ObjectIndex) /*...*/#endif

        🧠 小白理解:这就像外卖平台的“明细账”:每个菜系(类)、每个订单(对象)分别记:备餐多久(Poll/Quantize)、打包多久(Write)、写了多少字节(Bits)。

        ③ 两个你会经常用到的“统计开关”(源码可查✅)

        • 是否输出更“啰嗦”的 per-class CSV(非 Shipping 默认开):

          • net.Iris.UseVerboseIrisCsvStats

          • 位置:Core/Private/Iris/ReplicationSystem/ObjectReplicationBridge.cpp

        CPP
        // ObjectReplicationBridge.cpp(节选)static FAutoConsoleVariableRef CVarUseVerboseCsvStats(
        	TEXT("net.Iris.UseVerboseIrisCsvStats"),
        	bUseVerboseIrisCsvStats,
        	TEXT("Whether to use verbose per-class csv stats. Default is false in Shipping, otherwise True.")
        );
        • SubObject 统计是否并到 Root 上:

          • net.Iris.Stats.ShouldIncludeSubObjectWithRoot

          • 位置:Core/Private/Iris/Stats/NetStats.cpp

        ✅ 小白建议:当你看到“每类的 Write/Poll 统计怪怪的”,先确认 SubObject 是否被并账了;否则你会把锅甩错对象类型。


        💾 15.5 内存追踪:找出内存"黑洞"

        💡 15.5.1 为什么需要内存追踪?

        PLAINTEXT
        🎮 日常类比:家庭开支记账
        
        不记账:💸 "钱都花哪去了?"
        记账后:📊 一目了然!
        
        内存追踪 = 告诉你内存都被谁占用了

        📊 15.5.2 内存使用分类:别猜“内存去哪了”,看 LLM 怎么分科目✅

        在 UE5.5 Iris 里,IrisMemoryTracker 的核心工作是:注册一组 LLM Tag(见 15.5.4 的源码)。所以对新手来说,最靠谱的“分类”就是这些 Tag:

        LLM Tag

        你可以把它想成

        常见含义

        Iris

        “Iris 总账”

        Iris 相关内存总和

        IrisState

        “状态缓存”

        各类复制状态/状态数据的占用

        IrisInitialization

        “初始化开销”

        初始化/构建协议等阶段的占用

        IrisConnection

        “连接分摊”

        每连接相关的缓存、结构等占用

        🧠 小白理解:别纠结“ProtocolData/FilterData”这种你自己起的名字;先用引擎已经分好的科目,你才知道该去哪个模块继续深挖。

        📋 15.5.3 你会在 LLM 里看到什么?(输出示意)

        PLAINTEXT
        LLM (示意)
        - NetworkingSummary
          - Iris
            - State
            - Initialization
            - Connection

        ✅ 实战建议:如果 IrisConnection 随在线人数线性上涨,很可能是“每连接缓存过大/没释放”;如果 IrisState 随场景对象数量上涨,很可能是“状态缓存/对象数失控”。

        🔍 15.5.4 源码对照:IrisMemoryTracker 其实是 LLM 标签体系

        很多文章会把“内存追踪”说成一个独立系统,但在 Iris 里它更接近:给 LLM(Low Level Memory Tracker)注册一组 Tag,这样你就能在 LLM 视图里看到 Iris 的内存占用。

        • 声明 Tag:Engine/Source/Runtime/Experimental/Iris/Core/Public/Iris/Core/IrisMemoryTracker.h

        • 定义 Tag:Engine/Source/Runtime/Experimental/Iris/Core/Private/Iris/Core/IrisMemoryTracker.cpp

        CPP
        // IrisMemoryTracker.h(节选)LLM_DECLARE_TAG_API(Iris, IRISCORE_API);
        LLM_DECLARE_TAG_API(IrisState, IRISCORE_API);
        LLM_DECLARE_TAG_API(IrisInitialization, IRISCORE_API);
        LLM_DECLARE_TAG_API(IrisConnection, IRISCORE_API);
        
        // IrisMemoryTracker.cpp(节选)LLM_DEFINE_TAG(Iris, NAME_None, NAME_None, GET_STATFNAME(STAT_IrisLLM), GET_STATFNAME(STAT_NetworkingSummaryLLM));
        LLM_DEFINE_TAG(IrisState, "State", "Iris", GET_STATFNAME(STAT_IrisStateLLM), GET_STATFNAME(STAT_NetworkingSummaryLLM));
        LLM_DEFINE_TAG(IrisInitialization, "Initialization", "Iris", GET_STATFNAME(STAT_IrisInitializationLLM), GET_STATFNAME(STAT_NetworkingSummaryLLM));
        LLM_DEFINE_TAG(IrisConnection, "Connection", "Iris", GET_STATFNAME(STAT_IrisConnectionLLM), GET_STATFNAME(STAT_NetworkingSummaryLLM));

        ✅ 小白操作思路:你把 LLM 打开,就能看到 “Iris / State / Initialization / Connection” 这些分项。它不是“猜内存去哪了”,而是“把账本分了科目”。📒


        🚨 15.6 常见问题排查指南

        💡 15.6.1 问题排查的"望闻问切"

        PLAINTEXT
        🎮 日常类比:中医看病的四诊法
        
        👀 望:观察日志、统计数据
        👂 闻:监听网络流量、事件
        💬 问:了解问题发生的场景
        ✋ 切:使用调试工具深入分析

        🔍 15.6.2 问题 1:对象未复制(按源码能落地的排查路径)

        排查步骤:

        PLAINTEXT
        Step 1: 先确认“对象到底有没有注册进 Iris”
        Net.Iris.PrintReplicatedObjects RepSystemId=0
        → 在输出里搜对象名(或类名)
        → 如果根本找不到:问题通常在“注册/桥接层/生命周期”
        
        Step 2: 再确认“对象是不是被过滤/不相关了”
        Net.Iris.PrintRelevantObjectsToConnection RepSystemId=0 ConnectionId=1 WithFilter
        → 这个命令会在输出里附带过滤信息(WithFilter)
        → 如果对象在 ReplicatedObjects 里,但不在 RelevantObjects 里:优先查过滤/裁剪距离
        
        Step 3: 快速验证“是不是距离裁剪导致消失”
        Net.Iris.PrintNetCullDistances RepSystemId=0 NumClasses=20
        → 看你关心的类,MostCommon NetCullDistance 是多少
        
        Step 4: 只要你怀疑“某个对象一处理就出错”,直接在案发点断下来
        Net.Iris.DebugName=MyBoss   (或用 Net.Iris.DebugNetRefHandle 指定句柄)
        → 命中就 UE_DEBUG_BREAK(),你能直接看调用栈
        
        Step 5: 回到基础:属性是否真的可复制
        ✅ UPROPERTY(Replicated)
        ✅ GetLifetimeReplicatedProps 里注册
        ✅(如果是 Push Model)修改后记得“标脏”(告诉系统它变了)

        🔎 源码依据:Net.Iris.PrintReplicatedObjects/Net.Iris.PrintRelevantObjectsToConnection/Net.Iris.PrintNetCullDistances/Net.Iris.DebugName 都能在 UE5.5 源码中找到注册位置(见本章 15.3.2 与 15.3.1)。

        🔍 15.6.3 问题 2:属性不同步(“对象在,但某个字段不动”)

        排查步骤:

        PLAINTEXT
        Step 1: 先确认“对象这条链路是通的”
        Net.Iris.PrintReplicatedObjects RepSystemId=0
        Net.Iris.PrintRelevantObjectsToConnection RepSystemId=0 ConnectionId=1 WithFilter
        → 对象必须“已注册 + 对该连接相关”,否则你盯着属性看是白费劲
        
        Step 2: 再看“是不是复制条件把你挡在门外了”
        常见条件(在 Gameplay 代码里配):
        - COND_OwnerOnly / COND_SkipOwner
        - COND_InitialOnly
        → 现象:服务器改了,客户端永远收不到(其实是条件没满足)
        
        Step 3: 用 Iris 自带命令确认 PushBased 状态(源码真实存在✅)
        Net.Iris.PrintPushBasedStatuses
        → 它会打印哪些类是 fully push-based,并列出“不是 push-based 的属性路径”
        → 如果你改的属性属于 push-based:必须在 Gameplay 层做“标脏”(否则 Iris 认为“没变过”)
        
        Step 4: 还不对?考虑“你看到的是量化后的值”
        现象:服务器 100.123456,客户端 100.12
        → 这不是不同步,而是量化精度导致的“正常舍入”
        → 线索通常在具体序列化器的 Quantize/Dequantize(可回看第七部分)

        🔎 源码依据:Net.Iris.PrintPushBasedStatuses 注册于 ObjectReplicationBridgeDebugging.cpp,用于快速判断“这类到底是不是 push-based”。

        🔍 15.6.4 问题 3:性能瓶颈(“录像 + 记账”双管齐下)

        排查步骤:

        PLAINTEXT
        Step 1: 先用 Unreal Insights 录“录像”(看时间线)
        命令行:-trace=cpu,net
        或控制台:Trace.Start cpu,net
        复现后:Trace.Stop
        
        Step 2: 如果想要“表格化记账”,用 CSV Profiler(Iris 会上报 Iris* 类别)
        - Iris 在源码里用 CSV_DEFINE_CATEGORY/CSV_CUSTOM_STAT 上报(见 15.4.5)- 想要更细:检查 net.Iris.UseVerboseIrisCsvStats / net.Iris.EnableDetailedClientProfiler
        
        Step 3: 用 Iris 自带的“对象维度”命令快速判断规模是否异常
        Net.Iris.PrintRelevantObjects RepSystemId=0
        Net.Iris.PrintReplicatedObjects RepSystemId=0 WithSubObjects
        
        Step 4: 对症下药(把锅甩给正确的人😉)
        - IrisPollMS 高:降低轮询压力(例如更合理的 PollPeriod / Dormancy / 过滤)- IrisWriteKBytes 高:过滤/条件/量化/增量压缩有没有真正生效- 大量 SubObject 计入 Root:尝试切换 net.Iris.Stats.ShouldIncludeSubObjectWithRoot

        📋 15.6.5 问题排查快速参考表(按 UE5.5 源码存在的命令)

        现象

        可能原因

        最快验证手段

        客户端看不到对象

        没注册进 Iris

        Net.Iris.PrintReplicatedObjects(输出里搜名字)

        对某玩家不可见

        被过滤/距离裁剪

        Net.Iris.PrintRelevantObjectsToConnection ConnectionId=X WithFilter

        “敌人消失/闪烁”

        NetCullDistance/过滤抖动

        Net.Iris.PrintNetCullDistances + 过滤日志 LogIrisFiltering

        属性不更新

        非 push-based / 未标脏

        Net.Iris.PrintPushBasedStatuses + 检查 Gameplay 层是否正确“标脏”

        性能突然爆炸

        轮询/写出量暴增

        Insights(-trace=cpu,net)+ CSV(IrisPollMS/IrisWriteKBytes)

        内存上涨

        状态缓存/连接/初始化占用

        LLM 标签(见 15.5.4)


        📋 15.7 总结与最佳实践

        🎯 15.7.1 核心概念总结

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────────────┐│                    📋 第十五部分知识点总结                               │├────────────────────────────────────────────────────────────────────────┤│                                                                        ││   📝 日志系统                                                           ││   ├── LogIris 主日志类别及子类别                                        ││   ├── 7 个日志级别(Fatal → VeryVerbose)                              ││   └── 配置文件 / 代码 / 控制台 三种配置方式                              ││                                                                        ││   🔧 调试工具                                                           ││   ├── IrisDebugHelper:断点条件/调试输出(Watch/Immediate Window 友好)  ││   ├── 控制台命令:Net.Iris.PrintReplicatedObjects / PrintRelevant...     ││   └── “可视化”主要靠:日志打印 + Unreal Insights 时间线                 ││                                                                        ││   📈 性能分析                                                           ││   ├── IrisProfiler:时间统计、计数器                                    ││   ├── Unreal Insights:详细时间线分析                                   ││   └── IrisCsv:CSV 数据导出                                            ││                                                                        ││   💾 内存追踪                                                           ││   ├── IrisMemoryTracker:注册 LLM Tag(Iris/State/Initialization/Connection)││   └── 用 LLM Tag 追“哪一科目在涨”                                       ││                                                                        ││   🚨 问题排查                                                           ││   ├── 对象未复制:注册、过滤、属性配置                                   ││   ├── 属性不同步:复制条件、Push Model、序列化器                        ││   └── 性能瓶颈:分阶段分析、Insights 深入追踪                           ││                                                                        │└────────────────────────────────────────────────────────────────────────┘

        ✅ 15.7.2 最佳实践清单

        PLAINTEXT
        ┌────────────────────────────────────────────────────────────────────────┐
        │                    ✅ 调试与性能分析最佳实践                             │
        ├────────────────────────────────────────────────────────────────────────┤
        │                                                                        │
        │   🔧 开发阶段                                                           │
        │   □ 启用开发版日志级别                                                  │
        │   □ 配置 Unreal Insights 追踪                                          │
        │   □ 启用可视化调试工具                                                  │
        │   □ 记录基准性能数据                                                    │
        │                                                                        │
        │   🧪 测试阶段                                                           │
        │   □ 测试对象创建和销毁                                                  │
        │   □ 测试属性同步                                                        │
        │   □ 测试过滤器效果                                                      │
        │   □ 进行性能回归测试                                                    │
        │                                                                        │
        │   🚀 发布阶段                                                           │
        │   □ 关闭详细日志(只保留 Warning 以上)                                  │
        │   □ 禁用可视化调试                                                      │
        │   □ 设置性能监控告警                                                    │
        │                                                                        │
        │   🔍 问题排查                                                           │
        │   □ 先看日志,再用工具                                                  │
        │   □ 从现象定位到具体模块                                                │
        │   □ 使用控制台命令快速验证                                              │
        │   □ 记录问题和解决方案                                                  │
        │                                                                        │
        └────────────────────────────────────────────────────────────────────────┘

        📊 15.7.3 调试命令速查表(按 UE5.5 源码存在的命令)

        场景

        命令

        说明

        先确认“对象有没有进 Iris”

        Net.Iris.PrintReplicatedObjects RepSystemId=0

        输出里搜对象名/类名

        看“对某连接是否相关”

        Net.Iris.PrintRelevantObjectsToConnection RepSystemId=0 ConnectionId=1 WithFilter

        附带过滤信息(WithFilter)

        看“对任意连接相关”

        Net.Iris.PrintRelevantObjects RepSystemId=0

        快速判断规模是否异常

        看“永远相关列表”

        Net.Iris.PrintAlwaysRelevantObjects RepSystemId=0

        验证 AlwaysRelevant

        看“裁剪距离分布”

        Net.Iris.PrintNetCullDistances RepSystemId=0 NumClasses=20

        排查“距离裁剪导致消失/闪烁”

        看 PushBased 状态

        Net.Iris.PrintPushBasedStatuses RepSystemId=0

        判断“是否需要标脏/哪些属性不是 push-based”

        看动态过滤映射

        Net.Iris.PrintDynamicFilterClassConfig RepSystemId=0

        类 -> 动态过滤器映射

        命中即断点

        Net.Iris.DebugName=MyBoss

        命中对象/类名就 UE_DEBUG_BREAK()

        录 Insights 时间线

        Trace.Start cpu,net / Trace.Stop

        Iris 的 IRIS_PROFILER_SCOPE 会出现在 CPU 时间线上


        本文档基于 Unreal Engine 5.5.0 Iris 源代码分析(源码目录:Engine/Source/Runtime/Experimental/Iris/)

        最后更新于 April 13, 2026
        On this page
        暂无目录