3. Linux组件之内存池的实现

news2024/9/24 21:22:27

文章目录

  • 一、为什么需要内存池
  • 二、内存池的工作流程
  • 三、内存池的实现
    • 3.1 数据结构
    • 3.2 接口设计
      • 3.2.1 创建内存池
      • 3.2.2 内存池销毁
      • 3.2.3 内存分配
        • 1. 分配小块内存
        • 2. 分配大块内存
      • 3.2.4 内存池的释放
      • 3.2.5 内存池重置
    • 3.3 完整代码

一、为什么需要内存池

应用程序使用内存,需要先申请内存,使用完之后再释放内存。比如c语言中使用malloc/free来分配/释放内存,c++中使用new/delete来分配/释放内存。内存申请与释放都需要与操作系统进行交互,具体来说就是系统维护一个内存链表,当有一个内存申请过来时,根据分配算法在链表找一个合适的内存分配给它。分配的内存块可能会大于所申请的内存大小,这样需要进行切割,将剩余的内存插入到空闲链表中。释放的时候,系统要判断释放的内存块的前后是否有空闲,若有的话还要进行合并。可见,调用库中的内存分配函数,十分耗时。

另外,频繁的申请、释放内存,会使得一块大内存可能被分割成多块不连续的小内存。当需要再分配连续的大内存时候,尽管剩余内存的总和可能大于所要分配的内存大小,但系统就找不到连续的内存。这就是内存碎片的存在,从而导致分配错误。

综上所述,总结一下使用内存池的几个主要原因:

  • 1)提高内存分配效率:传统的内存分配方式会涉及到频繁的系统调用和内存管理开销,而内存池可以在初始化时一次性地分配一块连续的内存空间,然后根据需要从该池中快速分配内存,避免了频繁的系统调用,从而提高了内存分配的效率。
  • 2)降低内存碎片化:内存池会事先分配一定数量的连续内存块,当需要分配内存时,直接从这些预分配的内存块中获取,不涉及内存的分配和释放,减少了内存碎片的产生,提高了内存的利用率。
  • 3)减少动态内存分配次数:内存池可以在程序初始化的时候一次性分配好所需的内存,然后在运行时通过内存池进行内存分配,避免了频繁的动态内存分配,从而减少了内存管理的开销和潜在的内存泄漏风险。
  • 4)控制内存分配的策略:内存池可以自定义内存分配的策略,例如按照固定大小的内存块进行分配,或者按需动态调整内存池的大小等。这可以根据具体应用场景和需求来选择最合适的内存管理策略。

总之,内存池是一种有效管理内存的机制,通过提高内存分配和释放的效率,减少内存碎片化,降低动态内存分配的次数,能够帮助提高程序的性能和可靠性。

二、内存池的工作流程

1)开始时,申请一块大的内存(在该块内存不够用时在二次分配),
2)需要时,从这块内存中取出,并标记下这块内存被用了
3)释放时,标记此内存被释放了。注意此时并不真的把内存释放给操作系统,而是当一大块内存都空闲的时候,才释放给操作系统。
4)调用内存分配函数的时,小块内存每次都分配固定大小的内存块,这样就避免了内存碎片产生的可能。

参考 nginx 内存池的设计,以大、小块作为内存管理分配的方式。
具体来说,内存池中分大小块,申请内存大小大于某个值定为大块、否则是小块,内部使用链表串联。

在这里插入图片描述

三、内存池的实现

3.1 数据结构

大块内存结构

struct mp_large_s {
	struct mp_large_s *next;	// 指向下一个大块内存
	void *alloc;				// 指向实际分配的大块内存
};

小块内存结构

struct mp_node_s {

	unsigned char *last;		// 指向内存池中已分配结点的末尾,即下次可分配内存结点的首地址
	unsigned char *end;			// 指向内存池的末尾
	
	struct mp_node_s *next;		// 指向下一个结点
	size_t failed;				// 当前的内存池分配失败的次数
};

内存池结构

struct mp_pool_s {

	size_t max;					// 内存池可分配的最大空间,超过的话用大块内存

