【问题标题】:How to deduce contiguous memory from iterator如何从迭代器中推断出连续的内存
【发布时间】:2018-05-18 10:36:11
【问题描述】:

不知何故,VC++ (Dinkumware) 上的原生 stl::copy() 算法发现它可以在可简单复制的数据上使用 memcpy()。一个普通人能做到吗? - 假设每个元素都是_trivially_copyable。

random_access_iterator 是否意味着连续内存?标准对我来说不是很清楚。

所以,如果模板中只有一个或两个迭代器,是否有可能在编译时推断出底层数组可以用memcpy() 复制,如果可以的话怎么做?

编辑 - 这是我的动机。我有一个移动数据块而不是复制它的例程。为了在数据可存储时加快速度,我有时将其称为 stl::copy。我想知道这是否是唯一的方法。 (小时候,我总是会在家里尝试这个。)

// Move a range of values
template<class Ptr, class Ptr2>
inline Ptr2 move(Ptr src, Ptr end, Ptr2 dest) {
    using value_type = std::iterator_traits<Ptr>::value_type;
    if constexpr (std::is_trivially_copyable_v<value_type>) {
        return std::copy(src, end, dest);
    } else {
        while (src != end) {
            *dest = std::move(*src);
            ++src; ++dest;
        }
    }
    return dest;
}

编辑:感谢 zett42 找到这个相关问题:Contiguous iterator detection 我无法解释我是如何错过它的。

编辑更多:在经历了许多曲折的小段落之后,我发现 Dinkum 使用秘密标签作为与人群中的迭代器,例如_Really_trivial_ptr_iterator_tag。所以前景看起来很暗淡。

我的价值 0.02 美元:将 iterator_category 设为 ad-hoc 类型而不是破坏各种特征(如“points_into_contiguous_memory”等)是初学者的错误......因为 random_access_iterator 是仅用标签表示的 ad-hoc 类型,它不能在不破坏遗留应用程序的情况下进行子类型化。所以委员会现在有点卡住了。我说,是时候重新开始了。

哦,好吧。

【问题讨论】:

  • std::vector 的迭代器既是随机访问的,又代表连续的元素。这是根据定义。请注意,调用 memmove 不一定是最快的副本。如果你在 gcc 上使用 -O2 和 -march=native 编译 std::copy,你会得到更好的代码。
  • 只有几个contiguous iterator 类型。编译器非常了解它们,并且能够识别何时使用它们并进行所需的优化。
  • 假设你不知道迭代器是std::vector。
  • 快速浏览一下 MSVC 对 std::copy() 的实现,没有编译器魔法可以决定是否可以使用 memmove。模板元编程检测连续的迭代器。但这是非标准化的东西,另见Contiguous iterator detection
  • 可能有内部和特定于实现的类型、特征和标签可供算法函数检查。不幸的是,std::iterator_traits 没有连续迭代器的标签。

标签: c++ algorithm templates c++17 memcpy


【解决方案1】:

random_access_iterator 是否暗示连续内存?

简短回答:不。

长答案:取决于类型。

示例:对于std::vector(带有随机访问迭代器),内存被授予所有值都在一个可以从data()方法访问的连续内存块中。

对于std::deque(也带有随机访问迭代器),您知道内存区域被分成块(std::deque 被设计为可以有效地插入/删除中间元素),因此内存不太可能是连续的。

【讨论】:

  • 真糟糕。那么,回到这个问题,如果只保证某些 stl 容器是连续的,那么在只给定迭代器的情况下如何专门化它们?
  • @JiveDadson - 您可以研究标准,检测几个连续授予的容器并创建自定义类型特征,返回编译时真值(或假值,根据容器)并决定使用这种类型的特征(不是一个很好的解决方案;我知道;但在我看来是最好的)
  • 模板只能看到迭代器,不能看到容器。迭代器对它们指向的容器类型保密。也许他们甚至都不知道。
  • @JiveDadson - 我明白了......我认为,不幸的是,你是对的:从迭代器开始,没有办法检测相应的容器;所以我没有看到解决方案。
  • 然而,不知何故 DInkumware 做到了,至少对于指向 stl::vector 的指针。
【解决方案2】:

A) 不。这只是意味着存在 F(iterator +/- n) = iterator.next/prev.....n 类型的有效映射。这根本不意味着连续分配。 (界限确实适用)

B) 不,这取决于实施。例如,如果有 2 级间接,您可能不知道可能会收到哪种数据结构。

好消息?,您完全不必费心。在缓存和发生的分支预测之间,您根本不需要优化它。在运行期间,缓存行会被你打算复制的连续内存块填充,因此速度会非常快,而 memmove 或 memcpy 不会有太大帮助。

现代处理器无法保证在运行时将指令流水线化,并且他们会知道它是否是连续的。

【讨论】:

  • 编译器可能会生成可接受的代码,CPU 可以合理运行,但即使使用该代码的单个线程也无法接近内存带宽的限制。查看 C 库实现 memcpy 的汇编代码,了解它们将在多长时间内重叠多个操作并有效地将位进出内核。
猜你喜欢
  • 2017-08-15
  • 2011-03-26
  • 1970-01-01
  • 2017-08-08
  • 1970-01-01
  • 1970-01-01
  • 2015-05-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多