PostgreSQL数据库缓冲区管理器——本地缓冲区管理

news2025/1/13 9:36:26

本地缓冲区管理器(local buffer manager)为temporary表(无需WAL-logged或checkpointed)进行快速缓冲区管理,API定义在src/backend/storage/buffer/localbuf.c中。

获取LocalBuffer

初始化

首先解释temp_buffers GUC参数,其用于设置session使用的temporary buffers的最大数量,对应的变量为num_temp_buffers。InitLocalBuffers函数用于初始化local buffer cache。因为大多数查询不会涉及local buffers,为缓冲区分配空间的工作延迟到需要时才进行,该函数仅仅创建buffer headers。如下图所示,本地缓冲区和共享缓冲区的层次结构类似,其初始化也类似,不同的是LocalBufferDescriptors的元素的buf_id是负数,原因在代码注释中也解释过了。
在这里插入图片描述

static void InitLocalBuffers(void) {
	int			nbufs = num_temp_buffers;
	HASHCTL		info;
	
	if (IsParallelWorker()) ereport(ERROR, (errcode(ERRCODE_INVALID_TRANSACTION_STATE), errmsg("cannot access temporary tables during a parallel operation"))); /* Parallel workers can't access data in temporary tables, because they have no visibility into the local buffers of their leader.  This is a convenient, low-cost place to provide a backstop check for that.  Note that we don't wish to prevent a parallel worker from accessing catalog metadata about a temp table, so checks at higher levels would be inappropriate. */

	/* Allocate and zero buffer headers and auxiliary arrays */
	LocalBufferDescriptors = (BufferDesc *) calloc(nbufs, sizeof(BufferDesc));
	LocalBufferBlockPointers = (Block *) calloc(nbufs, sizeof(Block));
	LocalRefCount = (int32 *) calloc(nbufs, sizeof(int32));
	if (!LocalBufferDescriptors || !LocalBufferBlockPointers || !LocalRefCount) ereport(FATAL,(errcode(ERRCODE_OUT_OF_MEMORY),errmsg("out of memory")));
	nextFreeLocalBuf = 0;
	
	for (int i = 0; i < nbufs; i++){ /* initialize fields that need to start off nonzero */
		BufferDesc *buf = GetLocalBufferDescriptor(i);
		/* negative to indicate local buffer. This is tricky: shared buffers
		 * start with 0. We have to start with -2. (Note that the routine
		 * BufferDescriptorGetBuffer adds 1 to buf_id so our first buffer id
		 * is -1.) */
		buf->buf_id = -i - 2;
		/* Intentionally do not initialize the buffer's atomic variable
		 * (besides zeroing the underlying memory above). That way we get
		 * errors on platforms without atomics, if somebody (re-)introduces
		 * atomic operations for local buffers. */
	}

	/* Create the lookup hash table */
	MemSet(&info, 0, sizeof(info));
	info.keysize = sizeof(BufferTag);
	info.entrysize = sizeof(LocalBufferLookupEnt);
	LocalBufHash = hash_create("Local Buffer Lookup Table",nbufs, &info, HASH_ELEM | HASH_BLOBS);
	if (!LocalBufHash) elog(ERROR, "could not initialize local buffer hash table");	
	NLocBuffer = nbufs; /* Initialization done, mark buffers allocated */
}

获取

GetLocalBufferStorage函数为local buffer分配内存(TopMemoryContext内存上下文的LocalBufferContext),该函数的思想是聚合我们的存储请求,以便内存管理器不会看到大量相对较小的请求。因为一旦在特定进程中创建了本地缓冲区,我们就永远不会返回它,所以用单独管理的块来加重memmgr的负担是没有意义的。The idea of this function is to aggregate our requests for storage so that the memory manager doesn’t see a whole lot of relatively small requests. Since we’ll never give back a local buffer once it’s created within a particular process, no point in burdening memmgr with separately managed chunks.
在这里插入图片描述

