Algorithm 对N个数据流进行时间排序的算法

Algorithm 对N个数据流进行时间排序的算法,algorithm,sorting,asynchronous,time,stream,Algorithm,Sorting,Asynchronous,Time,Stream,我得到了N个异步的,带时间戳的数据流。每个流都有一个固定的ish速率。我想处理所有数据,但关键是我必须处理数据,以便尽可能接近数据到达的时间(这是一个实时流应用程序) 到目前为止,我的实现是创建一个固定的K个消息窗口,我使用优先级队列按时间戳排序。然后,在进入下一个窗口之前,我按顺序处理整个队列。这是可以的,但它不太理想,因为它会产生与缓冲区大小成比例的延迟,如果消息在缓冲区结束后刚刚到达,有时还会导致丢弃消息。它看起来像这样: // Priority queue keeping track o

我得到了N个异步的,带时间戳的数据流。每个流都有一个固定的ish速率。我想处理所有数据,但关键是我必须处理数据,以便尽可能接近数据到达的时间(这是一个实时流应用程序)

到目前为止,我的实现是创建一个固定的K个消息窗口,我使用优先级队列按时间戳排序。然后,在进入下一个窗口之前,我按顺序处理整个队列。这是可以的,但它不太理想,因为它会产生与缓冲区大小成比例的延迟,如果消息在缓冲区结束后刚刚到达,有时还会导致丢弃消息。它看起来像这样:

// Priority queue keeping track of the data in timestamp order.
ThreadSafeProrityQueue<Data> q;
// Fixed buffer size
int K = 10;
// The last successfully processed data timestamp
time_t lastTimestamp = -1;

// Called for each of the N data streams asyncronously
void receiveAsyncData(const Data& dat) {
   q.push(dat.timestamp, dat);
   if (q.size() > K) {
       processQueue();
   }
}

// Process all the data in the queue.
void processQueue() {
    while (!q.empty()) {
        const auto& data = q.top();
        // If the data is too old, drop it.
        if (data.timestamp < lastTimestamp) {
            LOG("Dropping message. Too old.");
            q.pop();
            continue;
        }
        // Otherwise, process it.
        processData(data);
        lastTimestamp = data.timestamp;
        q.pop();
    }
}

看看如果我按照收到数据的顺序处理数据,B总是会被删除吗?这就是我想要避免的。现在在我的算法中,B将每10帧被删除一次,我将以10帧的延迟处理数据,直到过去。

总是有延迟,延迟将由您愿意等待最慢的“固定速率”流的时间决定

建议:

  • 保留缓冲区
  • 保留一个bool标志数组,意思是:“如果位置ix为真,缓冲区中至少有一个来自流ix的样本”
  • 将all标志设置为true后立即进行排序/处理
  • 不是完全证明(每个缓冲区都会被排序,但从一个缓冲区到另一个缓冲区,您可能会有时间戳反转),但可能足够好吗

    利用“满足”标志的计数来触发处理(在步骤3)可用于减小延迟,但存在更多缓冲区间时间戳反转的风险。在极端情况下,只接受一个满足标志的处理意味着“一收到帧就推它,时间戳排序就糟透了”。
    我提到这一点是为了支持我的感觉,即延迟/时间戳反转平衡是您的问题固有的-除了绝对相等的帧速率之外,将有一个完美的解决方案,其中一个方面不会被牺牲


    由于“解决方案”是一种平衡行为,因此任何解决方案都需要收集/使用额外的信息来帮助决策(例如,“标志数组”)。如果我的建议对你的案例来说听起来很愚蠢(很可能,你选择分享的细节并不多),那么开始思考哪些指标与你的“体验质量”目标水平相关并使用额外的数据结构来帮助收集/处理/使用这些指标。

    总有一个滞后,这个滞后将由您愿意等待最慢的“固定利率”流的时间决定

    建议:

  • 保留缓冲区
  • 保留一个bool标志数组,意思是:“如果位置ix为真,缓冲区中至少有一个来自流ix的样本”
  • 将all标志设置为true后立即进行排序/处理
  • 不是完全证明(每个缓冲区都会被排序,但从一个缓冲区到另一个缓冲区,您可能会有时间戳反转),但可能足够好吗

    利用“满足”标志的计数来触发处理(在步骤3)可用于减小延迟,但存在更多缓冲区间时间戳反转的风险。在极端情况下,只接受一个满足标志的处理意味着“一收到帧就推它,时间戳排序就糟透了”。
    我提到这一点是为了支持我的感觉,即延迟/时间戳反转平衡是您的问题固有的-除了绝对相等的帧速率之外,将有一个完美的解决方案,其中一个方面不会被牺牲


    由于“解决方案”是一种平衡行为,因此任何解决方案都需要收集/使用额外的信息来帮助决策(例如,“标志数组”)。如果我的建议对您的案例来说听起来很愚蠢(很可能是,您选择分享的细节并不太多),那么请开始思考哪些指标与您的目标“体验质量”水平相关,并使用其他数据结构来帮助收集/处理/使用这些指标。

    我建议采用生产者/消费者结构。让每个流将数据放入队列,并让一个单独的线程读取队列。即:

    // your asynchronous update:
    void receiveAsyncData(const Data& dat) {
       q.push(dat.timestamp, dat);
    }
    
    // separate thread that processes the queue
    void processQueue()
    {
        while (!stopRequested)
        {
            data = q.pop();
            if (data.timestamp >= lastTimestamp)
            {
                processData(data);
                lastTimestamp = data.timestamp;
            }
        }
    }
    
    q.push(AddMs(dat.timestamp, 500), dat);
    
    这可以防止在处理批处理时在当前实现中看到的“滞后”

    processQueue
    函数正在一个单独的持久线程中运行
    stopRequested
    是程序在想要关闭时设置的标志——强制线程退出。有些人会为此使用
    volatile
    标志。我更喜欢使用手动重置事件之类的东西

    为了实现这一点,您需要一个允许并发更新的优先级队列实现,或者需要用同步锁包装队列。特别是,您要确保
    q.pop()
    在队列为空时等待下一项。或者当队列为空时,您从不调用
    q.pop()
    。我不知道您的
    ThreadSafePriorityQueue
    的具体内容,所以我不能确切地说您将如何编写它

    时间戳检查仍然是必要的,因为可以在较早的项目之前处理较晚的项目。例如:

  • 从数据流1接收到事件,但在将线程添加到队列之前,线程已被调出
  • 事件从数据流2接收,并添加到队列中
  • 数据流2中的事件由
    processQueue
    函数从队列中删除
  • 上面步骤1中的线程获取另一个时间片,并将该项添加到队列中
  • 这并不罕见,只是很少发生。时间差通常为微秒级

    如果你经常把更新弄得乱七八糟,那么你可能会引入人为的延迟。例如,在您更新的问题中,您在5点之前显示了顺序错误的消息
    q.push(AddMs(dat.timestamp, 500), dat);
    
    while (true)
    {
        if (q.peek().timestamp <= currentTime)
        {
            data = q.pop();
            if (data.timestamp >= lastTimestamp)
            {
                processData(data);
                lastTimestamp = data.timestamp;
            }
        }
    }