	struct mp_node_s *current;	// 指向当前内存池
	struct mp_large_s *large;	// 指向大块内存链表

	struct mp_node_s head[0];	// 指向小块内存链表

};

在这里插入图片描述

3.2 接口设计

3.2.1 创建内存池

内存池创建的时候分三块,一块存储mp_pool_s结构体,一块存储mp_node_s结构体,一块为申请的内存块。返回指向整个内存池的指针p。
在这里插入图片描述

struct mp_pool_s *mp_create_pool(size_t size) {

	struct mp_pool_s *p;
	// posix_memalign 用于分配内存块,并保证所分配的内存块以指定的对齐方式进行对齐。
	// 参数:1)一个指向指针的指针,用于保存分配得到的内存块的地址。2)所需的内存对齐方式. 3)要分配的内存块的大小
	// 一开始并不知道大块内存具体大小,因此只需要分配:申请空间的大小 + 内存池结构体大小 + 小块内存结点
	int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
	if (ret) {
		return NULL;
	}
	
	p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;	// 设置小块内存可分配的最大空间
	p->current = p->head; 														// 设置当前指向的内存块
	p->large = NULL;															// 设置大块内存

	p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
	p->head->end = p->head->last + size;
	p->head->failed = 0;

	return p;

}

3.2.2 内存池销毁

void mp_destory_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h, *n;
	struct mp_large_s *l;

	// 释放大块内存
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	// 释放小块内存
	h = pool->head->next;
	while (h) {
		n = h->next;
		free(h);
		h = n;
	}

	// 释放内存池
	free(pool);

}

3.2.3 内存分配

通过mp_alloc向内存池申请内存,如果用户申请的内存大于pool->max,则认为是大块内存,直接向 OS 申请。否则就是小块内存,直接在内存池里面分配。

如果申请小块内存,遍历现有的小块内存链表,寻找是否有满足需求的内存块
1)如果有,返回待分配内存块的首地址
2)如果没有,创新新的小块内存

void *mp_alloc(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *p;

	// 1、申请小块内存
	if (size <= pool->max) {

		p = pool->current;

		do {
			// 遍历小块内存链表,寻找可用的空间分配内存
			m = mp_align_ptr(p->last, MP_ALIGNMENT);
			// 若当前结点的剩余空间足够分配
			if ((size_t)(p->end - m) >= size) {
				p->last = m + size;
				return m;
			}
			// 若当前内存块的剩余内存小于所需内存,则到下一个内存块中寻找
			p = p->next;
		} while (p);

		// 没找到合适的小块内存,则申请小块内存
		return mp_alloc_block(pool, size);
	}

	// 2、申请大块内存
	return mp_alloc_large(pool, size);
	
}

1. 分配小块内存

调用mp_alloc_block创建新的小块内存,并插入到小块内存链表的末尾。

有一种情况,每个小块内存最后都剩下一小块,比如A内存块剩下5,B内存块剩下16。current指向的是A。此时需要申请大小20的内存,从current开始查,A不满足。再申请大小10的内存,从current开始查,A还是不满足。每次都从A开始,就很费时间,因此引入failed
每次调用mp_alloc_block函数,代表内存池现有小块内存的空间分配失败,此时,所有可用的小块内存的 failed + 1,表示不满足用户的需求增加1次。若某个小块内存若连续 5 次不满足用户需求,则不再使用它,遍历时跳过。
在这里插入图片描述

