计算每秒字节数(平滑的方式)

时间:2013-09-04 17:17:21

标签: javascript design-patterns file-upload

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

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

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

3 个答案:

答案 0 :(得分:9)

你的第一个想法并不错,它被称为moving average,并且你可以定期调用你的更新函数,你只需要保持一个恒定长度的队列(FIFO buffer): / p>

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);

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

Discrete PT1 filter approximation

u[k]是样本k的输入(或实际值),y[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的刷新率将“闪烁”变为“摆动”。我认为没有人希望以每秒刷新率(甚至几秒钟)的速度显示上传速度。

答案 1 :(得分:2)

一个简单的低通滤波器可以确保不会产生不准确性。但是如果你想更深入地考虑测量传输速率,你需要维护单独的整数计数器来做正确的事。

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

  

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

     

totalBytes /总权重

     

(B0 + B1 + B2 + ...)/(W0 + W1 + W2 + ...)

所以只需保持单独的(int64!)字节和毫秒的总和。并且只将它们划分为渲染步骤以显示速率。请注意,如果您改为使用调和平均值(您应该对速率执行此操作 - 因为您实际上是平均秒/字节),那么这与发送字节所需的时间相同,并按照有多少字节进行加权。 / p>

  

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

因此,按时间加权的算术平均值与按字节加权的调和平均值相同。只需在一个var中保持一个运行的字节总数,在另一个var中保持时间。有一个更深层次的原因,这种简单的计数实际上是正确的。想想积分。假设没有并发性,这实际上只是传输的总字节数除以总观察时间。假设计算机实际上每毫秒需要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个用户同时执行1MB / s的1s,那么1s的速度为2MB / s。您需要有效地重建时间重叠,并减去时间的重复计算。在传输结束时显式记录(size_byte,start_ms,stop_ms)可以测量有趣的东西:

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

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

答案 2 :(得分:0)

使用衰减平均值,然后您不必保留旧值。

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

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

你不必保留任何旧值,它们都在较小和较小的比例。您必须为 factor 选择一个适合您想要的新旧值混合值以及平均值更新频率的值。

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