【问题标题】:Writer/Reader buffer mechanism for large size - high freq data c++大尺寸的写入器/读取器缓冲机制-高频数据c ++
【发布时间】:2013-11-19 09:21:41
【问题描述】:

我需要一个单一的写入器和多个读取器(最多 5 个)机制,写入器会推送大小几乎为 1 MB 的数据,并且每秒连续推送 15 个包,这将在 c++ 中编写。我正在尝试做的是一个线程继续写入数据,而 5 个读取器将同时根据数据的时间戳进行一些搜索操作。我必须将每个数据包保留 60 分钟,然后才能将它们从容器中取出。

由于数据可以像 15 MB * 60 秒 * 60 分钟 = 54000MB/h 一样增长,我需要将近 50 GB 的空间来保存数据并使操作对写入器和读取器都足够快。但问题是我们无法将这种大小的数据保存在缓存或 RAM 中,因此它必须位于 SSD 等硬盘驱动器中(HDD 对于这种操作来说太慢了)

到目前为止,我一直在想的是,将循环缓冲区(因为我可以计算最大大小)直接实现到 SSD 上,到目前为止我找不到合适的例子,我没有'不知道是否有可能,或者实现某种映射机制,即一个循环数组将在 RAM 中可用,它只保留数据的时间戳和内存的物理地址,以搜索可用的数据硬盘。所以我猜至少搜索操作会更快。

由于任何类型的锁、互斥锁或信号量都会减慢操作速度(尤其是写入至关重要,我们不能因为任何读取操作而丢失数据)我不想使用它们。我知道有一些共享锁可用,但我再次认为它们有一些缺点。有没有办法/想法来实现这种无锁、无等待和线程安全的系统?任何数据结构(容器)、模式、示例代码/项目或其他类型的建议都将受到高度赞赏,谢谢...

编辑:除了更大的 RAM 之外,还有其他想法吗?

【问题讨论】:

  • 如果上限真的是 50gb,我很确定您可以轻松获得一台可以处理它的机器。 128gb 对于重型工作站来说是相当标准的。
  • 如果您的内存有限,那么只需将最后几秒钟保留在 RAM 中,然后在使用 memory-mapped files 时将其他几秒钟刷新到磁盘。
  • @JoachimPileborg 在 RAM 中仅保留几秒钟没有任何帮助。因为阅读器线程可以请求任何时间范围内的数据,而不仅仅是最后一个。

标签: c++ multithreading thread-safety circular-buffer readerwriterlock


【解决方案1】:

这可以在商用 PC 上完成(并且可以扩展到服务器而无需更改代码)。

锁不是问题。如果只有一个编写者和很少的消费者在大数据上执行耗时的任务,那么您将很少遇到锁定并且几乎为零的锁定争用,因此这不是问题。
从简单的自旋锁(如果您真的非常渴望低延迟)或最好是pthread_mutex(无论如何,大多数时候都回退为自旋锁)中的任何东西都可以。没什么特别的。

请注意,您不要获取锁,从套接字接收一兆字节的数据,将其写入磁盘,然后释放锁。这不是它的工作原理。
您收到一兆字节的数据并将其写入您独占拥有的区域,然后获取锁,更改指针(并因此转移所有权),然后释放锁.锁保护元数据,而不是千兆字节大小的缓冲区中的每一个字节。长时间运行的任务,短锁定时间,争用 = 零。

至于实际数据,写出 15MiB/s 绝对没有挑战,普通硬盘可以做到 5-6 倍,SSD 轻松做到 10 到 20 倍。这也不是您甚至需要自己做的事情。这是你可以留给操作系统来管理的东西。

我会在磁盘和内存映射上创建一个 54.1GB1 文件(假设它是一个 64 位系统,在谈论多 GB 内存服务器时这是一个合理的假设,这没问题) .操作系统负责其余的工作。您只需将数据写入用作循环缓冲区的映射区域2
最近编写的内容将或多或少保证3 驻留在 RAM 中,因此消费者可以访问它而不会出错。较旧的数据可能在 RAM 中,也可能不在 RAM 中,具体取决于您的服务器是否有足够的可用物理 RAM。