static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *h = pool->head;
	// 第一个小块内存中,可分配的内存大小
	size_t psize = (size_t)(h->end - (unsigned char *)h);
	
	// 申请内存,大小与第一个小块内存一样
	int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
	if (ret) return NULL;

	struct mp_node_s *p, *new_node, *current;

	// 初始化新的内存块
	new_node = (struct mp_node_s*)m;
	new_node->end = m + psize;
	new_node->next = NULL;
	new_node->failed = 0;

	// 将指针m移动到可分配内存的开始位置
	m += sizeof(struct mp_node_s);
	m = mp_align_ptr(m, MP_ALIGNMENT);
	new_node->last = m + size;

	// 从当前指向的内存块开始,寻找最后一个内存块链表的结点
	current = pool->current;
	for (p = current; p->next; p = p->next) {
		// 若某个小块内存连续5次都分配失败,则跳过这个小块内存,下次不再遍历它
		if (p->failed++ > 4) { //
			current = p->next;
		}
	}

	// 将新创建的内存块,尾插到小内存块链表
	p->next = new_node;

	// 更新pool->current指针,判断 current 是否为空?
	// 若非空,指向current指向的结点;若为空,代表之前所有的内存块都分配失败,则指向新的内存块
	pool->current = current ? current : new_node;

	return m;

}

2. 分配大块内存

调用mp_alloc_large()分配一个新的大块内存,插入到链表的开头,头插法。

static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {

	void *p = malloc(size);
	if (p == NULL) return NULL;

	size_t n = 0;
	struct mp_large_s *large;

	// 遍历大块内存链表,找到可以挂载 p 的位置
	for (large = pool->large; large; large = large->next) {
		// 找到可以挂载的地方
		if (large->alloc == NULL) {
			large->alloc = p;
			return p;
		}
		// 若连续 4 次都没找到,就不再寻找了
		if (n ++ > 3) break;
	}

	// 创建一个新的大块内存结点,用来挂载p
	large = mp_alloc(pool, sizeof(struct mp_large_s));
	if (large == NULL) {
		free(p);
		return NULL;
	}

	// 将新创建的结点,头插到大块内存链表中
	large->alloc = p;
	large->next = pool->large;
	pool->large = large;

	return p;
}

3.2.4 内存池的释放

Nginx 内存池内部仅提供大块内存的释放接口

void mp_free(struct mp_pool_s *pool, void *p) {

	struct mp_large_s *l;
	for (l = pool->large; l; l = l->next) {
		if (p == l->alloc) {
			free(l->alloc);
			l->alloc = NULL;

			return ;
		}
	}
	
}

3.2.5 内存池重置

nginx的小块内存是无法释放的,因为这个小块内存的分配都是通过last指针偏移的。
如果1和3分配出去了。但现在2不使用了,怎么归还到小块内存里?mp_node_s 结构通过指针last和end标识空闲内存,因此不能把2这块内存给拉进去空闲内存空间里面。
在这里插入图片描述
但是如果不释放小块内存,小块内存随着分配越来越大,系统的内存分配失败的概率越大。因此引入内存池重置。

当一个内存池被使用过一段时间后,可能会有一些内存块被分配出去,但是在后续的使用中,这些内存块可能已经不再需要了。此时,可以选择重置内存池,将所有已经分配的内存块释放回来,并重新初始化内存池的状态,使其恢复到最初的状态,以便重新利用这些内存空间。

void mp_reset_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h;
	struct mp_large_s *l;

	// 释放大块内存
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	pool->large = NULL;

	// 通过指针复位重置小块内存(不调用free归还内存)
	for (h = pool->head; h; h = h->next) {
		h->last = (unsigned char *)h + sizeof(struct mp_node_s);
	}

}

3.3 完整代码




#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <fcntl.h>



#define MP_ALIGNMENT       		32
#define MP_PAGE_SIZE			4096
#define MP_MAX_ALLOC_FROM_POOL	(MP_PAGE_SIZE-1)

// 按指定的对齐方式 alignment 对齐。
#define mp_align(n, alignment) (((n)+(alignment-1)) & ~(alignment-1))
#define mp_align_ptr(p, alignment) (void *)((((size_t)p)+(alignment-1)) & ~(alignment-1))


// 大块内存结构
struct mp_large_s {
	struct mp_large_s *next;	// 指向下一个大块内存
	void *alloc;				// 指向实际分配的大块内存
};

// 小块内存结构
struct mp_node_s {

	unsigned char *last;		// 指向内存池中已分配结点的末尾,即下次可分配内存结点的首地址
	unsigned char *end;			// 指向内存池的末尾
	
	struct mp_node_s *next;		// 指向下一个结点
	size_t failed;				// 当前的内存池分配失败的次数
};

