【问题标题】:Cheapest structure for deferencing?最便宜的防御结构?
【发布时间】:2015-02-10 15:44:45
【问题描述】:

假设我有一个以unsigned int 为键的关联数组;值可以是任何固定大小的类型。有一些预定义的最大数量。实例数。

API 使用示例:MyStruct * valuePtr = get(1234);put(6789, &myStructInstance); ...基本。

当我从这个数组中快速随机读取条目时,我想尽量减少缓存未命中,因此我预先设置了malloc(sizeof(MyType) * MAX_ENTRIES) 以尽可能确保引用的局部性。

泛型对于 values 数组很重要。我看过C pseudo-generics,但为了简单起见更喜欢void *;但是,不确定这是否与性能目标不一致。最终,我想知道什么对性能最有利。

我应该如何实现关联数组以提高性能?到目前为止的想法......

  • 我是否向关联数组传递了一个指向malloced 值数组的void * 指针并允许它在内部使用它(为此我们需要保证匹配的键数组大小)?我可以一般地这样做吗,因为需要(?)知道类型才能索引到 values 数组?
  • 我是否在关联数组中有一个单独的void * valuePtrs[],然后让这些指针指向malloced 值数组中的每个元素?这似乎可以避免需要了解具体类型?
  • 我是否使用C pseudo-generics 从而允许get() 返回特定的值类型?当然,在这种情况下,唯一的好处是不必显式转换,例如MyStruct* value = (MyStruct*) get(...)... 数组元素仍然需要取消引用,所以开销相同?

而且,总的来说,上述减少 cahce 失误的方法是否有意义?

【问题讨论】:

  • 请考虑展示您如何想象 API 使用数组,这有助于使其更加具体。
  • @unwind 如您所愿,请参阅第二段。
  • 是的......对于那些字符串示例,您是否设想数组中存储的数据只是字符串指针,而不是字符串本身?如果它只是指针,那么你当然会使用void *,因为它可以指向任何东西,然后你的数组就变成了一个“指针数组”。
  • @unwind 好点,实际上,让我调整一下,因为字符串是一个不好的例子。最一般地,值数组将是一个结构数组。编辑:相应更新。
  • 是的,字符串是这样的一个坏例子。现在,如果没有泛型,我看不出你如何可以拥有一个返回类型是动态的函数,就像你展示的那样。它必须是 bool get(void *value, const void *key) 或类似的东西才能复制一个在编译时大小未知的值。

标签: c performance data-structures collections memory-layout


【解决方案1】:

在这两种情况下,性能基本相同。 在第一个(void* 实现)中,您需要查找值 + 取消引用指针。所以这是两条指令。
在另一个实现中,您需要将索引乘以值的大小。所以这个实现也需要两条指令。 然而,第一个实现将更容易和更干净地实现。此外,阵列是完全透明的;用户不需要知道数组中的结构类型。

【讨论】:

  • 谢谢鲁本。事实上,关联数组查找的性能已经与我的用例一样高,其中最坏情况下的读取访问时间比其他任何事情都更重要……我选择了二进制搜索查找。所以恐怕这个答案并不是很有帮助。如果你能详细说明 void * 添加另一个取消引用,我至少可以 +1 给你。
  • 要么使用 void* 数组,它具有完全通用的值数组。如果你想有值,你将不得不跟随指针指向它的位置,从而添加指令。还有一个选项(获取半通用值数组)是使其成为一个结构,其中包含值数组(带有值,而不是指向值的指针)以及此表中条目的大小。这样,您可以在不知道数组中的结构类型的情况下计算条目的位置。第一个是更好的选择。抱歉,我的第一个答案是错误的。
  • 再次感谢鲁本,这是有用的反馈。 +1。
【解决方案2】:

请参阅下面分类的解决方案,根据优缺点(感谢 Ruben 帮助我思考)...我已经为我的用例实现了选项 2 和 5,这有点笼统;如果您需要一个非常具体的一次性数据结构,我推荐选项 4。选项 3 是最灵活的,但对代码来说是微不足道的,也是最慢的。选项 4 是最快的。选项 5 有点慢,但在数组大小和一般使用方面具有灵活性。


关联数组结构指向类型化指针数组:

优点不需要失败值,不需要显式转换,不需要数组的编译时大小

缺点昂贵的双重 deref,需要通用库代码


关联数组结构包含void * 指针数组:

优点不需要失败值,不需要通用库代码

缺点昂贵的双重 deref,get() 之后的显式转换,如果不使用 VLA,则需要数组的编译时大小


关联数组结构指向void * 值的数组:

优点没有通用库代码,不需要数组的编译时大小

缺点代价高昂的三重 deref,get() 之后的显式转换,需要偏移量计算,这需要显式传入 sizeof 值


关联数组结构保存有类型值的数组:

专业人士廉价的单一 deref,不需要显式转换,键和条目连续分配

缺点需要通用库代码,必须提供失败值,如果不使用 VLA,则需要数组的编译时大小


关联数组结构指向类型值数组:

优点不需要显式转换,灵活的数组大小

缺点昂贵的双重 deref,需要通用库代码,必须提供失败值,如果不使用 VLA,则需要数组的编译时大小

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-04-11
    • 2019-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多