【问题标题】:How to use std::sort with opaque data types efficiently?如何有效地使用带有不透明数据类型的 std::sort?
【发布时间】:2013-02-23 23:19:33
【问题描述】:

我正在开发一个定义接口的 SDK,例如

class FooIter
{
    // Move to the next foo, return false if there is none.
    virtual bool Move() = 0;

    // Return a pointer to the current foo.
    virtual const void* GetFoo() = 0;

    // Get the size of a 'foo', which is a fixed-size POD.
    virtual size_t GetFooSize() = 0;

    // Get a comparator for foos.
    virtual const FooComparator* GetComparator() = 0;
};

class FooComparator
{
    virtual int compare(const void* first, const void* second) const = 0;
};

所以基本上, foo 是一种不透明类型,我可以将其视为固定长度的二进制缓冲区 + 和相关的排序函数。

现在,我想在将这些 foo 传递回客户端代码之前对其进行排序。可能有 许多 foo,所以我必须实现外部排序,但我想使用 std::sort 对初始运行进行排序。

我在想我会分配一个大小为 N * FooIter::GetFooSize() 的缓冲区,使用 FooIter 用 foos 填充它,然后在将其写入磁盘之前使用 std::sort 对其进行排序。

我可以从编写一个迭代器类开始

class FooBufferIter
{
public:
    FooBufferIter(const void* fooAddr, int fooSize) : m_fooAddr(fooAddr), m_fooSize(fooSize) {}

    FooWrapper operator*() {return FooWrapper(m_fooAddr, m_fooSize);}

    FooBufferIter operator++() {return FooBufferIter(m_fooAddr + m_fooSize, m_fooSize);}

    // All other needed iterator methods.
private:
    const void* m_fooAddr;
    int m_fooSize;
};

还有一个 foo 内存的包装类

class FooWrapper
{
public:
    FooWrapper(const void* fooAddr, int fooSize) : m_fooAddr(fooAddr), m_fooSize(fooSize) {}

private:
    const void* m_fooAddr;
    int m_fooSize;
};

我的理解是 std::sort 将使用 std::swap 重新排列序列中的元素。我的问题是我看不到如何在 FooWrapper 上专门化 std::swap 以有效地执行交换(最重要的是,没有动态分配)。我可以逐字节交换,但这似乎也效率低下。

这样做的另一种方法是将指针的并行序列排序到我的 Foo 数组中,但我不想这样做,因为在实践中,foo 可能会非常小,所以并行序列可以使用与 foo 序列一样多的内存,我想最大化一次可以排序的它们的数量。

还有很好的 ol' qsort 可能更适合这种事情,但我不确定如何将 FooComparator 对象转换为函数指针(FooComparator 可能有多种实现)。

或者有没有更好的方法来解决这个问题?我真的不想编写自己的排序实现,尽管它可能不会难。

【问题讨论】:

  • 我不明白为什么字节交换会不好。您需要重新排列对象,因此您需要复制内存(或指向它们的指针)。总的来说,它实际上取决于 opaque 类型的实现。如果它们可以有效地交换,那就没问题了。
  • @pmr - 我可能只是想多了,但交换它们的理想方法是有一个足够大的临时缓冲区来容纳一个 foo,memcpy a 到它,memcpy b 到 a,然后memcpy 将临时缓冲区放入 b。如果按字节进行,则需要在循环中进行 n 字节交换,其中 n 是 FooIter::GetFooSize()
  • 是的,但正如我所说:如果你必须做一个手术,你需要做。临时缓冲区将是静态的(它也是静态的,因为您不能交换不同类型的数据)。您可能可以进行(逐字异或交换)[en.wikipedia.org/wiki/XOR_swap_algorithm],但这是否真的会使其更快还有待观察。
  • 好吧,我认为实际的答案是自己实现排序,从而允许安全使用一次性分配的缓冲区作为临时缓冲区。但是现在我已经考虑了更多,这确实是过早的优化。

标签: c++ sorting stl


【解决方案1】:

我会构建一个 void* 缓冲区,对它们进行排序,然后生成输出缓冲区。

作为第一步。因为容易。然后编写其他所有内容并寻找性能瓶颈。

下一步,我会看看是否可以使用完整的类型信息进行内部排序。因为最优。

如果做不到这一点,一个带有专门交换的 pod 块伪引用迭代器。如果性能测试证明进一步优化是合理的,那么它对小型和大型的 tomfoolery 进行排序,因此它对大的指针和小型的数据进行排序。

但是从 KISS 开始,先做必须努力的部分。

【讨论】:

  • 基于 FooIter::GetFooSize() 有不同的实现可能是要走的路...
猜你喜欢
  • 1970-01-01
  • 2013-07-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-18
  • 2021-06-02
  • 1970-01-01
相关资源
最近更新 更多