// 内存池结构
struct mp_pool_s {

	size_t max;					// 内存池可分配的最大空间,超过的话用大块内存

	struct mp_node_s *current;	// 指向当前内存池
	struct mp_large_s *large;	// 指向大块内存链表

	struct mp_node_s head[0];	// 指向小块内存链表

};

struct mp_pool_s *mp_create_pool(size_t size);
void mp_destory_pool(struct mp_pool_s *pool);
void *mp_alloc(struct mp_pool_s *pool, size_t size);
void *mp_nalloc(struct mp_pool_s *pool, size_t size);
void *mp_calloc(struct mp_pool_s *pool, size_t size);
void mp_free(struct mp_pool_s *pool, void *p);

// 创建内存池
struct mp_pool_s *mp_create_pool(size_t size) {

	struct mp_pool_s *p;
	// posix_memalign 用于分配内存块,并保证所分配的内存块以指定的对齐方式进行对齐。
	// 参数:1)一个指向指针的指针,用于保存分配得到的内存块的地址。2)所需的内存对齐方式. 3)要分配的内存块的大小
	// 一开始并不知道大块内存具体大小,因此只需要分配:申请空间的大小 + 内存池结构体大小 + 小块内存结点
	int ret = posix_memalign((void **)&p, MP_ALIGNMENT, size + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s));
	if (ret) {
		return NULL;
	}
	
	p->max = (size < MP_MAX_ALLOC_FROM_POOL) ? size : MP_MAX_ALLOC_FROM_POOL;	// 设置小块内存可分配的最大空间
	p->current = p->head; 														// 设置当前指向的内存块
	p->large = NULL;															// 设置大块内存

	p->head->last = (unsigned char *)p + sizeof(struct mp_pool_s) + sizeof(struct mp_node_s);
	p->head->end = p->head->last + size;
	p->head->failed = 0;

	return p;

}

// 内存池销毁
void mp_destory_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h, *n;
	struct mp_large_s *l;

	// 释放大块内存
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	// 释放小块内存
	h = pool->head->next;
	while (h) {
		n = h->next;
		free(h);
		h = n;
	}

	// 释放内存池
	free(pool);

}

// 重置内存池
void mp_reset_pool(struct mp_pool_s *pool) {

	struct mp_node_s *h;
	struct mp_large_s *l;

	// 释放大块内存
	for (l = pool->large; l; l = l->next) {
		if (l->alloc) {
			free(l->alloc);
		}
	}

	pool->large = NULL;

	// 通过指针复位重置小块内存(不调用free归还内存)
	for (h = pool->head; h; h = h->next) {
		h->last = (unsigned char *)h + sizeof(struct mp_node_s);
	}

}

// 分配小块内存
static void *mp_alloc_block(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *h = pool->head;
	// 第一个小块内存中,可分配的内存大小
	size_t psize = (size_t)(h->end - (unsigned char *)h);
	
	// 申请内存,大小与第一个小块内存一样
	int ret = posix_memalign((void **)&m, MP_ALIGNMENT, psize);
	if (ret) return NULL;

	struct mp_node_s *p, *new_node, *current;

	// 初始化新的内存块
	new_node = (struct mp_node_s*)m;
	new_node->end = m + psize;
	new_node->next = NULL;
	new_node->failed = 0;

	// 将指针m移动到可分配内存的开始位置
	m += sizeof(struct mp_node_s);
	m = mp_align_ptr(m, MP_ALIGNMENT);
	new_node->last = m + size;

	// 从当前指向的内存块开始,寻找最后一个内存块链表的结点
	current = pool->current;
	for (p = current; p->next; p = p->next) {
		// 若某个小块内存连续5次都分配失败,则跳过这个小块内存,下次不再遍历它
		if (p->failed++ > 4) { //
			current = p->next;
		}
	}

	// 将新创建的内存块,尾插到小内存块链表
	p->next = new_node;

	// 更新pool->current指针,判断 current 是否为空?
	// 若非空,指向current指向的结点;若为空,代表之前所有的内存块都分配失败,则指向新的内存块
	pool->current = current ? current : new_node;

	return m;

}