static Block GetLocalBufferStorage(void) {
	static char *cur_block = NULL;
	static int	next_buf_in_block = 0;
	static int	num_bufs_in_block = 0;
	static int	total_bufs_allocated = 0;
	static MemoryContext LocalBufferContext = NULL;

	char	   *this_buf;

	if (next_buf_in_block >= num_bufs_in_block) {					
		if (LocalBufferContext == NULL) LocalBufferContext = AllocSetContextCreate(TopMemoryContext, "LocalBufferContext", ALLOCSET_DEFAULT_SIZES); /* We allocate local buffers in a context of their own, so that the space eaten for them is easily recognizable in MemoryContextStats output.  Create the context on first use. */
		
 		int			num_bufs; /* Need to make a new request to memmgr */		
		num_bufs = Max(num_bufs_in_block * 2, 16); /* Start with a 16-buffer request; subsequent ones double each time */		
		num_bufs = Min(num_bufs, NLocBuffer - total_bufs_allocated); /* But not more than what we need for all remaining local bufs */		
		num_bufs = Min(num_bufs, MaxAllocSize / BLCKSZ); /* And don't overflow MaxAllocSize, either */
		cur_block = (char *) MemoryContextAlloc(LocalBufferContext, num_bufs * BLCKSZ);
		
		next_buf_in_block = 0;
		num_bufs_in_block = num_bufs;
	}

	/* Allocate next buffer in current memory block */
	this_buf = cur_block + next_buf_in_block * BLCKSZ;
	next_buf_in_block++;
	total_bufs_allocated++;

	return (Block) this_buf;
}

LocalBufferAlloc

本地缓冲区初始化的调用链为ReadBufferExtended/ReadBufferWithoutRelcache --> ReadBuffer_common --> LocalBufferAlloc --> InitLocalBuffers。ReadBufferExtended—返回一个缓冲区,该缓冲区包含请求关系的请求块。如果请求的blknum是P_NEW,则扩展关系文件并分配一个新块。(调用者负责确保只有一个后端同时尝试扩展关系!)ReadBufferExtended – returns a buffer containing the requested block of the requested relation. If the blknum requested is P_NEW, extend the relation file and allocate a new block. (Caller is responsible for ensuring that only one backend tries to extend a relation at the same time!) 返回:包含读取的块的缓冲区的缓冲区编号。返回的缓冲区已固定。不返回错误,而是返回elog。假设调用此函数时,reln已经打开。Returns: the buffer number for the buffer containing the block read. The returned buffer has been pinned. Does not return on error — elog’s instead. Assume when this function is called, that reln has been opened already.
ReadBufferMode形参取值:在RBM_NORMAL模式下,从磁盘读取页面,并验证页面标题。如果页头无效,将引发错误。(但请注意,全零页面被视为“有效”;请参阅PageIsVerifiedExtended()。)In RBM_NORMAL mode, the page is read from disk, and the page header is validated. An error is thrown if the page header is not valid. (But note that an all-zero page is considered “valid”; see PageIsVerifiedExtended().) RBM_ZERO_ON_ERROR与正常模式类似,但如果页面标题无效,则页面将归零,而不是抛出错误。这适用于非关键数据,调用方准备修复错误。RBM_ZERO_ON_ERROR is like the normal mode, but if the page header is not valid, the page is zeroed instead of throwing an error. This is intended for non-critical data, where the caller is prepared to repair errors.在RBM_ZERO_AND_LOCK模式下,如果页面已经不在缓冲区缓存中,则会用零填充,而不是从磁盘读取。当调用者从头开始填充页面时很有用,因为这样可以节省I/O,并避免在磁盘上的页面有损坏的页头时出现不必要的故障。页面返回时被锁定,以确保调用者有机会在页面对其他人可见之前对其进行初始化。注意:不要使用此模式读取超出关系当前物理EOF的页面;当修改并写出页面时,这可能会导致md.c.出现问题。但P_NEW是可以的。In RBM_ZERO_AND_LOCK mode, if the page isn’t in buffer cache already, it’s filled with zeros instead of reading it from disk. Useful when the caller is going to fill the page from scratch, since this saves I/O and avoids unnecessary failure if the page-on-disk has corrupt page headers. The page is returned locked to ensure that the caller has a chance to initialize the page before it’s made visible to others. Caution: do not use this mode to read a page that is beyond the relation’s current physical EOF; that is likely to cause problems in md.c when the page is modified and written out. P_NEW is OK, though. RBM_ZERO_AND_CLEANUP_LOCK与RBM_ZEOR_AND_LOCK相同,但在页面上获得清理强度锁。RBM_ZERO_AND_CLEANUP_LOCK is the same as RBM_ZERO_AND_LOCK, but acquires a cleanup-strength lock on the page. RBM_NORMAL_NO_LOG模式的处理方式与RBM_NORAL相同。RBM_NORMAL_NO_LOG mode is treated the same as RBM_NORMAL here.
如果策略不为NULL,则使用非默认缓冲区访问策略。If strategy is not NULL, a nondefault buffer access strategy is used.

