【问题标题】:How to improve performance of a dynamic array implemented with void**?如何提高使用 void** 实现的动态数组的性能?
【发布时间】:2019-03-25 05:00:06
【问题描述】:

我需要实现一个可以处理任何类型的简单动态数组。

现在我的void** 实现比直接使用int* 慢约50%:

#define N 1000000000

// Takes ~6 seconds
void** a = malloc(sizeof(void*) * N);
for (int i =0; i < N; i++) {
        *(int*)(&a[i]) = i;

}
printf("%d\n", *(int*)&a[N-1]);

// Takes ~3 seconds
int* b = malloc(sizeof(int) * N) ;
for (int i =0; i < N; i++) {
        b[i] = i;
}
printf("%d\n", b[N-1]);

我不是 C 专家。有没有更好的方法来做到这一点?

谢谢

编辑

看起来使用void** 是个坏主意。有没有办法用void* 实现这个?

这是它在 Go 中的实现方式:

type slice struct {
        array unsafe.Pointer
        len   int
        cap   int
}

我想做类似的事情。

edit2

我设法用void* 实现了这一点。

解决方案非常简单:

 void* a = malloc(sizeof(int) * N);
 for (int i = 0; i < N; i++) {
    ((int*)a)[i] = i;
 }
 printf("%d\n", ((int*)a)[N-1]);

现在的性能是一样的。

【问题讨论】:

  • 您的编译器可能很难优化第一个版本,因为您的指针类型不一致;没有指向指针的指针,但你声明void** a
  • void **a 和所有这些诡计的目的是什么?这应该解决什么问题?
  • 看起来void** a 版本会导致未定义的行为。你有一个指向指针类型的指针,你初始化了第一个点数组,但是指向的指针没有被初始化,你写入它们。基本上,您正在写入一个充满垃圾指针的数组。
  • 可能sizeof(void*) 大于sizeof(int) 提示:您可以打印尺寸!

标签: c dynamic-arrays void-pointers


【解决方案1】:

您的两个备选方案并不相似。在第二个有效的情况下,您分配的空间足以容纳N 整数,然后将值分配给该空间的int-size 成员。然而,在第一个中,您分配了足够大的空间以容纳指向 void 的 N 指针,然后,在不初始化这些指针的情况下,您尝试将值分配给 它们指向的对象。即使这些指针已被初始化为指向int 对象,也存在额外的间接级别。

从某种意义上说,您的第一个代码可以被纠正,如下所示:

void** a = malloc(sizeof(void*) * N);
for (int i =0; i < N; i++) {
        a[i] = (void *) i;

}
printf("%d\n", (int) a[N-1]);

这依赖于 C 允许指针和整数类型之间的转换(尽管不一定没有数据丢失)这一事实,并注意只有一个间接级别(数组索引),而不是两个。

由于第一个替代方案的实现行为未定义,我们只能推测它在实践中运行速度较慢的原因。但是,如果我们假设一个简单的实现,那么您观察到的这种性能损失可能是由于所有数组写入的缓存局部性不佳造成的。

【讨论】:

  • "C 允许指针和整数类型之间的转换" 是的,但只有指针->整数->指针,而不是整数->指针->整数。这不一样。
  • @alk,C 允许整数->指针和指针->整数。这些可以任意组合组成。我认为您的意思是整数->指针->整数不能保证保值,这与我打算通过“尽管不一定没有数据丢失”来传达的意思相同。然而,在实践中,int -> void * -> int可能是保值的,这是由实现定义的,因此人们可以获得对是否可以任何给定的实现都依赖它。
  • @JohnBollinger 现在我收到了cast to 'void *' from smaller integer type 'int'。此外,我不想只存储整数。
  • @Alex,这将是一个警告,而不是错误。您可以先将其转换为intptr_t,然后将结果转换为int,以使其静音,尽管您的编译器可能会替换为不同的警告。至于特定于ints,我提出的替代方案既不比你原来的int-specific 多也不少。您必须在某处进行适当的特定于类型的转换才能使其正常工作。
  • C11 6.3.2.3/6:“任何指针类型都可以转换为整数类型。[...]如果结果不能以整数类型表示,则行为未定义." 假设void* 为64 位,int 为32 位,当将void* 分配给int 时,我们有UB,不是吗?
【解决方案2】:

请注意,sizeof(void *) 是 64 位处理器上sizeof(int) 的双倍(8 字节地址与 4 字节有符号整数)。如果这是您的情况,我敢打赌,区别仅在于页面缓存未命中。您的内存单元需要加载两倍以上的页面,这很慢(link for more information here)。

还请注意,C++ 向量不是“可用于任何类型的动态数组”。它们绑定到一个类型,例如:std::vector&lt;int&gt; 是一个动态数组,但您只能存储 int

解决您的问题的方法是在 C 中实现某种 std::vector&lt;void *&gt;。但它效率不高:。

  • 您需要为每个元素分配 2 次(1 次分配给容器,1 次分配给元素本身)
  • 每次访问数据都需要做2级间接访问(1级获取容器中的指针,1级获取元素中的数据)
  • 您需要在每个元素中存储某种类型的信息。如果没有,您不知道动态数组中有什么

【讨论】:

  • C++ 指的是 OP 的评论,澄清了这个问题:我正在尝试实现一个适用于任何类型的动态数组。类似于 C++ 中的向量。
  • 是的,C++ 向量是一个不好的例子,因为它只是模板。我可以对宏或 codegen 做同样的事情,但我不想拥有 N * nr_types 函数。我想以 Go 切片的工作方式实现它。我设法做到了(见我的回答)。
【解决方案3】:

我设法用void* 实现了这一点。

解决方案非常简单:

 void* a = malloc(sizeof(int) * N);
 for (int i =0;i<N;i++) {
  ((int*)a)[i] = i;
 }
 printf("%d\n", ((int*)a)[N-1]);

现在的性能是一样的。

我还看到了这篇很棒的文章,它解释了如何在 C 中实现通用数据结构:

http://jiten-thakkar.com/posts/writing-generic-stack-in-c

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-14
    • 1970-01-01
    • 2023-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-08
    • 1970-01-01
    相关资源
    最近更新 更多