// 分配大块内存
static void *mp_alloc_large(struct mp_pool_s *pool, size_t size) {

	void *p = malloc(size);
	if (p == NULL) return NULL;

	size_t n = 0;
	struct mp_large_s *large;

	// 遍历大块内存链表,找到可以挂载 p 的位置
	for (large = pool->large; large; large = large->next) {
		// 找到可以挂载的地方
		if (large->alloc == NULL) {
			large->alloc = p;
			return p;
		}
		// 若连续 4 次都没找到,就不再寻找了
		if (n ++ > 3) break;
	}

	// 创建一个新的大块内存结点,用来挂载p
	large = mp_alloc(pool, sizeof(struct mp_large_s));
	if (large == NULL) {
		free(p);
		return NULL;
	}

	// 将新创建的结点,头插到大块内存链表中
	large->alloc = p;
	large->next = pool->large;
	pool->large = large;

	return p;
}

// 对齐分配内存池
void *mp_memalign(struct mp_pool_s *pool, size_t size, size_t alignment) {

	void *p;
	
	int ret = posix_memalign(&p, alignment, size);
	if (ret) {
		return NULL;
	}

	struct mp_large_s *large = mp_alloc(pool, sizeof(struct mp_large_s));
	if (large == NULL) {
		free(p);
		return NULL;
	}

	large->alloc = p;
	large->next = pool->large;
	pool->large = large;

	return p;
}


// 内存分配
void *mp_alloc(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *p;

	// 1、申请小块内存
	if (size <= pool->max) {

		p = pool->current;

		do {
			// 遍历小块内存链表,寻找可用的空间分配内存
			m = mp_align_ptr(p->last, MP_ALIGNMENT);
			// 若当前结点的剩余空间足够分配
			if ((size_t)(p->end - m) >= size) {
				p->last = m + size;
				return m;
			}
			// 若当前内存块的剩余内存小于所需内存,则到下一个内存块中寻找
			p = p->next;
		} while (p);

		// 没找到合适的小块内存,则申请小块内存
		return mp_alloc_block(pool, size);
	}

	// 2、申请大块内存
	return mp_alloc_large(pool, size);
	
}

// 非对齐分配内存池
void *mp_nalloc(struct mp_pool_s *pool, size_t size) {

	unsigned char *m;
	struct mp_node_s *p;

	if (size <= pool->max) {
		p = pool->current;

		do {
			m = p->last;
			if ((size_t)(p->end - m) >= size) {
				p->last = m+size;
				return m;
			}
			p = p->next;
		} while (p);

		return mp_alloc_block(pool, size);
	}

	return mp_alloc_large(pool, size);
	
}

// 分配并清零内存空间。
void *mp_calloc(struct mp_pool_s *pool, size_t size) {
	// 在给定的内存池中进行内存分配
	void *p = mp_alloc(pool, size);
	if (p) {
		//将分配到的内存块清零
		memset(p, 0, size);
	}

	return p;
	
}

// 内存池释放,只针对大块内存
void mp_free(struct mp_pool_s *pool, void *p) {

	struct mp_large_s *l;
	for (l = pool->large; l; l = l->next) {
		if (p == l->alloc) {
			free(l->alloc);
			l->alloc = NULL;
			return ;
		}
	}
	
}


