动态分配许多小块内存

Dynamically allocate many small pieces of memory

本文关键字:内存 许多小 动态分配      更新时间:2023-10-16

我认为这是一个非常普遍的问题。让我举个例子。

我有一个文件,它包含许多行(例如一百万行),每一行都是以下形式:首先是数字X,然后是长度为X的字符串。

现在我想读取文件并存储所有字符串(无论出于何种原因)。通常,我要做的是:对于每一行我读取长度X,并使用malloc(在C中)或new(在c++中)分配X字节,然后读取字符串。

我不喜欢这个方法的原因是:它可能会发生,大多数字符串都很短,比如在8字节以下。在这种情况下,根据我的理解,分配将是非常浪费的,无论是在时间和空间上。

(这里的第一个问题:我是否理解正确,分配小块内存是浪费的?)

我考虑过以下优化:每次我分配一个大块,比如1024字节,每当需要一小块时,就从大块中删除它。这种方法的问题是,回收几乎是不可能的…

这可能听起来像我想做内存管理自己…但是,我还是想知道是否有更好的方法?如果需要,我不介意使用一些数据结构来进行管理。

如果你有一些好主意,只能有条件地工作(例如,知道大多数作品都很小),我也会很高兴知道它。

进行内存分配的"自然"方法是确保每个内存块至少足够大,可以包含一个指针和一个大小,或者一些类似的簿记,足以维护一个自由节点的结构。具体细节各不相同,但是您可以通过查看从分配器获得的实际地址来实验性地观察开销,当您进行少量分配时。

在这个意义上,小的分配是"浪费"的。实际上,在大多数C或c++实现中,所有块都四舍五入到2的某个幂的倍数(幂取决于分配器,有时取决于分配的数量级大小)。所以所有的分配都是浪费的,但是按比例来说,如果大量的1和2字节分配被填充到16字节,比大量的113和114字节分配被填充到128字节,浪费更多。

如果你愿意放弃释放和重用单个分配的能力(这是好的,例如,如果你计划在你担心这个文件的内容后释放所有的),那么当然,你可以以更紧凑的方式分配许多小字符串。例如,将它们首尾相连地放在一个或几个大的分配中,每个字符串以空结束,并处理指向每个字符串第一个字节的指针。每个字符串的开销是1或0字节,这取决于您如何考虑null。如果您只是用空字节覆盖换行符,那么在将文件分割成行的情况下,这可以特别巧妙地工作。显然,您不需要介意每行都删除了换行符!

如果您需要释放和重用

,并且您知道所有分配的大小是相同的,那么您可以从簿记中删除大小,并编写自己的分配器(或者,在实践中,找到一个您满意的现有池分配器)。分配的最小大小可以是一个指针。但是,只有当所有字符串都小于指针的大小时,"most"才不会那么简单。

是的,静态分配一个较大的缓冲区并读入其中是读取数据的常用方法。

假设您选择1KB作为缓冲区大小,因为您希望大多数读取都适合这个大小。

您是否能够将大于1KB的罕见读取切成多个读取?

    吗?

    • 当且仅当需要时,您可以动态分配。一些简单的指针魔法就可以完成这项工作。

    static const unsigned int BUF_SIZE = 1024;
    static char buf[BUF_SIZE];
    while (something) {
        const unsigned int num_bytes_to_read = foo();
        const char* data = 0;
        if (num_bytes_to_read <= BUF_SIZE) {
           read_into(&buf[0]);
           data = buf;
        }
        else {
           data = new char[num_bytes_to_read];
           read_into(data);
        }
        // use data
        if (num_bytes_to_read > BUF_SIZE)
           delete[] data;
    }
    

    这段代码是C、c++和伪代码的完美混搭,因为您没有指定语言。

    如果你真的在用c++,看在上帝的份上,就用向量吧;如果需要,就让它生长,否则就重新使用它的存储空间。

    您可以先计算文本的行数及其总长度,然后分配一个内存块来存储文本,再分配一个内存块来存储文本中的指针。通过第二次读取文件来填充这些块。记住要加上结束的0。

    如果整个文件适合内存,那么为什么不获取文件的大小,分配那么多的内存和足够的指针,然后读取整个文件并创建指向文件中行指针的数组呢?

    我会使用最大的缓冲区来存储"x"。你没有告诉我们,当sizeof(x)时x的最大大小是多少。我认为将其存储在缓冲区中以避免对每个单词进行寻址并相对快速地访问它们是至关重要的。

    例如:

    char *buffer = "word1word2word3";
    

    当存储地址或…等时。"快速"访问

    变成了这样:

    char *buffer = "xx1word1xx2word2xx3word3";
    

    正如你所看到的,对于固定大小的x,它可以非常有效地跳到一个字到另一个字,而不需要存储每个地址,只需要读取x并使用x进行跳加addr…X不转换为char,整型注入和读取使用他的类型大小,不需要字符串的结尾为字这种方式,只有为完整buff知道缓冲区的结束(如果X ==0,那么它的结束)。

    我不太擅长解释,因为我的英语不好,我给你推了一些代码作为更好的解释:

    #include <stdio.h>
    #include <stdint.h>
    #include <string.h>
    void printword(char *buff){
        char *ptr;
        int i;
        union{
            uint16_t x;
            char c[sizeof(uint16_t)];
        }u;
        ptr=buff;
        memcpy(u.c,ptr,sizeof(uint16_t));
        while(u.x){
            ptr+=sizeof(u.x);
            for(i=0;i<u.x;i++)printf("%c",buff[i+(ptr-buff)]);/*jump in buff using x*/
            printf("n");
            ptr+=u.x;
            memcpy(u.c,ptr,sizeof(uint16_t));
        }
    }
    void addword(char *buff,const char *word,uint16_t x){
        char *ptr;
        union{
            uint16_t x;
            char c[sizeof(uint16_t)];
        }u;
        ptr=buff;
    /* reach end x==0 */
        memcpy(u.c,ptr,sizeof(uint16_t));
        while(u.x){ptr+=sizeof(u.x)+u.x;memcpy(u.c,ptr,sizeof(uint16_t));}/*can jump easily! word2word*/
    /* */
        u.x=x;
        memcpy(ptr,u.c,sizeof(uint16_t));
        ptr+=sizeof(u.x);
        memcpy(ptr,word,u.x);
        ptr+=u.x;
        memset(ptr,0,sizeof(uint16_t));/*end of buffer x=0*/
    }
    int main(void){
        char buffer[1024];
        memset(buffer,0,sizeof(uint16_t));/*first x=0 because its empty*/
        addword(buffer,"test",4);
        addword(buffer,"yay",3);
        addword(buffer,"chinchin",8);
        printword(buffer);
        return 0;
    }