Buffer ReadBufferExtended(Relation reln, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy) {
	bool		hit;	
	RelationOpenSmgr(reln); /* Open it at the smgr level if not already done */
	
	if (RELATION_IS_OTHER_TEMP(reln)) ereport(ERROR,(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot access temporary tables of other sessions"))); /* Reject attempts to read non-local temporary relations; we would be likely to get wrong data since we have no visibility into the owning session's local buffers. */	
	pgstat_count_buffer_read(reln); /* Read the buffer, and update pgstat counters to reflect a cache hit or miss. */
	
	Buffer buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence, forkNum, blockNum, mode, strategy, &hit);
	
	if (hit) pgstat_count_buffer_hit(reln);
	return buf;
}							 

ReadBufferWithoutRelcache函数和ReadBufferExtended相似,但是不需要relcache条目。

Buffer ReadBufferWithoutRelcache(RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode, BufferAccessStrategy strategy) {
	bool		hit;
	SMgrRelation smgr = smgropen(rnode, InvalidBackendId);
	return ReadBuffer_common(smgr, RELPERSISTENCE_PERMANENT, forkNum, blockNum, mode, strategy, &hit);
}

请添加图片描述

LocalBufferAlloc函数为给定的relation的数据页查找或创建一个local buffer。该API类似于bufmgr.c中的BufferAlloc,不同的是不需要任何locking操作(API is similar to bufmgr.c’s BufferAlloc, except that we do not need to do any locking since this is all local),IO_IN_PROGRESS不会设置,当前仅支持默认访问策略default access strategy。LocalBufferAlloc函数执行流程如下:调用InitLocalBuffers初始化初始化Local Buffer;初始化BufferTag;查询LocalBufHash,判定需要的buffer已经存在;如果存在,通过LocalBufferLookupEnt中存储的BufferDesc的序号,找到LcoalBufferDescriptors数组中的元素,这里使用了LocalRefCount来模拟shared buffer中的PinBuffer,更新计数值;如果不存在,需要获取新的Buffer,这里使用clock sweep algorithm算法,获取到的缓冲区未被引用,但它可能仍然是脏的。如果是这样的话,请在重复使用之前将其写出来。

BufferDesc *LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum, bool *foundPtr){
	BufferTag	newTag;			/* identity of requested block */
	INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
	
	if (LocalBufHash == NULL) InitLocalBuffers(); /* Initialize local buffers if first request in this session */

	/* See if the desired buffer already exists */
	LocalBufferLookupEnt *hresult = (LocalBufferLookupEnt *)hash_search(LocalBufHash, (void *) &newTag, HASH_FIND, NULL);
	
	BufferDesc *bufHdr; int			b; uint32		buf_state;
	if (hresult){
		b = hresult->id;
		bufHdr = GetLocalBufferDescriptor(b);
		buf_state = pg_atomic_read_u32(&bufHdr->state);

		/* this part is equivalent to PinBuffer for a shared buffer */
		if (LocalRefCount[b] == 0){
			if (BUF_STATE_GET_USAGECOUNT(buf_state) < BM_MAX_USAGE_COUNT){
				buf_state += BUF_USAGECOUNT_ONE;
				pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state);
			}
		}
		LocalRefCount[b]++;
		ResourceOwnerRememberBuffer(CurrentResourceOwner,BufferDescriptorGetBuffer(bufHdr));
		if (buf_state & BM_VALID) *foundPtr = true;
		else{/* Previous read attempt must have failed; try again */
			*foundPtr = false;
		}
		return bufHdr;
	}


	/* Need to get a new buffer.  We use a clock sweep algorithm (essentially the same as what freelist.c does now...) */
	int trycounter = NLocBuffer;
	for (;;){
		b = nextFreeLocalBuf;
		if (++nextFreeLocalBuf >= NLocBuffer) nextFreeLocalBuf = 0; // 到数组尾部,循环到数组首部
		bufHdr = GetLocalBufferDescriptor(b);

		if (LocalRefCount[b] == 0){  // 如果RefCount为0,说明可能可以使用
			buf_state = pg_atomic_read_u32(&bufHdr->state);
			if (BUF_STATE_GET_USAGECOUNT(buf_state) > 0){
				buf_state -= BUF_USAGECOUNT_ONE;  // 减少bufDesc中state计数
				pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state);
				trycounter = NLocBuffer;
			}else{/* Found a usable buffer */ // bufDesc中state计数为零
				LocalRefCount[b]++;
				ResourceOwnerRememberBuffer(CurrentResourceOwner,BufferDescriptorGetBuffer(bufHdr));
				break;
			}
		}
		else if (--trycounter == 0) ereport(ERROR,(errcode(ERRCODE_INSUFFICIENT_RESOURCES),errmsg("no empty local buffer available")));
	}
	
	if (buf_state & BM_DIRTY){ /* this buffer is not referenced but it might still be dirty. if that's the case, write it out before reusing it! */
		SMgrRelation oreln;
		Page		localpage = (char *) LocalBufHdrGetBlock(bufHdr);
		
		oreln = smgropen(bufHdr->tag.rnode, MyBackendId); /* Find smgr relation for buffer */
		PageSetChecksumInplace(localpage, bufHdr->tag.blockNum);
		/* And write... */
		smgrwrite(oreln, bufHdr->tag.forkNum, bufHdr->tag.blockNum, localpage, false);
		
		/* Mark not-dirty now in case we error out below */
		buf_state &= ~BM_DIRTY;
		pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state);

		pgBufferUsage.local_blks_written++;
	}

	/* lazy memory allocation: allocate space on first use of a buffer. */
	if (LocalBufHdrGetBlock(bufHdr) == NULL){ /* Set pointer for use by BufferGetBlock() macro */
		LocalBufHdrGetBlock(bufHdr) = GetLocalBufferStorage();
	}

	/* Update the hash table: remove old entry, if any, and make new one. */
	if (buf_state & BM_TAG_VALID){
		hresult = (LocalBufferLookupEnt *)hash_search(LocalBufHash, (void *) &bufHdr->tag,HASH_REMOVE, NULL);
		if (!hresult) elog(ERROR, "local buffer hash table corrupted");
		/* mark buffer invalid just in case hash insert fails */
		CLEAR_BUFFERTAG(bufHdr->tag);
		buf_state &= ~(BM_VALID | BM_TAG_VALID);
		pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state);
	}
    bool		found;
	hresult = (LocalBufferLookupEnt *) hash_search(LocalBufHash, (void *) &newTag, HASH_ENTER, &found);
	if (found)					/* shouldn't happen */
		elog(ERROR, "local buffer hash table corrupted");
	hresult->id = b;

	/* it's all ours now. */
	bufHdr->tag = newTag;
	buf_state &= ~(BM_VALID | BM_DIRTY | BM_JUST_DIRTIED | BM_IO_ERROR);
	buf_state |= BM_TAG_VALID;
	buf_state &= ~BUF_USAGECOUNT_MASK;
	buf_state += BUF_USAGECOUNT_ONE;
	pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state);

	*foundPtr = false;
	return bufHdr;
}

