7

我正在寻找一种解决方案来计算重复调用的每秒传输字节数function(如下)。由于它的不准确性,我不想简单地将传输的字节除以经过的总时间:这导致运行几分钟后无法显示快速的速度变化。

预设(大约每 50 毫秒调用一次):

function uploadProgress(loaded, total){
    var bps = ?;
    $('#elem').html(bps+' bytes per second');
};
  • 如何获得(仅)最后几秒的平均每秒字节数,n这是一个好主意吗?
  • 还有哪些其他方法可以计算不闪烁但精确的 bps 值?
4

3 回答 3

10

你的第一个想法不错,它被称为移动平均,并且如果你定期调用你的更新函数,你只需要保持一个恒定长度的队列(一个FIFO 缓冲区):

var WINDOW_SIZE = 10;
var queue = [];

function updateQueue(newValue) {
    // fifo with a fixed length
    queue.push(newValue);
    if (queue.length > WINDOW_SIZE)
        queue.shift();
}

function getAverageValue() {

    // if the queue has less than 10 items, decide if you want to calculate
    // the average anyway, or return an invalid value to indicate "insufficient data"

    if (queue.length < WINDOW_SIZE) {

        // you probably don't want to throw if the queue is empty, 
        // but at least consider returning an 'invalid' value in order to
        // display something like "calculating..."

        return null;
    }

    // calculate the average value
    var sum = 0;
    for (var i = 0; i < queue.length; i++) {
        sum += queue[i];
    }
    return sum / queue.length;
}

// calculate the speed and call `updateQueue` every second or so
var updateTimer = setInterval(..., 1000);

避免计算速度突然变化的更简单方法是使用低通滤波器。PT1 滤波器的简单离散近似为:

离散 PT1 滤波器逼近

哪里u[k]是 sample 处的输入(或实际值),是 sampleky[k]的输出(或过滤值k,并且T是时间常数(越大T意味着y跟随u速度越慢)。

那将被翻译成类似:

var speed = null;
var TIME_CONSTANT = 5;

function updateSpeed(newValue) {    
    if (speed === null) {
        speed = newValue;
    } else {
        speed += (newValue - speed) / TIME_CONSTANT;
    }
}

function getFilteredValue() {
    return speed;
}

两种解决方案都会给出相似的结果(至少出于您的目的),而后一种解决方案似乎更简单(并且需要更少的内存)。

另外,我不会那么快地更新值。过滤只会以 50ms 的刷新率将“闪烁”变成“摆动”。我认为没有人希望以每秒超过一次(甚至几秒钟)的刷新率显示上传速度。

于 2013-09-05T13:07:42.250 回答
2

一个简单的低通滤波器就可以确保不产生误差。但是,如果您对测量传输率有更深入的思考,您就会开始维护单独的整数计数器以正确地完成它。

如果您希望它是一个精确的计数,请注意有一个可用的简化。首先,在处理速率时,它们的算术平均值不适用于字节/秒(秒/字节更正确 - 这会导致调和平均值)。另一个问题是它们应该被加权。正因为如此,简单地保持 int64 运行总字节数与观察时间的关系实际上是正确的——听起来很愚蠢。通常,您对每个 w 加权 1/n。看看当你按时间衡量时发生的一个简洁的简化:

(w0*b0/t0 + w1*b1/t1 + w2*b2/t2 + ...)/(w0+w1+w2+...)

总字节数/总重量

(b0+b1+b2+...)/(w0+w1+w2+...)

因此,只需将字节和毫秒的总数分开(int64!)。并且仅将它们划分为渲染步骤以可视化速率。请注意,如果您改为使用调和平均值(您应该对速率执行此操作 - 因为您实际上是在平均秒/字节),那么这与发送一个字节所需的时间相同,由有多少字节加权。

1 / (( w0*t0/b0 + w1*t1/b0 + ... )/(w0+w1+w2+...)) = totalBytes/totalTime

因此,按时间加权的算术平均值与按字节加权的调和平均值相同。只需在一个变量中保持运行总字节数,在另一个变量中保持时间。有一个更深层次的原因是,这个简单的计数实际上是正确的。想想积分。假设没有并发,这实际上只是传输的总字节数除以总观察时间。假设计算机实际上每毫秒走 1 步,并且只发送整个字节 - 并且您观察整个时间间隔没有间隙。没有近似值。

请注意,如果您将 (msec, byte/msec) 的积分视为 (x,y) 的单位,则曲线下的面积是观察期间发送的字节数(确切地说)。无论观察结果如何切割,您都会得到相同的答案。(即:经常报告 2 倍)。

因此,通过简单地报告 (size_byte, start_ms,stop_ms),您只需将 (stop_ms-start_ms) 累积到时间中,并在每次观察时累积 size_byte 。如果您想将这些速率划分为分钟桶中的图表,那么只需维护每分钟(观察)的 (byte,ms) 对。

请注意,这些是个人转账的费率。个人传输可能会遇到 1MB/s(用户观点)。这些是您向最终用户保证的费率。

对于简单的情况,您可以将其留在这里。但是正确地计算,可以得到更多有趣的东西。

从服务器的角度来看,负载很重要。假设有两个用户同时体验 1MB/s。对于该统计数据,您需要减去重复计算的时间。如果 2 个用户在 1s 内同时进行 1MB/s,那么 1s 为 2MB/s。您需要有效地重建时间重叠,并减去重复计算的时间段。在传输结束时显式记录 (size_byte,start_ms,stop_ms) 允许您测量有趣的事情:

  • 在任何给定时间的未完成传输的数量(队列长度分布 - 即:“我会用完内存吗?”)
  • 作为传输次数函数的吞吐量(队列长度的吞吐量 - 即:“当我们的广告在电视上显示时网站是否崩溃?”)
  • 利用率 - 即:“我们是否为我们的云提供商支付了过高的费用?”

在这种情况下,所有累积的计数器都是精确的整数运算。减去重复计算的时间会突然让您进入更复杂的算法(当有效和实时计算时)。

于 2016-01-03T07:54:10.347 回答
0

使用衰减平均值,那么您就不必保留旧值。

更新:基本上它是这样的公式:

average = new_value * factor + average_old * (100 - factor);

您不必保留任何旧值,它们都以越来越小的比例存在。您必须选择适合您想要的新旧值的混合的因子值,以及更新平均值的频率。

我相信这就是计算 Unix“平均负载”的方式。

于 2013-09-04T17:43:34.713 回答