int main(int argc, char *argv[]) {

	int size = 1 << 12;

	struct mp_pool_s *p = mp_create_pool(size);

	int i = 0;
	for (i = 0;i < 10;i ++) {

		void *mp = mp_alloc(p, 512);
	}

	printf("mp_create_pool: %ld\n", p->max);
	printf("mp_align(123, 32): %d, mp_align(17, 32): %d\n", mp_align(24, 32), mp_align(17, 32));

	int j = 0;
	for (i = 0;i < 5;i ++) {

		char *pp = mp_calloc(p, 32);
		for (j = 0;j < 32;j ++) {
			if (pp[j]) {
				printf("calloc wrong\n");
			}
			printf("calloc success\n");
		}
	}

	printf("mp_reset_pool\n");
	for (i = 0;i < 5;i ++) {
		void *l = mp_alloc(p, 8192);
		mp_free(p, l);
	}
	mp_reset_pool(p);

	printf("mp_destory_pool\n");
	for (i = 0;i < 58;i ++) {
		mp_alloc(p, 256);
	}

	mp_destory_pool(p);

	return 0;

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/710320.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【我们一起60天准备考研算法面试(大全)-第一天 1/60(排序、进制)】【每天40分钟,我们一起用60天准备 考研408-数据结构(笔试)】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

与彭老师交流(北京大学 心理与认知科学学院)

交流&#xff1a;主要是了解人家在做什么对什么感兴趣&#xff0c;和让人家知道你在做什么对什么感兴趣&#xff0c;然后你觉得未来可以做什么有价值的事情。 1.老师做的方向里面有包含利用人工智能这一块的知识&#xff0c;我觉得我是可以做的&#xff0c;机理这一块的东西我不…

MySQL数据库复合查询

文章目录 一、多表查询二、自连接三、子查询1.单行子查询2.多行子查询3.多列子查询4.在from子句中使用子查询 四、合并查询 一、多表查询 在实际开发中&#xff0c;我们需要查询的数据往往会来自不同的表&#xff0c;所以需要进行多表查询。下面我们用一个简单的公司管理系统&…

启动游戏提示缺少(或丢失)xinput1_3.dll的解决办法

在我们打开游戏的或者软件的时候&#xff0c;电脑提示“找不到xinput1_3.dll&#xff0c;无法继续执行此代码”怎么办&#xff1f;相信困扰着不少小伙伴&#xff0c;我再在打开吃鸡的时候&#xff0c;然后花了一上午的时候时间研究&#xff0c;现在终于知道xinput1_3.dll文件是…

基于TCP协议实现多人在线的聊天室

基于TCP协议实现多人在线的聊天室 程序采用CS架构&#xff0c;功能支持注册&#xff0c;登录&#xff0c;退出&#xff0c;私聊&#xff0c;群聊&#xff0c;查看历史信息。属于Java Swing窗口程序。 技术&#xff1a;Java swing&#xff0c;mysql 开发工具&#xff1a;IDEA…

性能测试之全链路压测流量模型你了解多少

目录 前言 基于业务模型实现 基于流量录制回放 灰度分流 总结&#xff1a; 前言 现在全链路越来越火&#xff0c;各大厂商也纷纷推出了自己的全链路压测测试方案。特别是针对全链路压测流量模型&#xff0c;各家方案都有所不同。最近我看了一些这方面的资料&#xff0c;有…

万能的网关系统设计方案,一篇带走

本文准备围绕七个点来讲网关&#xff0c;分别是网关的基本概念、网关设计思路、网关设计重点、流量网关、业务网关、常见网关对比&#xff0c;对基础概念熟悉的朋友可以根据目录查看自己感兴趣的部分。 什么是网关 网关&#xff0c;很多地方将网关比如成门&#xff0c; 没什么…

mac pro m1:搭建zookeeper集群并设置开机自启

0. 引言 之前我们讲解过搭建zookeeper单节点&#xff0c;但在实际生产中&#xff0c;为了保证服务高可用&#xff0c;通常我们是采用集群模式。所以本次我们来实操集群模式的搭建 1. zk集群模式 zk可以作为注册中心和配置中心&#xff0c;常用在微服务各类组件的多节点服务治…

Tomcat优化及部署

目录 一、Tomcat概述 二、Tomcat核心组件 1.Web容器 2.servlet容器 3.JSP容器 三、Tomcat的功能组件 1.connector 2.container 3.service 四、Tomcat部署 1.关闭防火墙和安全机制 2.将安装 Tomcat 所需软件包传到/opt目录下 3.安装JDK 4.设置JDK环境变量 5.编写j…

链路性能测试中参数多样性方法分享

目录 业务无关量 随机数字 线程安全随机 随机字符串 业务相关量 随机相关量 上游接口获取 上游接口造数据 提前造数据 总结&#xff1a; 下面分享几种我工作中常用到的增加参数多样性的方法。 业务无关量 这个是最常用到的&#xff0c;当部分的接口参数对接下来的用…

机器人开发--EKF扩展卡尔曼滤波介绍

机器人开发--EKF卡尔曼滤波介绍 1 介绍1.1 概述KF (Kalman Filter)EKF (Extended Kalman Filter)UKF (Unscented Kalman Filter) 1.2 发展历史1.3 卡尔曼演化分支1.4 应用1.5 特点1.6 姿态估计问题 from 南叔先生1.7 EKF、PF、UKF 对比1.8 EKF 递归框架 2 理解機器人學&#xf…

使用Matplotlib画三维图

使用matplotlib画3D图&#xff1a; import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D# 创建X和Y的网格点 x np.linspace(-5, 5, 100) y np.linspace(-5, 5, 100) X, Y np.meshgrid(x, y)# 创建Z的网格点&#xff08;这里使用一…

nginx 配置m3u8播放视频

第一步nginx配置&#xff1a; 参考 csdn - CircleMouse Nginx配置搭建m3u8格式的视频播放服务 user www www; worker_processes auto; error_log /www/wwwlogs/nginx_error.log crit; pid /www/server/nginx/logs/nginx.pid; worker_rlimit_nofile 51200;stream {l…

提高情商的训练方法

在当今社会&#xff0c;情商已经成为了一个越来越受到重视的概念。情商指的是一个人在情感方面的智力水平&#xff0c;即情绪智商&#xff0c;包括了自我意识、自我管理、社交意识和关系管理等多个方面。而提高情商并非是天生的天赋&#xff0c;而是可以通过学习和实践获得的技…

自然语言处理从入门到应用——预训练模型总览:词嵌入的两大范式

分类目录&#xff1a;《自然语言处理从入门到应用》总目录 相关文章&#xff1a; 预训练模型总览&#xff1a;从宏观视角了解预训练模型 预训练模型总览&#xff1a;词嵌入的两大范式 预训练模型总览&#xff1a;两大任务类型 预训练模型总览&#xff1a;预训练模型的拓展 …

【论文解读系列】MLLM研究综述

A Survey on Multimodal Large Language Models 1 中国科大科技学院、认知智能国家重点实验室 2 腾讯优图实验室 MLLM目录 0. 摘要1. 引言2. 总览3. 方法3.1 多模态指令调谐3.1.1 引言3.1.2 前言(Preliminaries)3.1.3 模态对齐3.1.4 数据3.1.5 模态桥接3.1.6 评估 3.2 多模态…

深入理解 Golang: 网络编程

Go 中的 Epoll 关于计算机网络分层与 TCP 通信过程过程此处不再赘述。 考虑到 TCP 通信过程中各种复杂操作&#xff0c;包括三次握手&#xff0c;四次挥手等&#xff0c;多数操作系统都提供了 Socket 作为 TCP 网络连接的抽象。Linux -> Internet domain socket -> SOC…

layui中文、以及图标乱码解决方案

最终解决方案…手动对js文件中的中文&#xff0c;用unicode进行编码

修改 ChatGLM2-6B 自我认知的 Lora 微调教程

修改 ChatGLM2-6B 自我认知的 Lora 微调教程 0. 背景1. 部署微调项目2. 数据集说明3. 模型监督微调(Lora)4. 模型效果测试5. 导出微调模型6. 调用导出的模型 0. 背景 现在开始学习微调&#xff0c;主要学习 Lora 微调。 这次尝试了修改 ChatGLM2-6B 自我认知&#xff0c;文章…

2023.7.2-【for语言】:输入一个整数,并输入该整数对应个数的整数,求他们的和与平均值

程序&#xff1a; int a;int b0;int c;int sum0;double ave;printf("请输入待求整数的个数&#xff1a;");scanf("%d",&a);for (b 1; b<a; b){printf("整数%d&#xff1a;", b);scanf("%d", &c);sum c;}printf("以上…