仍然可以访问较旧的数据,但速度可能会稍慢(如果没有足够的物理 RAM 来保持整个数据集的驻留)。但是,它不会影响读取最近写入数据的生产者或消费者(除非机器的规格非常低,以至于它甚至无法在 RAM 中保存 2-3 个 1MiB 块,但是你有一个不同的问题! )。

你对如何处理数据不是很具体,除了会有 5 个消费者,所以我不会太深入这部分。您可能必须实现一个作业调度系统,或者您可以将每个传入的块分成 5 个更小的块,或者其他什么——这取决于您想要做什么。

在任何情况下,您需要考虑的是映射环形缓冲区中“有效”的数据区域(作为指针,或者更好地作为映射中的偏移量)和“未使用”的区域。
生产者是映射的所有者,它“允许”消费者在元数据中给定的范围内访问数据(一对开始/结束的偏移量)。只有生产者可以更改此元数据。 访问此元数据的任何人(包括生产者)都需要获取锁。

甚至可能使用原子操作来做到这一点,但看到你很少锁定,我什至不会打扰。使用锁很容易,而且您不会犯任何微妙的错误。

由于生产者知道消费者只会查看明确定义的范围内的数据,因此它可以在不锁定的情况下写入边界之外的区域(已知为“emtpy”的区域)。之后只需要lock就可以改变边界。

作为 54.1Gib > 54Gib,您可以在映射中写入一百个备用 1MiB 块。这可能比需要的要多得多(应该做 2 或 3 个),但多做一些也没有什么坏处。当您写入新块时(并将有效范围增加 1),还要调整“有效范围”的另一端。这样,线程将不再被允许访问旧块,但仍在该块中工作的线程可以完成其工作(数据仍然存在)。
如果对正确性有严格要求,如果处理一个块需要很长时间(在这种情况下超过 1 1/2 分钟),这可能会产生竞争条件。如果你想绝对确定,你需要另一个锁,在最坏的情况下可能会阻塞生产者。这是您绝对不想要的,但是在最坏的情况下阻止生产者是唯一在每个人为的情况下都是 100% 正确的事情,除非假设的计算机具有无限的内存。
鉴于这种情况,我认为这种理论上的竞赛是“允许的”事情。如果处理一个块真的需要这么长时间,并且有这么多数据稳定地进入,那么您手头就会遇到一个更严重的问题,所以实际上,这不是问题。

如果你的老板在未来某个时候决定你应该保留超过 1 小时的积压,你可以放大文件并重新映射,并且当“空”区域在旧缓冲区的末尾下一个时大小,只需扩展“已知”文件大小,并在生产者中调整您的 max_size 值。消费者线程甚至不需要知道。您当然可以创建另一个文件、复制数据、交换并同时阻止消费者,但我认为这是一个较差的解决方案。尺寸增加可能没有必要立即可见,但另一方面,非常希望这是一个“不可见”的过程。
如果您将更多 RAM 放入计算机中,您的程序将“神奇地”使用它,而无需您进行任何更改。操作系统只会在 RAM 中保留更多页面。如果你再添加几个消费者,它仍然可以正常工作。


1 故意比你需要的更大,让有几个“额外”的 1MiB 块。

2 最好可以madvise 操作系统(如果您使用具有破坏性 DONT_NEED 提示的系统,例如 Linux)在覆盖之前不再对内容感兴趣地区。但如果你不这样做,它会以任何一种方式工作,只是效率稍低,因为操作系统可能会执行读-修改-写操作,而写操作就足够了。

3当然没有真正保证,但无论如何都会如此。