预取

LocalPrefetchBuffer函数用于initiate异步读取relation数据块,其调用堆栈如下PrefetchBuffer --> LocalPrefetchBuffer。

void LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum) {
#ifdef USE_PREFETCH
	BufferTag	newTag;			/* identity of requested block */
	LocalBufferLookupEnt *hresult;

	INIT_BUFFERTAG(newTag, smgr->smgr_rnode.node, forkNum, blockNum);
	
	if (LocalBufHash == NULL) InitLocalBuffers(); /* Initialize local buffers if first request in this session */

	/* See if the desired buffer already exists */
	hresult = (LocalBufferLookupEnt *) hash_search(LocalBufHash, (void *) &newTag, HASH_FIND, NULL);
	if (hresult){ /* Yes, so nothing to do */ return; }

	/* Not in buffers, so initiate prefetch */
	smgrprefetch(smgr, forkNum, blockNum);
#endif							/* USE_PREFETCH */
}

删除Local Buffer

MarkLocalBufferDirty函数用于标记local buffer dirty,其调用堆栈如下MarkBufferDirty/MarkBufferDirtyHint --> MarkLocalBufferDirty。

void MarkLocalBufferDirty(Buffer buffer) {
	int			bufid = -(buffer + 1);;
	BufferDesc *bufHdr = GetLocalBufferDescriptor(bufid);

	uint32 buf_state = pg_atomic_read_u32(&bufHdr->state);
	if (!(buf_state & BM_DIRTY)) pgBufferUsage.local_blks_dirtied++;

	buf_state |= BM_DIRTY;
	pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state);
}

