【问题标题】:Most efficient method of copying std::deque contents to byte-array将 std::deque 内容复制到字节数组的最有效方法
【发布时间】:2017-12-28 20:12:18
【问题描述】:

有没有更好的方法将std::deque 的内容复制到字节数组中?似乎 STL 中应该有一些东西可以做到这一点。

// Generate byte-array to transmit
uint8_t * i2c_message = new uint8_t[_tx.size()];
if ( !i2c_message ) {
    errno = ENOMEM;
    ::perror("ERROR: FirmataI2c::endTransmission - Failed to allocate memory!");
} else {
    size_t i = 0;

    // Load byte-array
    for ( const auto & data_byte : _tx ) {
        i2c_message[i++] = data_byte;
    }

    // Transmit data
    _marshaller.sendSysex(firmata::I2C_REQUEST, _tx.size(), i2c_message);
    _stream.flush();

    delete[] i2c_message;
}

我正在寻找空间或速度或两者的建议...

编辑:需要注意的是_marshaller.sendSysex()不能抛出。

跟进:

我认为值得回顾一下所有内容,因为 cmets 非常有启发性(除了火焰战争)。 :-P

问题的答案...

使用std::copy

大局:

与其简单地提高代码的原始性能,不如考虑为代码库增加健壮性和寿命。

我忽略了 RAII - 资源获取就是初始化。通过朝另一个方向前进并稍微降低性能,我可以在弹性方面获得巨大收益(正如@PaulMcKenzie 和@WhozCraig 所指出的)。事实上,我什至可以将我的代码与依赖项的变化隔离开来!

最终解决方案:

在这种情况下,我实际上可以访问(并且能够更改)更大的代码库 - 通常情况并非如此。我重新评估了使用std::deque 所带来的好处,并将整个底层容器换成了std::vector。从而节省容器交换的性能损失,并获得连续数据和 RAII 的好处。

*我选择了std::deque,因为在发送之前我总是需要push_front 两个字节来完成我的字节数组。然而,由于它总是两个字节,我能够用两个虚拟字节填充向量并用随机访问替换它们 - O(n) 时间。

【问题讨论】:

  • std::copy() 也许?
  • 我只见过 std::copy 用于从数组复制到容器,而不是相反。
  • 首先,我会使用std::vector<uint8_t>。其次,我会使用迭代器构造函数来构建相同的。这消除了大部分代码,包括给你reliable RAII。向量的data() 成员,或第一个元素的地址,将授予您指向您似乎正在寻找的连续数据的指针。
  • 假设std::deque 是基于std::vector,你同样可以很好地使用它。
  • @user0042 std::deque 没有被标准规定为基于std::vector。这样做毫无意义,因为这两种容器类型在几个任务上的表现非常不同。两者都是随机可访问的容器,但双端队列专门用于快速端(前端或后端)插入和移除,而向量提供保证的连续性;双端队列没有提供的东西,以及为什么 OP 这样做的原因。

标签: c++ algorithm stl deque


【解决方案1】:

拥抱 C++ 标准库。假设_tx 真的是std::deque<uint8_t>,那么一种方法就是:

std::vector<uint8_t> msg(_tx.cbegin(), _tx.cend());
_marshaller.sendSysex(firmata::I2C_REQUEST, msg.size(), msg.data());

这会分配适当大小的连续缓冲区,从源迭代器对复制内容,然后调用您的发送操作。向量将在范围退出时自动清理,如果构建它的分配失败,则会引发异常。

标准库提供了大量的方法来传递数据,特别是考虑到迭代器标记了从哪里开始和在哪里停止。不妨利用它来发挥你的优势。此外,让 RAII 像这样处理实体的所有权和清理,而不是手动管理内存几乎总是一种更好的方法,应该受到鼓励。

总而言之,如果您需要连续性(并且从发送调用的外观判断,这正是您这样做的原因),那么从非连续空间复制到连续空间几乎是您唯一的选择,而且占用空间和复制时间。为了避免这种情况,您无能为力。我想偷看std::deque 的实现细节并可能做一些类似堆叠发送调用的事情是可能的,但我严重怀疑会有任何回报,唯一的节省可能会在多发送调用中消失。

最后,还有一个值得考虑的选择。看看这一切的根源。 std::deque 真的有保证吗?例如,当然是您在其他地方构建该容器。如果您可以使用std::vector 以高效或几乎如此高效地执行构建操作,那么整个问题就会消失,因为您可以发送它。

例如,如果您知道(可证明)您的 std::deque 永远不会大于某个大小 N,您可以预先设置 std::vector 或类似的连续 RAII-受保护的分配,大小为2*N,在中间开始一个前迭代器和后迭代器对,或者通过向后移动前迭代器来添加数据,或者通过向前移动后迭代器来添加数据。最后,您的数据将在前后连续,剩下的就是发送。不需要副本,但仍需要添加空间。这一切都取决于确定地知道最大消息大小。如果您可以使用,这可能是一个值得分析的想法。

【讨论】:

  • RAII 是一个很好的观点。我不喜欢这种分配,这就是促使我提出这个问题的原因。但是,我知道这不是更节省空间,而且我怀疑它是否更高效,所以虽然这是个好建议,但它并不能真正回答问题。
  • @Zak 如果您需要连续性(并且从发送调用的外观判断,这正是您这样做的原因),那么从非连续空间复制到连续空间几乎是您唯一的选项,这需要空间和复制时间。为了避免这种情况,您无能为力。我想窥探std::deque 的实现细节并可能做一些类似堆叠发送调用的事情是可能的,但我严重怀疑会有任何回报,唯一的节省可能会在多发送调用中消失。跨度>
  • 将其粘贴到您的答案中,我会给您打勾。还要感谢您的精彩、完整和深思熟虑的对话。
  • @Zak Done,以及您可能会发现有用的有趣思维代码。祝你好运。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-10-03
  • 2015-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-17
  • 2021-10-30
相关资源
最近更新 更多