【讨论】:

  • 非常感谢您的详细回复。但由于解释非常抽象,我在某些方面对实现它感到困惑。您是否知道任何类型的示例实现、文档、文章、代码示例等...使其更具体?
  • 找到一个示例实现可能会很困难,因为这是一个相当具体的问题。但是您可能会找到实施中所需的大多数组件的示例。内存映射在某种程度上是特定于系统的,而且在某种程度上是低级的,因此至少需要知道您打算在什么操作系统或什么类型的操作系统(例如 Linux、BSD、Windows 服务器)上运行它。当我说例如“服务器”==“POSIX”时,我有点假设。 “pthread_mutex”,但你当然可以在每个操作系统上做同样的事情。
  • 一般来说,这种方法非常简单。您使用比 1 小时回溯稍大的环形缓冲区,并使用互斥锁保护对环形缓冲区元数据(开始/结束字段)的访问。唯一“特殊”的另一件事是将其放入内存映射区域,这需要使用操作系统特定的调用(例如用于 POSIX 和类似 POSIX 的系统的 mmap,或用于 Windows 系统的 CreateFileMapping/MapViewOfFile)。这些都是有据可查的,但如上所述,首先需要知道您的目标是什么系统。
  • 操作系统肯定是 Windows,很可能是 7 x64。我对内存映射不是很熟悉,所以研究一下我猜这将是一个很好的起点。根据 Timestamp 属性(与数据本身一起提供)搜索数据包怎么样。在项目上实现这种搜索机制是否容易?有什么建议吗?
  • 对于Windows,你需要知道这些用于内存映射的函数:123以及这个4和“备注”中提到的那些如果您没有 C++11 线程函数,则用于旋转互斥锁。
【解决方案2】:

54GB/小时 = 15MB/秒。这些天好的 SSD 可以写入 300+ MB/s。如果您在 RAM 中保留 1 小时,然后偶尔将较旧的数据刷新到磁盘,您应该能够处理超过 15MB/s 的 10 倍(前提是您的搜索算法足够快以跟上)。

关于线程之间的快速锁定机制,我建议研究 RCU - Read-Copy Update。 Linux 内核目前正在使用它来实现非常高效的锁定。

【讨论】:

  • 实际上系统应该适用于不同的尺寸和频率。我的意思是其他实现的数据/秒可以更大,所以它必须是通用的。但是你说得对,SSD 已经足够满足你的需求了。但我不确定 SSD 是否可以像 RAM 一样使用,我可以实现任何类型的容器结构并直接在其上进行所有读写操作。而且由于 1 小时的数据将超过 50 GB,因此不可能将其保存在 RAM 中,对吧?
  • 你可以找到supports 128GB RAM的主板。它并不便宜,但您的计算机总价不应超过 2000 美元
  • @mvp:实际上重量级的数据库服务器现在经常以 1TB/2TB 的速度被淘汰(当然不便宜,但工时也不便宜)。
【解决方案3】:

您有最低硬件要求吗?如今,54GB 内存是完全可能的(如今许多主板可能需要 4x16GB,这甚至还不是服务器硬件)。所以如果你想要一个 SSD,你也可以像你建议的那样需要大量的 RAM 并有一个内存循环缓冲区。

另外,如果数据中有足够的冗余,使用一些廉价的压缩算法(那些在 CPU 上很容易使用的压缩算法,即某种“0级”压缩)可能是可行的。 IE。您不存储原始数据,而是存储一些由读者解压缩的压缩格式(可能还有一些索引)。

【讨论】:

  • 没有这么严格的硬件要求,但我不能只热衷于 RAM。因为系统需要进一步修改,以适应不同的和可能更大的数据和更高的频率。因此,使用 HDD 或至少使用 SSD 就足够了。
  • @user2955554:64GB 的 RAM 是普通 PC 可以处理的。在我工作的公司,我们有一些具有 512GB 或 1TB RAM 的重量级数据库服务器。有一个简单的实现和更多的 RAM 可能会更便宜(长期)。
【解决方案4】:

周围有很多好的建议。我想补充一下循环缓冲区的实现,你可以看看Boost Circular Buffer

【讨论】:

    猜你喜欢
    • 2015-04-10
    • 2013-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-09-15
    • 2023-03-11
    • 1970-01-01
    相关资源
    最近更新 更多