界面FPS流畅度(卡顿值)评估公式
界面fps能达到60 且每帧间隔在16.7ms左右,用户流畅度体感为最高。出现掉帧(帧速减少), 抖帧(帧速不均匀),则认为会影响到用户流畅度体验。目前
百度
开发同学提供了可以设置一段时间内帧长的Demo,经过产品同学多轮、多人的试验结论:
连续5帧帧长为30ms时,能够在网页浏览的界面滑动时明确感觉到抖动。
单帧帧长为70ms时,能够在网页浏览的界面滑动时明确感觉到界面顿住。
腾讯
PerfDog Jank计算方法:
1. 同时满足以下两条件,则认为是一次卡顿Jank.
a) 当前帧耗时>前三帧平均耗时2倍。
b) 当前帧耗时>两帧电影帧耗时(1000ms/24*2=84ms)。
2. 同时满足两条件,则认为是一次严重卡顿BigJank.
a) 当前帧耗时>前三帧平均耗时2倍。
b) 当前帧耗时>三帧电影帧耗时(1000ms/24*3=125ms)。
1) BigJank:1s内严重卡顿次数
2) Jank(/10min):平均每10分钟卡顿次数。
3) BigJank(/10min):平均每10分钟严重卡顿次数
目前市面上有些做卡顿评估的方案,但是计算方法取值太过离散,不具备连续性。我们需要一个能评测当前帧率的公式,计算出每帧的评分。来对界面流畅度有个完善的评估。
几个重要指标
1.平均帧率 (平均帧率不能检测出短时间内突然卡顿后又恢复的情况)
2.帧率方差 (方差只能检测帧速的波动,如果帧速一直是30,方差也会为0)
3.掉帧卡顿比率 (若某帧时长超过了30ms 则认为丢失了一帧)
4.降帧次数 (平均每秒相邻两个FPS点下降大于8帧的次数)
5. Ft 当前帧耗时
FS = FrameSpeed = 16.7/Ft : 手机满帧率是一秒60帧,平均每帧16.7ms, 假定满帧速=16.7/16.7 = 1 , 当前帧耗时越多,速度越慢,最低趋近于0. 故帧速取值区间为(0, 1]
由于用户感知卡顿与帧速和帧速波动值有关, 所以计算流畅值的公式应该与用户当前帧速,与帧速的导数dx相关, 由于导数的取值范围为(-∞, +∞), 因此我们使用上下两帧帧速差值的平方来评估速率波动,整体公式为: 其中 为当前帧帧速FS。
如果当前帧为连续的第四帧满帧速(遇到连续的4帧,帧速为1)则把当前帧流畅值恢复为100
式中 a=4 b=1 t = 0.85 t=>(0,1]
公式说明:
其中a 为当前帧速的权重值, b为当前帧与上一帧速波动的权重值
由于:
使用试验数据
[16.7, 16.7, 32, 32, 32, 32, 32, 16.7, 16.7, 70, 170, 170, 170, 170, 16.7, 16.7, 16.7, 16.7, 120, 16.7]
当a=1 b=1 时计算出流畅值为
[100, 100, 64, 58, 55, 53, 52, 65, 82, 24, 16, 12, 11, 10, 14, 57, 78, 89, 14, 20]
当a=2 b=1 时流畅值为
[100, 100, 60, 54, 53, 52, 52, 76, 92, 27, 14, 11, 10, 10, 42, 80, 93, 97, 17, 47]
当a=3 b=1 时流畅值为
[100, 100, 58, 53, 52, 52, 52, 82, 95, 27, 13, 10, 10, 9, 57, 89, 97, 99, 16, 60]
[100.0, 100.0, 61.3, 61.3, 60.2, 58.1, 57.9, 87.1, 91.5, 32.1, 16.7, 18.0, 15.5, 14.4, 66.9, 79.2, 88.9, 95.6, 17.6, 91.8]
import os, sys, math
import queue, random
import matplotlib.pyplot as plt
import numpy as np
class SmoothAlgrithm:
frameFullTime = 1000/60
speedQueue = [1.0, 1.0, 1.0]
smoothQueue = [1.0, 1.0, 1.0]
smoothLayout = {}
totalSmooth = 0
duration = 0
calDuration = 0
frameCount = 0
originSmoothArr = []
curSmoothArr = []
def __init__(self):
self.smoothLayout = {'A':0, 'B':0, 'C':0, 'D':0, 'E':0}
def getFrameSmooth(self, frameTime):
self.duration += frameTime
if (frameTime < self.frameFullTime):
frameTime = self.frameFullTime
self.calDuration += frameTime
speed = self.frameFullTime / frameTime
avgSpeed = sum(self.speedQueue) / len(self.speedQueue)
avgSmooth = sum(self.smoothQueue) / len(self.smoothQueue)
if (avgSpeed == 1.0 and speed == 1.0):
score = 1
smooth = 100
else:
score = (4*math.pow(speed, 0.90) - pow((speed - avgSpeed), 2) + avgSmooth) / 5
smooth = (score*100)
self.speedQueue.insert(0, speed)
del self.speedQueue[-1]
self.smoothQueue.insert(0, score)
del self.smoothQueue[-1]
if (smooth > 80):
self.smoothLayout['A'] = self.smoothLayout['A'] + 1
elif (smooth > 60):
self.smoothLayout['B'] = self.smoothLayout['B'] + 1
elif (smooth > 40):
self.smoothLayout['C'] = self.smoothLayout['C'] + 1
elif (smooth > 20):
self.smoothLayout['D'] = self.smoothLayout['D'] + 1
else:
self.smoothLayout['E'] = self.smoothLayout['E'] + 1
self.frameCount += 1
self.originSmoothArr.append(int(smooth))
self.totalSmooth += smooth / speed
self.curSmoothArr.append(int(smooth / speed))
return smooth
def setFrameArr(self, arr):
smooths = []
for ft in arr:
smooth = int(self.getFrameSmooth(ft)*10)/10
smooths.append(smooth)
print(smooths)
def calAvgSmooth(self):
frameNumber = self.duration / self.frameFullTime
print('Origin smooth:', int(sum(self.originSmoothArr)/self.frameCount))
print('cur smooth:', int(sum(self.curSmoothArr)/frameNumber))
calFrames = self.calDuration / self.frameFullTime
print('cal smooth:', int(sum(self.curSmoothArr)/calFrames))
def calFPS(self):
print('cur fps:', int(self.frameCount*1000/self.duration))
maxFrameTime = 200
def randomFrametime():
rdnum = random.randint(0, 100)
if (rdnum < 90): #70%的满帧
return 16;
rdnum = random.randint(0, 350) #20% 16.7-35
if (rdnum > 167):
return rdnum / 10.0
rdnum = random.randint(350, maxFrameTime*10) / 10.0 #10 分布在35-200
return rdnum
def randPrduceSmooth():
smoothAlg = SmoothAlgrithm()
ftSmoothTuple = [(0,0, []) for x in range(0, maxFrameTime+1)]
for i in range (0, maxFrameTime*1000):
ft = randomFrametime()
smooth = smoothAlg.getFrameSmooth(ft)
idx = int(ft)
ftSmoothTuple[idx][-1].append(smooth)
for idx in range(16, maxFrameTime+1):
ftSmooth = ftSmoothTuple[idx]
count = len(ftSmooth[-1]) or 1
avgFtSmooth = sum(ftSmooth[-1]) / count
avgFtSmooth = int(avgFtSmooth*10)/10.0
ftSmoothTuple[idx] = (avgFtSmooth, count, ftSmooth[-1])
print((idx, count, avgFtSmooth))
x = np.arange(16,maxFrameTime)
y = [ftSmoothTuple[idx][0] for idx in range(16,maxFrameTime)]
plt.plot(x,y,"ob")
plt.ylabel('smooth')
plt.xlabel('frametime')
plt.show()
smoothAlg = SmoothAlgrithm()
#arr = [2, 11.0, 13.4, 16.7, 32, 32, 32, 32, 32, 16.7, 16.7, 70, 170, 170, 170, 170, 16.7, 16.7, 16.7, 16.7, 120, 16.7]
arr = [1 for x in range(0, 60)]
smoothAlg.setFrameArr(arr)
print("framecount: ", len(arr), "framenumber:", int(sum(arr)/smoothAlg.frameFullTime), " duration:", sum(arr))
print("\n orignarr:", smoothAlg.originSmoothArr)
print("\n curarr:", smoothAlg.curSmoothArr)
smoothAlg.calFPS()
smoothAlg.calAvgSmooth()