我正在阅读有关slab分配器的内容,当我调用read来从socket中获取数据时,我想将这种特殊技术用于我的应用程序(读取将数据从内核缓冲区复制到我选择的缓冲区中,我想要使用一些动态分配的内存).
在我阅读时,似乎Linux内核已经在使用这种技术了.如果这用于实现malloc或者new,那么鉴于分配已经具有性能,我仍然值得这么做吗?
我认为在没有SLAB算法的情况下使用堆栈分配可能会更好,但我不确定哪种方法最好.
解决方法
但是,您可能不会遇到任何问题,只需对每个请求进行malloc,除非您真的接近机器的极限,这似乎不太可能.但是我相信知道你的选择比用别人的话来说更好.以下是一些需要考虑的想法.
静态数组
最简单的替代方法是使用单个全局请求槽阵列,并跟踪哪些正在使用.这意味着对多少请求的静态限制,但另一方面,没有开销,也没有碎片的真正问题.只是设置限制真的很高.
这是一个示例实现.如果您不熟悉按位操作,它可能看起来有点令人困惑,但要点是我们有一个额外的数组,每个请求槽包含一个位(打开或关闭),指定该插槽是否正在使用中.相反,你可以在结构本身中添加一个’is_used’变量,但最终会将结构填充多于一位,这违背了我们最小化开销的目标.
头文件非常小(顺便说一下,这是C的真正之美!):
typedef struct request_s { /* your 32 bytes of information */ unsigned char data[32]; } request_t; request_t *alloc_request(void); void free_request(request_t *req);
源文件:
#include the header file /* note: this example is not written with multithreading in mind */ /* allow a million requests (total 32 MB + 128 KB of memory) */ #define MAX_REQUESTS (1*1024*1024) static request_t g_requests[MAX_REQUESTS]; /* use one bit per request to store whether it's in use */ /* unsigned int is 32 bits. shifting right by 5 divides by 32 */ static unsigned int g_requests_used[MAX_REQUESTS >> 5]; request_t *alloc_request(void) { /* note: this is a very naive method. you really don't want to search * from the beginning every time,but i'll leave improving that as an * exercise for you. */ unsigned int word_bits; unsigned int word,bit; /* look through the bit array one word (i.e.,32 bits) at a time */ for (word = 0; word < (MAX_REQUESTS >> 5); word++) { word_bits = g_requests_used[word]; /* we can tell right away whether the entire chunk of 32 requests is * in use,and avoid the inner loop */ if (word_bits == 0xFFFFFFFFU) continue; /* now we know there is a gap somewhere in this chunk,so we loop * through the 32 bits to find it */ for (bit = 0; bit < 32; bit++) { if (word_bits & (1U << bit)) continue; /* bit is set,slot is in use */ /* found a free slot */ g_requests_used[word] |= 1U << bit; return &g_requests[(word << 5) + bit]; } } /* we're all out of requests! */ return NULL; } void free_request(request_t *req) { /* make sure the request is actually within the g_requests block of * memory */ if (req >= g_requests && req < g_requests + MAX_REQUESTS) { /* find the overall index of this request. pointer arithmetic like this * is somewhat peculiar to c/c++,you may want to read up on it. */ ptrdiff_t index = req - g_requests; /* reducing a ptrdiff_t to an unsigned int isn't something you should * do without thinking about it first. but in our case,we're fine as * long as we don't allow more than 2 billion requests,not that our * computer could handle that many anyway */ unsigned int u_index = (unsigned int)index; /* do some arithmetic to figure out which bit of which word we need to * turn off */ unsigned int word = u_index >> 5; /* index / 32 */ unsigned int bit = u_index & 31; /* index % 32 */ g_requests_used[word] &= ~(1U << bit); } }
(是的,是的,您可以编写索引/ 32而不是索引>> 5,依此类推,编译器会为您优化它.但它对我来说感觉不对…)
你可以深入研究并优化分配器搜索空闲插槽.一个简单的想法是在最后一次分配的位置开始搜索,并在结束时回滚.
这种方法有很多好处,但一个很大的缺点:限制.您可能希望将限制设置为高于您预期的最大请求数量.但是,如果你能做到这一点,那么你可能没有碰到你系统的极限,所以你为什么要放在首位呢?!
内存池(静态数组的链表)
如果您不喜欢静态限制,则可以批量分配.保留一个内存“池”的链表,每个池都有一定数量的请求槽.每个单独的池基本上是如上所述的静态阵列.如果所有现有池都已满,我们将一个新池malloc并将其添加到链接列表中.池基本上看起来像这样:
#define REQUESTS_PER_POOL 1024 typedef struct request_pool_s request_pool_t; struct request_pool_s { request_t requests[REQUESTS_PER_POOL]; unsigned int requests_used[REQUESTS_PER_POOL >> 5]; request_pool_t *prev; request_pool_t *next; };
当流量消失时,你会希望能够释放游泳池,否则这几乎与静态限制不同!不幸的是,在这种情况下,您将处理碎片问题.您可能会发现自己有很多稀疏使用的池,特别是如果请求有时可能持续很长时间.整个池在其上的每个最后一个插槽为空之前都无法释放.你仍然可以节省开销(考虑到个别请求的小规模),但处理碎片可能会将这从一个小巧,优雅的解决方案转变为更多的工作而不是它的价值.
您可以减少每个池的请求数以减少碎片的影响,但此时我们将失去此方法的优势.
哪一个?
首先,您应该考虑单个malloc的替代方案的主要原因是:结构的小尺寸(32字节),大量的结构,以及创建和销毁它们的频率.
>静态数组减少了很多开销,但在这个时代很难证明其合理性.除非您的服务器在Arduino上运行.
>内存池是这类问题的明显方向,但它们可能需要相当多的工作才能顺利运行.如果这是你的胡同,那么我说去吧.
> Slab分配器就像复杂的内存池,不限于某个单一的struct大小.它们对你来说太过分了,因为你只有32字节的请求,尽管你可能找到适合你的第三方库.
采取简单的路线,简单地malloc’ing每个请求是一个滑坡,最终可能会完全放弃C.