你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

服务器端性能查询

要提供稳定的帧速率和良好的用户体验,服务器上必须具有良好的渲染性能。 请务必谨慎监视服务器上的性能特性,并在必要时进行优化。 可通过专用 API 函数查询性能数据。

对渲染性能影响最大的是模型输入数据。 可按配置模型转换中描述的方式调整输入数据。

客户端应用程序性能也可能成为瓶颈。 若要深入分析客户端性能,建议进行performance trace

客户端/服务器时间线

在详细讨论各种延迟值之前,有必要在时间线上查看客户端和服务器之间的同步点:

Pipeline timeline

上图显示了操作方式:

  • 客户端以恒定的 60 Hz 帧速率(每 16.6 毫秒)开始姿态估计
  • 服务器随后根据姿态开始渲染
  • 服务器发回已编码的视频图像
  • 客户端对图像进行解码,根据它执行一些 CPU 和 GPU 工作,然后显示图像

帧统计信息查询

帧统计信息提供了关于最后一帧的一些大致信息,例如延迟。 FrameStatistics 结构中提供的数据是在客户端测量的,因此 API 是同步调用:

void QueryFrameData(RenderingSession session)
{
    FrameStatistics frameStatistics;
    if (session.GraphicsBinding.GetLastFrameStatistics(out frameStatistics) == Result.Success)
    {
        // do something with the result
    }
}
void QueryFrameData(ApiHandle<RenderingSession> session)
{
    FrameStatistics frameStatistics;
    if (session->GetGraphicsBinding()->GetLastFrameStatistics(&frameStatistics) == Result::Success)
    {
        // do something with the result
    }
}

检索到的 FrameStatistics 对象包含以下成员:

成员 说明
LatencyPoseToReceive 从客户端设备上的相机姿态估计一直到该姿态的服务器帧可完全用于客户端应用程序之间的延迟。 该值包括网络往返、服务器渲染、视频解码和抖动补偿时间。 请查看上图中的时间间隔 1
LatencyReceiveToPresent 从收到的远程帧可用一直到客户端应用在 CPU 上调用 PresentFrame 之间的延迟。 请查看上图中的时间间隔 2
LatencyPresentToDisplay 从在 CPU 上显示一个帧一直到显示屏亮起之间的延迟。 该值包括客户端 GPU 时间、操作系统执行的任何帧缓冲、硬件重新投影和与设备相关的显示屏扫描输出时间。 请查看上图中的时间间隔 3
TimeSinceLastPresent 在 CPU 上对 PresentFrame 进行后续调用之间的时间。 如果值大于显示持续时间(例如,在 60 Hz 客户端设备上为 16.6 毫秒),则表示存在客户端应用程序未及时完成其 CPU 工作负载所造成的问题。
VideoFramesReceived 最后一秒内从服务器接收的帧数。
VideoFrameReusedCount 最近一秒在设备上多次使用的已接收帧数。 如果值不是零,则表示由于网络抖动或服务器渲染时间过长,必须重新使用和重新投影帧。
VideoFramesSkipped 在最后一秒被解码但由于新帧到达而未显示的已接收帧数。 如果值不是零,则表示网络抖动导致多个帧延迟,然后以突发形式一起到达客户端设备。
VideoFramesDiscarded 与 VideoFramesSkipped 非常相似,但是被丢弃的原因是帧到达时间太晚,以至于它甚至不能与任何挂起的姿态相关联。 如果发生这种丢弃情况,则会出现严重的网络争用。
VideoFrameMinDelta 在最后一秒内到达的两个连续帧之间的最短时间。 与 VideoFrameMaxDelta 一起使用时,此范围可指示由网络或视频编解码器引起的抖动。
VideoFrameMaxDelta 在最后一秒内到达的两个连续帧之间的最长时间。 与 VideoFrameMinDelta 一起使用时,此范围可指示由网络或视频编解码器引起的抖动。

所有延迟值的总和通常远大于 60 Hz 下的可用帧时间。 这很正常,因为多个帧并行传输,而新帧请求按所需的帧速率启动,如图中所示。 然而,如果延迟太大,就会影响后期阶段的重新投影的质量,并可能降低整体体验。

