在C语言中,什么样的比例才能达到零分配内存?

dxpyg8gm  于 2023-10-16  发布在  其他
关注(0)|答案(4)|浏览(97)

我的代码需要处理一个包含多个字符串的大型结构数组。
实际上,整个数组将包含大约25 k个结构,每个结构的大小大约为256字节,因此整个数组需要大约6 MiB的堆空间。

// example
struct element {
    char foo[32];
    char bar[16];
    ...
}; // sizeof(struct element) = 256

我担心calloc的性能,因为它会将所有内存清零,而且我不需要初始化每个字节。所以我做了element_arr = malloc(num_elements * sizeof(struct element))
我在运行时分配数组,因为我在编译时不知道num_elements
为了使代码正常工作,我实际上只需要每个成员(foobar等)的第一个字节为零,其余的可以保持未初始化。
假设我每个结构有8个字符串成员,所以我只需要3%的零字节,其他97%的清除字节是浪费的,因为它们最终会被真实的数据覆盖)。
我有几个选择:
1.立即将所有内容归零,例如:用calloc,它确实(我希望)使用矢量指令来写大块对齐的零。

  1. memset每个256字节大小的struct element,然后用真实的数据填充它。
    1.在使用struct element之前,为每个成员分配0。(*element->foo = 0; ...)这将转换为mov指令链,并在-O3处进行优化。这是繁琐的语言明智的(但可以照顾)。
mov     byte ptr [rdi + 152], 0
        mov     byte ptr [rdi + 208], 0
        mov     byte ptr [rdi + 200], 0
        mov     byte ptr [rdi + 128], 0
        ...

与ARM 64类似。
1.对element_arr的大小做一个非常保守的假设(例如,64 MiB),将其放置在内存的零初始化部分。(操作系统需要将我的内存清零)
char element_arr[64 * 1000 * 1000] = {0};(检查num_elements < 250000以确定)
选择什么有什么区别吗?你有什么建议?
编辑:@John Bayko各个结构是递增填充的,但是所有的字符串都需要以“\0”开头,否则算法无法区分真实的字符串(已经填充)或未初始化的垃圾。
在阅读其他答案后,我相信这可能不会很快成为一个问题。很高兴知道最简单的解决方案(calloc)在大多数用例中都是很好的解决方案。
我在我的开发机器上分析了我的代码,实际上,分配所花费的时间是可以忽略的。
谢谢你的回复。

w1e3prcc

w1e3prcc1#

你有什么建议?
设置一个测试工具来评估性能,然后对各种代码选项进行评级。
如果不对代码进行分析,仍然会有许多依赖于实现的问题。考虑到操作系统可以在空闲时间将内存池归零。例如,代码执行scanf()调用并等待输入。在这段时间内,可能会将兆字节的内存归零,以供以后使用calloc()--实际上使程序的内存归零时间为零。OS甚至可以使用程序启动前创建的池中的零内存。
即使element_arr = calloc_or_malloc(num_elements * sizeof(struct element))在那个时候也不一定会使用内存,因为真正的使用可能会延迟到代码访问内存。

dohp0rv5

dohp0rv52#

你可能过早地考虑了优化。只初始化一部分内存进行计算,或者手动调用memset(),很容易比只调用calloc()慢得多。
如果分配6 MiB内存,则可能超过M_MMAP_THRESHOLD,因此内存将直接来自专用的mmap()调用,并且将在开始时已归零,而无需执行任何操作。因此,在这里使用calloc()以外的任何东西都没有意义。
只有在使用某种特殊的libc实现时才进行选择性归零,并执行适当的基准测试以确定其性能。如果你正在使用glibc、musl或其他主要的C库,你应该避免在如此大的分配上手动清零,而使用calloc()
在任何情况下,也要考虑到现代CPU可以以 * 每秒千兆字节 * 的速度执行清零,所以如果这是你在程序的整个运行时只做一次或两次的事情,它将是不明显的,而且只使用calloc()而不是手动和选择性地清零更容易,这可能会导致错误/bug。

5kgi1eie

5kgi1eie3#

你应该使用最简单的方法:如果您可以利用calloc执行的零初始化(大部分是免费的),那么使用calloc来分配整个数组。
尝试对使用malloccalloc的版本进行基准测试是非常棘手的:对于较大的存储器块,malloccalloc很可能从操作系统获得一组新的存储器Map页面,出于安全原因,无论如何,在现代操作系统中,这些页面将被初始化为零,以防止进程窃听其他进程释放的存储器。
这意味着零初始化基本上是免费的,手动清零内存几乎总是效率较低。
更令人惊讶的是,calloc可能会返回一个页面块,这些页面实际上是同一个零初始化页面的别名,并使用写时复制标记进行Map,因此这些页面只会在程序实际修改时进行Map和初始化。
您应该阅读这些问题和页面:

hs1ihplo

hs1ihplo4#

从你在文本中所说的,在代码之后,恕我直言,初始化结构体的最好方法是在每个字符串数组的第一个字节中放置一个0,这可以通过一个简单的循环来完成,比如:

struct element *p = element_arr;
for (int i = 0; i < 64 * 1000 * 1000; i++, p++) {
    p->foo[0] = '\0';
    p->bar[0] = '\0';
    /* ... */
}

它只会把0存储在有趣的地方,而不需要填满整个数组。这将是更有效的,如果你在结构中每个数组平均有32-64个字节,每个字段平均有48个字节,你将在结构中有大约5个字段,你将对一个字符进行125,000次赋值,而不是6,000,000次,这是一个很好的改进。对不对?

相关问题