DropRelFileNodeLocalBuffers函数从缓冲池中删除具有块号>=firstDelBlock的指定关系的所有页面。(特别是,当firstDelBlock=0时,所有页面都会被删除。)脏页面只会被删除,而不必先将它们写出来。因此,这是不可回滚的,因此只能极其小心地使用!This function removes from the buffer pool all the pages of the specified relation that have block numbers >= firstDelBlock. (In particular, with firstDelBlock = 0, all pages are removed.) Dirty pages are simply dropped, without bothering to write them out first. Therefore, this is NOT rollback-able, and so should be used only with extreme caution! 调用堆栈如下DropRelFileNodeBuffers --> DropRelFileNodeLocalBuffers

void DropRelFileNodeLocalBuffers(RelFileNode rnode, ForkNumber forkNum, BlockNumber firstDelBlock) {
	for (int i = 0; i < NLocBuffer; i++){
		BufferDesc *bufHdr = GetLocalBufferDescriptor(i);		
		uint32		buf_state = pg_atomic_read_u32(&bufHdr->state);
		
		LocalBufferLookupEnt *hresult;
		if ((buf_state & BM_TAG_VALID) && RelFileNodeEquals(bufHdr->tag.rnode, rnode) &&
			bufHdr->tag.forkNum == forkNum && bufHdr->tag.blockNum >= firstDelBlock){
			
			if (LocalRefCount[i] != 0) elog(ERROR, "block %u of %s is still referenced (local %u)",bufHdr->tag.blockNum,relpathbackend(bufHdr->tag.rnode, MyBackendId,bufHdr->tag.forkNum), LocalRefCount[i]);
			
			/* Remove entry from hashtable */
			hresult = (LocalBufferLookupEnt *)hash_search(LocalBufHash, (void *) &bufHdr->tag,HASH_REMOVE, NULL);
			if (!hresult)		/* shouldn't happen */
				elog(ERROR, "local buffer hash table corrupted");
				
			/* Mark buffer invalid */
			CLEAR_BUFFERTAG(bufHdr->tag);
			buf_state &= ~BUF_FLAG_MASK;
			buf_state &= ~BUF_USAGECOUNT_MASK;
			pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state);
		}
	}
}

DropRelFileNodeAllLocalBuffers function removes from the buffer pool all pages of all forks of the specified relation. 函数调用堆栈DropRelFileNodesAllBuffers --> DropRelFileNodeAllLocalBuffers。

void DropRelFileNodeAllLocalBuffers(RelFileNode rnode) {
	for (int i = 0; i < NLocBuffer; i++){
		BufferDesc *bufHdr = GetLocalBufferDescriptor(i);
		LocalBufferLookupEnt *hresult;
		uint32		buf_state = pg_atomic_read_u32(&bufHdr->state);
		if ((buf_state & BM_TAG_VALID) && RelFileNodeEquals(bufHdr->tag.rnode, rnode)){
			if (LocalRefCount[i] != 0) elog(ERROR, "block %u of %s is still referenced (local %u)",bufHdr->tag.blockNum, relpathbackend(bufHdr->tag.rnode, MyBackendId,bufHdr->tag.forkNum), LocalRefCount[i]);
			/* Remove entry from hashtable */
			hresult = (LocalBufferLookupEnt *)hash_search(LocalBufHash, (void *) &bufHdr->tag,HASH_REMOVE, NULL);
			if (!hresult)/* shouldn't happen */ elog(ERROR, "local buffer hash table corrupted");
			
			/* Mark buffer invalid */
			CLEAR_BUFFERTAG(bufHdr->tag);
			buf_state &= ~BUF_FLAG_MASK;
			buf_state &= ~BUF_USAGECOUNT_MASK;
			pg_atomic_unlocked_write_u32(&bufHdr->state, buf_state);
		}
	}
}

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

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