VideoFramesReceivedVideoFrameReusedCountVideoFramesDiscarded 可用于测量网络和服务器的性能。 低 VideoFramesReceived 值和高 VideoFrameReusedCount 值的组合可能指示网络拥塞或服务器性能不佳。 VideoFramesDiscarded 值高也表示网络拥塞。

最后,TimeSinceLastPresentVideoFrameMinDeltaVideoFrameMaxDelta 给出了传入视频帧和本地当前调用之间有差异这一概念。 差异大表示帧速率不稳定。

上面的值均未明确指示纯网络延迟(图中的红色箭头),因为需要从往返值 LatencyPoseToReceive 中减去服务器忙于渲染的确切时间。 总延迟中的服务器端信息不可用于客户端。 不过,下一段落将说明如何通过来自服务器的额外输入估算此值并通过 NetworkLatency 值进行公开。

性能评估查询

可通过性能评估查询更深入地了解服务器上的 CPU 和 GPU 工作负载。 由于已从服务器请求数据,因此查询性能快照时遵循通常的异步模式:

async void QueryPerformanceAssessment(RenderingSession session)
{
    try
    {
        PerformanceAssessment result = await session.Connection.QueryServerPerformanceAssessmentAsync();
        // do something with result...
    }
    catch (RRException ex)
    {
    }
}
void QueryPerformanceAssessment(ApiHandle<RenderingSession> session)
{
    session->Connection()->QueryServerPerformanceAssessmentAsync([](Status status, PerformanceAssessment result) {
        if (status == Status::OK)
        {
            // do something with result...
        }
    });
}

FrameStatistics 对象相反,PerformanceAssessment 对象包含服务器端信息:

成员 解释
TimeCPU 每帧平均服务器 CPU 时间(毫秒)
TimeGPU 每帧平均服务器 GPU 时间(毫秒)
UtilizationCPU 服务器 CPU 总使用率(百分比)
UtilizationGPU 服务器 GPU 总使用率(百分比)
MemoryCPU 服务器主内存总使用率(百分比)
MemoryGPU 专用视频内存的总使用率(占服务器 GPU 的百分比)
NetworkLatency 近似平均往返网络延迟(毫秒)。 在上图中,此值对应于红色箭头的总和。 该值是通过 FrameStatisticsLatencyPoseToReceive 值减去实际服务器渲染时间来计算的。 尽管该近似值不准确,但它提供了网络延迟的某个指示,与客户端上计算的延迟值不相关。
PolygonsRendered 在一帧中渲染的三角形的数目。 此数字还包括稍后在渲染过程中剔除的三角形。 这意味着,即使相机位置不同,该数字也变化不大,但是性能可能会大幅度变化,具体取决于三角形剔除率。
PointsRendered 在一帧中呈现的点云中的点数。 此处适用的 PolygonsRendered 与上述剔除标准相同。

为了帮助你评估这些值,每个部分都有质量分类,例如“很好”、“好”、“一般”或“差”。 此评估指标可粗略指示服务器运行状况,但不得将它视为绝对指标。 例如,假设看到 GPU 时间的得分为“一般”。 它被认为是中等性能,因为它接近整个帧时间预算的限制。 然而,对你来说这个值可能还不错,因为你正在渲染一个复杂的模型。

统计信息调试输出

ServiceStatistics 是一个 C# 类,它包装了帧统计信息和性能评估查询,并提供了方便的功能来以聚合值或预构建字符串的形式返回统计信息。 要在客户端应用程序中显示服务器端统计信息,最简单方法是使用以下代码。

ServiceStatistics _stats = null;

void OnConnect()
{
    _stats = new ServiceStatistics();
}

void OnDisconnect()
{
    _stats = null;
}

void Update()
{
    if (_stats != null)
    {
        // update once a frame to retrieve new information and build average values
        _stats.Update(Service.CurrentActiveSession);

        // retrieve a string with relevant stats information
        InfoLabel.text = _stats.GetStatsString();
    }
}

上述代码将用以下文本填充文本标签:

ArrServiceStats string output

GetStatsString API 格式化所有值的字符串,但你也可从 ServiceStatistics 实例中以编程方式查询单独的每个值。

还存在一些成员的变体,它们将在一段时间内聚合这些值。 请查看具有 *Avg*Max*Total 后缀的成员。 成员 FramesUsedForAverage 指示已用于此聚合的帧数。

API 文档

后续步骤