相关文章

Servlet学习日志

Hello Web Web World Wide Web 全球广域网->也称为万维网 是一种基于超文本和HTTP协议的分布式应用架构 HTML&#xff08;HyperText Mark-up Language&#xff09;:用于链接网络上的信息 HTTP协议&#xff08;HyperText Transfer Protocol&#xff09;:规定了服务端与客户…

国际海运流程有哪些,国际海运物流哪家比较好

国际海运的优点是运输费划算&#xff0c;并且可以走的货物类目多&#xff0c;合适大物件、大批的长距离运送。进出境运输工具的负责人、进出境物品的所有人或者他们的代理人向海关办理货物、物品或运输工具进出境手续及相关海关事务的过程&#xff0c;包括向海关申报、交验单据…

分析 SpringBoot 底层机制【Tomcat 启动分析 +Spring 容器初始化 +Tomcat 如何关联 Spring 容器 】

目录 一.搭建 SpringBoot 底层机制开发环境 1.pom.xml文件配置 2.springboot主程序MainApp.java 3.启动项目&#xff0c;然后我们准备开始思考 4.开始思考 底层机制分析: 仍然是 我们实现 Spring 容器那一套机制 IO/文件扫描注解反射 集合映射集合映射 二.源码分析: Spri…

DeepLab V2学习笔记

DeepLab V2遇到的相关问题以及解决方法对于DeepLab V1的优化模型总结补充的一个小知识点ASPPLearning rate policy实验结果消融实验实验结果遇到的相关问题以及解决方法 和DeepLab V1中遇到的问题几乎没有什么太大的差别&#xff0c;在文章的引言部分作者提出了在语义分割中遇…

小程序商城拓展中小商户“线上时间”,系统化思维

在数字经济时代初期&#xff0c;新一代信息基础设施建设仍有待完善&#xff0c;在拓展“互联网”应用场景中应不断缩小“数字鸿沟”&#xff0c;逐渐向产业和行业下沉&#xff0c;让互联网巨头和中小微商户共享数字技术的红利&#xff0c;也因此将推动包括传统商户在内的数字化…

政务系统信息网络安全的风险评估

在当今信息时代的大形势下&#xff0c;互联网从普及到飞速发展&#xff0c;政务系统信息网络安全的问题也日益被重视起来。一方面&#xff0c;人们应该了解到政务系统信息网络安全的重要性&#xff1b;另一方面&#xff0c;人们对政务系统信息网络安全风险评估工作有所了解。有…

公厕智能感应皂液盒,无液手机信息提醒

传统式的洗手液一按一压。有时候经常控制不住量&#xff0c;一不小心就容易按多&#xff0c;弄得洗手台乱七八糟&#xff0c;满身也是黏哒哒的&#xff0c;特别让人糟心。刚做完饭&#xff0c;满手油腻&#xff0c;或是刚上完厕所&#xff0c;去按洗手液泵势必会弄脏它。 所以智…

【Python+Appium】开展自动化测试(一)

目录 前言 一&#xff0c;安装JDK&#xff0c;配置JDK环境变量 1&#xff0c;下载jdk 2&#xff0c;安装jdk 3&#xff0c;配置jdk环境变量 二&#xff0c;安装Android SDK&#xff0c;配置Android SDK环境 1&#xff0c;下载Android SDK 2&#xff0c;双击SDK Manager…

【日常记录】解决‘GLIBC_2.34‘ not found,并且gcc制定glibc版本编译

小荣的日常记录 &#x1f525;系列专栏&#xff1a;日常记录 &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月21日&#x1f334; &#x1f36d;作者水平很有限&#xff0c;如果发现错…

DETR训练自己数据集心得

对QKV的理解&#xff0c;先废一下话... 计算流程参考&#xff1a;https://zhuanlan.zhihu.com/p/82312421 给定一组query&#xff0c;和当前输入样本input&#xff08;每个样本都有各自的key&#xff09;&#xff0c;经过空间变化后input→query。 计算query和key之间的相关…

深度学习与总结JVM专辑(二):垃圾回收基础(图文+代码)

垃圾回收基础概念什么是垃圾回收为什么要进行垃圾回收垃圾什么是垃圾你是垃圾吗&#xff1f;让我用算法来测测你。引用计数算法可达性分析算法对象引用对象&#xff0c;真的死了吗方法区回收废弃常量无用类垃圾回收算法回收类型分代收集理论标记清除算法&#xff08;Mark-Sweep…

【Hack The Box】linux练习-- Irked

HTB 学习笔记 【Hack The Box】linux练习-- Irked &#x1f525;系列专栏&#xff1a;Hack The Box &#x1f389;欢迎关注&#x1f50e;点赞&#x1f44d;收藏⭐️留言&#x1f4dd; &#x1f4c6;首发时间&#xff1a;&#x1f334;2022年11月17日&#x1f334; &#x1f36…

[附源码]计算机毕业设计JAVA基于ssm的电子网上商城

[附源码]计算机毕业设计JAVA基于ssm的电子网上商城 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM my…

基于FPGA的PID控制器开发与实现

欢迎订阅《FPGA学习入门100例教程》、《MATLAB学习入门100例教程》 效果预览: 目录 一、理论基础 二、核心程序 三、测试结果

数据结构与算法之顺序表详解

标题&#xff1a;猜数字小游戏 作者&#xff1a;Ggggggtm 寄语&#xff1a;与其忙着诉苦&#xff0c;不如低头赶路&#xff0c;奋路前行&#xff0c;终将遇到一番好风景 文章目录&#xff1a; 一、顺序表的概念与结构 1、线性表的解释 2、顺序表概念解释 二、顺序表的思路及代码…

ERP系统三种部署方式的区别

ERP系统被认为是一种 "企业应用程序"&#xff0c;指的是为满足企业的软件需求和提高业务绩效而设计的软件。今天有许多不同的ERP系统可供使用&#xff0c;其范围很广&#xff0c;取决于企业的规模、功能和需求。ERP系统的类型根据其部署方式划分&#xff0c;包括云ER…

二、Redis分布式锁

一、什么是分布式锁 分布式锁是一种跨进程的&#xff0c;跨机器节点的一种互斥锁。保证在多个机器节点对共享资源访问的一个排他性。 分布式与单机情况下最大的不同在于分布式锁是多进程的而单机锁是单进程多线程的。 二、为什么需要分布式锁 与分布式锁相对就的是单机锁&…

BHQ-1 amine,1308657-79-5,BHQ染料通过FRET和静态猝灭的组合工作

英文名称&#xff1a;BHQ-1 amine 中文名称&#xff1a;BHQ-1 氨基 CAS&#xff1a;1308657-79-5 外观&#xff1a;深紫色粉末 分子式&#xff1a;C25H29N7O3 分子量&#xff1a;475.55 结构式&#xff1a; 溶解性&#xff1a;溶于大部分有机溶剂&#xff0c;溶于水 储存…

luffy-(13)

内容概览 支付宝支付介绍支付宝支付二次封装订单相关表设计生成订单接口支付前端支付宝回调接口 支付宝支付介绍 """ 项目中有需要在线支付功能,可以使用支付宝支付(沙箱环境)微信支付(需要有备案过的域名)云闪付我们的项目以支付宝支付为例支付流程使用官方…

【动手学深度学习】关于“softmax回归的简单实现”报错的解决办法(含源代码)

目录&#xff1a;关于“softmax回归的简单实现”报错的解决办法一、前言二、实现步骤2.1 导包2.2 初始化模型参数2.3 重新审视Softmax的实现2.4 优化算法2.5 训练2.6 源代码三、问题出现四、问题的解决五、再跑代码六、改正后的函数源代码一、前言 在之前的学习中&#xff0c;…