[SGI STL]空间配置器--内存管理

2023-11-02

[SGI STL]系列文章前言

       废话不多说,读侯捷的SGI STL源码分析目的有三个:

1,接触c++不久就开始跟STL打交道,一直有个好奇心,这么强大的库到底是谁、咋实现的?;

2,不熟悉实现就用不好STL,所以想更好的应用STL,就有必要一探其底层驱动;

3,引用林语堂先生的一句话:“只用一样东西,不明白它的道理,实在不高明”;


目录

1,如何使用空间适配器

2,一个标准的空间配置器

3,SGI STL 空间配置器架构

4,构造和析构的基本工具:construct()和destroy()

5,空间的配置与释放,alloc

5.1 第一级配置器 __malloc_alloc_template

5.2 第二级配置器 __default_alloc_template

5.2.1 freelist

5.2.2 二级配置器中的空间配置函数allocator()

5.2.3 空间释放函数deallocate()

5.2.4 内存池(mem pool)

5.2.5 第二级配置器总的流程框图

6 内存基本处理工具

7 小结


1,如何使用空间适配器

其实以运用STL的角度来看,完全可以忽略空间适配器,因为每个容器都是通过默认参数指定好了allocator,通过查看vector的声明可以看出:

template<class _Ty,class _Alloc = allocator<_Ty> >
	class vector
{
//...
}

如下代码中的vector没有指定allocator,默认的allocator会自动根据你传入的元素,调整内存空间:

#include <vector>

void main()
{
	std::vector<int> vecTemp;
	for (int i = 0;i<10;i++)
	{
		vecTemp.push_back(i);
	}

	getchar();
}

其实,完整的vecTemp声明应该是 vetor<int, allocator<int>> vecTemp。

假如我们自定义了将内存分配指向磁盘或者其他存储介质空间的allocator,那么只要在声明时传入设计好的allocator,不再使用默认的allocator就行了。

那么问题来了,怎么样才能设计一个allocator呢?继续看~

2,一个标准的空间配置器

首先,设计一个空间配置器需要包含什么接口呢?我们从如下的例子引入:

	class Foo{...};
	Foo* pFoo = new Foo;//< 第一阶段,干了俩事:1,配置内存 2,在配置好的内存上构造对象
	delete pFoo; //< 第二阶段,也干了俩事:1,析构对象 2,释放内存

所以,一个allocator至少要包含四个功能:申请内存、构造对象、析构对象、释放内存。

其次,我可以很负责人的告诉你,如果你的allocator只包含上述四个功能,肯定无法再STL中运用^_^。因为STL对allocator的组成已经规定好了,即STL规范。那么STL中的allocator相关的规范是啥呢?我们通过一个符合STL标准的allocator(主要参考书中的JJ::allocator,略有修改)来说明:

namespace JJ 
{
	template <class T>
	class allocator
	{
	public:
		//< 七个typedef主要是为了迭代器的类型萃取,迭代器章节会提到
		typedef T		value_type;
		typedef T*		pointer;
		typedef const T*	const_pointer;
		typedef T&		reference;
		typedef const T&	const_reference;
		typedef size_t		size_type;
		typedef ptrdiff_t	difference_type;

		//< 成员模板 rebind
		//< 定义了一个associated type other,other也是一个allocator的实例,但是负责管理的对象类型与T不同
		//< 具体可以参考https://blog.csdn.net/qq100440110/article/details/50198789
		template <class U>
		struct rebind
		{
			typedef allocator<U> other;
		};

		//内存申请 直接使用new
		pointer allocate(size_type n, const void* hint = 0)
		{
			T* tmp = (T*)(::operator new((size_t)(n * sizeof(T))));
			if (tmp == 0)
				cerr << "out of memory" << endl;

			return tmp;
		}

		//构造函数 使用placement_new 在p处构造T1
		void construct(pointer p, const T& value)
		{
			new(p) T1(value);
		}

		//析构函数
		void destroy(pointer p)
		{
			p->~T();
		}

		//释放内存  直接使用delete
		void deallocate(pointer p)
		{
			::operator delete(p);
		}

		//取地址
		pointer address(reference x)
		{
			return (pointer)&x;
		}

		//返回const对象的地址
		const_pointer const_address(const_reference x)
		{
			return (const_pointer)&x;
		}

		//可成功配置的最大量
		size_type max_size() const
		{
			return size_type(UINT_MAX / sizeof(T));
		}
	};
}// NAMESPACE_JJ_END

这样,我们设计的第一个allocator完成了,就可以在实际中使用了:

	int ia[5] = { 1,2,3,4,5 };
	vector<int, JJ::allocator<int> > vec(ia, ia + 5);

3,SGI STL 空间配置器架构

有人可能会想,既然设计一个空间配置器这么简单,STL的多个毛啊,为啥它的这么NB。其实,STL的空间配置器不只多个毛,是多很多毛,不是NB,而是很NB,从这就能看出来王者与青铜的差别了,膜拜之~

由于一个内存配置与释放操作通常分两个阶段(见2中的例子),为了精密分工,STL allocator将这两个阶段的操作区分开来:

1,内存配置由alloc::allocate()负责,内存释放由alloc::deallocate()负责;

2,对象构造由::construct()负责,对象析构由::destroy()负责。

其实,对于内存配置和释放还有个allocator::allocate()和allocator::deallocate(),这是SGI定义的符合部分STL标准的配置器,但由于效率不佳,不推荐使用。其实它就是对::operator new和::operator delete做了一层薄薄的封装。

思维导图如下:

书中原图:

毋庸置疑,<stl_construt.h>和<stl_alloc.h>是STL空间配置器的重头菜,我们在这里分别介绍。

4,构造和析构的基本工具:construct()和destroy()

先上一张书中的construt()和destroy()示意图,对照着图就很容易理解了:

首先,对于construct()来说很简单了,就是接受一个指针p和一个初值value,用途就是将初值设定到指针所致的空间上,可以通过placement new来完成。

template<class T1, class T2>
void construct(T1 *p, const T2 &value)
{
	new(p) T1(value);
}

其次,从图中可以看出destroy()有两个版本:

第一个版本:接受一个指针(图中的第四个),准备将所指之物析构掉。这很简单,直接调用析构函数即可。

template<class T>
void destroy(T *ptr)
{
	ptr->~T();
}

第二个版本:接受一个迭代器区间,准备将这个范围内的对象析构掉。

再讲这个版本的destroy()之前,讲一下trivial destructor:如果不定义析构函数,而是使用系统自带的,也就是析构函数没什么作用,那么这个析构函数称为trivial destructor。

这里提现了STL作者的设计亮点,他不是直接调用每个对象的析构,而是首先确定每个对象是否有non_rivial destructor(即自己定义了析构函数)。如果有,则调用对象析构,如果没有,就什么也不做结束。

反正思路就是上面写的,具体可以看一下书上的代码,至于每个对象是否有non_rivial destructor的判断,则用到了_type_traits<T>,会在后面讲述。

图中的第二个和第三个是第二个版本的char*和wchar*的特化。

以上就是关于construt()和destroy()的所有内容,其实还是挺简单的。

5,空间的配置与释放,alloc

这一节我觉得是整个SGI STL空间配置器的核心。

设计者设计了两个配置器,准确的说是两级配置器,两个配置器相辅相成,相互配合最终完成空间的配置。

第一级配置器直接使用malloc()和free(),第二级则视情况采取不同的策略。而分界点是配置的内存是否大于128B,大于就用第一级,小于等于则通过第二级访问复杂的memory pool整理方式。

通过是否定义_USE_MALLOC宏,来设定是只打开第一级还是同时打开第一级与第二级。SGI STL没定义那个宏,也就是同时开放一、二级。

5.1 第一级配置器 __malloc_alloc_template

先说一下整体的思路。就像上图所说的,这个配置器中的allocator()直接调用C中的malloc(),reallocator()直接调用C中的realloc()。如果配置成功,则返回指针,如果不成功则调用out of memory处理;deallocator()直接调用free()。

out of memory主要调用用户设置的__malloc_alloc_oom_handler,这个可以通过模拟C++中的set_new_handler()的set_malloc_handler()来设定。如果用户指定了,则循环调用这个handler,直到分配到内存,如果没定义,则抛bad_alloc异常。

具体代码如下:

#if 0
#include<new>
#define __THROW_BAD_ALLOC throw bad_alloc
#elif !defined(__THROW_BAD_ALLOC)
//#include<iostream.h>
#define __THROW_BAD_ALLOC cout<<"Out Of Memory."<<endl; exit(1)
#endif

//inst完全没用
template<int inst>
class __malloc_alloc_template
{
private:
	//以下用来处理内存不足的情况;oom:out of memory
	static void * oom_malloc(size_t n); 
	static void * oom_realloc(void *p, size_t n);
	static void(*__malloc_alloc_oom_handler)();

public:
	static void* allocate(size_t n)
	{
		void *result = malloc(n); //< 直接调用malloc()
		if (result == 0)
			result = oom_malloc(n); //< 分配失败调用oom_malloc()
		return result;
	}

	static void  deallocate(void *p, size_t)
	{
		free(p); //< 直接调用free()
	}

	static void* reallocate(void *p, size_t old_sz, size_t new_sz)
	{
		void *result = realloc(p, new_sz); //< 直接调用C中的realloc()
		if (0 == result)
			result = oom_realloc(p, new_sz);  //< 分配失败调用oom_realloc
		return result;
	}

	//模拟C++中的set_new_handler(),也就是通过这个函数指针来指定自己的out-of-memory操作
	static void(* set_malloc_handler(void(*f)()))()
};

// 初值为0,客户端指定
template<int inst>
void(*__malloc_alloc_template<inst>::__malloc_alloc_oom_handler)() = 0;

//如果指定了 __malloc_alloc_oom_handler,则循环调用,直到分配到内存,否则抛异常
template<int inst>
void* __malloc_alloc_template<inst>::oom_malloc(size_t n)
{
	void(*my_malloc_handler)();
	void *result;
	for (;;)
	{
		my_malloc_handler = __malloc_alloc_oom_handler;
		if (0 == my_malloc_handler)
		{
			__THROW_BAD_ALLOC;
		}
		(*my_malloc_handler)();
		result = malloc(n);
		if (result)
			return result;
	}
}

//如果指定了 __malloc_alloc_oom_handler,则循环调用,直到分配到内存,否则抛异常
template<int inst>
void* __malloc_alloc_template<inst>::oom_realloc(void *p, size_t n)
{
	void(*my_malloc_handler)();
	void *result;
	for (;;)
	{
		my_malloc_handler = __malloc_alloc_oom_handler;
		if (0 == my_malloc_handler)
		{
			__THROW_BAD_ALLOC;
		}
		(*my_malloc_handler)();
		result = realloc(p, n);
		if (result)
			return result;
	}
}

所谓的C++ new handler机制是指,你可以要求系统在内存配置需求无法被满足时,调用一个你指定的函数。之所以要模拟这种机制,因为它并不是使用::operator new来配置内存的。

5.2 第二级配置器 __default_alloc_template

其实第一级配置器可以说用户是通过new和free直接与系统内存打交道的,而第二级配置器相对比较复杂,大概分为3块内存,简要的沟通机制可参考下图:

三块空间分别为freelist、mempool、系统内存。

各实现的伪代码可以参考博客:https://blog.csdn.net/qq973177663/article/details/50815055?locationNum=9

总是通过freelist来获得内存,freelist如果没有内存了,则调用refill()向mempoor获得内存,mempoor如果也不够,则调用trunk_alloc()向内存申请,内存都没有调用第一级配置器,看看out of memory机制能够起作用。

整个第二级配置器无非就是对上述freelist空间、mempoor空间的创建、内存申请、内存回收、以及之间的通信。源码如下:

enum { __ALIGN = 8 }; //< 小型区块的上调边界
enum { __MAX_BYTES = 128 }; //< 小型区块的上限
enum { __NFREELISTS = __MAX_BYTES / __ALIGN }; //< freelist个数:16个

template<bool threads, int inst>
class __default_alloc_template
{
public:
	//< 三个接口
	static void *allocate(size_t n);
	static void deallocate(void *p, size_t n);
	static void* reallocate(void *p, size_t old_sz, size_t new_sz);

private:
	// 将申请的size上调至__ALIGN的倍数
	static size_t ROUND_UP(size_t bytes)/
	{
		return (bytes + __ALIGN - 1) & ~(__ALIGN - 1);//
	}
	
	// freelist节点结构
	union obj
	{
		union obj * free_list_link;
		char client_data[1];
	};

	// 根据要申请的区块大小,决定使用第n号freelist,n从0算起
	static size_t FREELIST_INDEX(size_t bytes)
	{
		return (bytes + __ALIGN - 1) / __ALIGN - 1;
	}

	// 返回一个大小为n的区块对象,并可能(通常)加入大小为n的其他区块到freelist
	static void* refill(size_t n);

	// 配置一大块空间,可容纳nobjs个大小为size的区块
	// 注意此处nobjs是引用,如果配置有所不便(内存不够),nobjs会降低
	static char* chunk_alloc(size_t size, int &nobjs);

	static obj * free_list[__NFREELISTS]; //< 16个freelist 
	static char *start_free;//< 内存池其实位置
	static char *end_free; //< 内存池结束位置
	static size_t heap_size; //< 配置内存的附加量
};

// 赋初值
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::start_free = 0;
template<bool threads, int inst>
char* __default_alloc_template<threads, inst>::end_free = 0;
template<bool threads, int inst>
size_t __default_alloc_template<threads, inst>::heap_size = 0;

template<bool threads, int inst>
__default_alloc_template<threads, inst>::obj*
__default_alloc_template<threads, inst>::free_list[__NFREELISTS] =
{ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 };

 

5.2.1 freelist

简单说,就是16个8byte倍数但小于128byte的链表,链表的节点如下:

union obj
{
    union obj* free_list_link;
    char client_data[1];
};

其实我对这个节点的定义还是有点疑问的,具体请看我的另一篇博客:

https://blog.csdn.net/u012481976/article/details/82724916

一共16个freelist,每个freelist就是一个链表,16个的区别就是链表节点所占空间大小不一样,:

对于每个freelist的空间申请与释放,其实就是一些链表的操作。

5.2.2 二级配置器中的空间配置函数allocator()

以下代码描述了如何利用二级配置器中的allocator()配置空间,以及freelist空间如何与mempool之间通信,代码如下:

static void* allocate(size_t n)
{
	obj* volatile* my_free_list;
	void* result = 0;

	//如果大于128B, 直接调用一级配置器
	if (n > (size_t)_MAX_BYTES) 
	{
		return (malloc_alloc::allocate(n));
	}
	//寻找 16个free-list 中的一个
	my_free_list = free_list + FREELIST_INDEX(n);
	result = *__my_free_list;
	if (result == 0)
	{
		//如果freelist上没有可用空间,则将空间调整至8的倍数
        //调用refill,向mempool申请内存,重新填充该freelist
		result = refill(ROUND_UP(n));
		return result;
	}
	else 
	{
		*my_free_list = result->_M_free_list_link;
	}

	return result;
};

其中freelist与mempool之间的通信函数refill(),源码如下:

template<bool threads, int inst>
void* __default_alloc_template<threads, inst>::refill(size_t n)
{
	//默认取20个新节点连接到freelist上(其实是19个,第一个返回给用户)
	int nobjs = 20;

	//调用chunk_alloc(),尝试取得nobjs个区块作为freelist的新节点
	//注意此处参数nobjs是通过引用传入,有可能变小
	char* chunk = chunk_alloc(n, nobjs);
	obj* volatile* my_free_list;
	obj * result;
	obj * current_obj, *next_obj;
	int i;

	//如果只获得一个区块,则将这个区块直接反馈,freelist无新节点
	if (1 == nobjs)
	{
		return chunk;
	}

	//找到需要填充的链表的位置
	my_free_list = freeList + FREELIST_INDEX(n);
	result = (obj*)chunk;//第一块返回给客户端
	//引导freelist指向新的空间
	*my_free_list = next_obj = (obj*)(chunk + n);//这里把第二块先挂到指针数组对应位置下  //注意这里的n在传参数时已经调整到8的倍数
	for (i = 1;; i++) {//从1开始,0返回给客户端
		cur_obj = next_obj;
		next_obj = (obj*)((chat*)next_obj + n);
		if (nobjs - 1 == i) {                   //因为第一次从内存池取下的空间在物理上是连续的 尾插方便用 以后用完还回自由链表的就不是了
			cur_obj->free_list_link = NULL;//这里没有添加节点
			break;
		}
		else {
			cur_obj->free_list_link = next_obj;//nobjs - 2是最后一次添加节点
		}
	}
	return result;
}

 

5.2.3 空间释放函数deallocate()

如果释放的空间大于128b则调用第一级配置器,如果小于128b,则将要释放的空间链接到对应的freelist上,也就是一个在链表头插入节点的过程:

static void deallocate(void* p, size_t n)
{
	obj* volatile*  my_free_list;
	obj* q = (obj*)p;

	//如果大于128,调用第一级配置器
	if (n > (size_t)_MAX_BYTES)
	{
		malloc_alloc::deallocate(p, n);
		return;
	}	
		
	//寻找对应的freelist
	my_free_list = _S_free_list + _S_freelist_index(n);
	//回收该区块
	q->free_list_link = *my_free_list;
	*my_free_list = q;
}

5.2.4 内存池(mem pool)

chunk_alloc()是负责mem pool与系统内存打交道的,源码如下:

template<bool threads, int inst>
void* __default_alloc_template<threads, inst>::chunk_alloc(size_t size, int& nobjs)
{
	char * result;
	size_t total_bytes = size * nobjs;
	size_t bytes_left = end_free - start_free;

	// 内存池剩余空间完全满足需求量
	if (bytes_left >= total_bytes) 
	{
		result = start_free;
		start_free += total_bytes;
		return result;
	}
	else if (bytes_left >= size) 
	{
		// 内存池剩余空间不能完全满足需求量,但足够供应一个(含)以上的区块
		nobjs = bytes_left / size;
		total_bytes = size * nobjs;
		result = start_free;
		start_free += total_bytes;
		return result;
	}
	else 
	{
		// 内存池剩余空间连一个区块的大小都无法提供
		size_t bytes_to_get = 2 * total_bytes + ROUND_UP(heap_size >> 4);
		// 以下试着让内存池中的残余零头还有利用价值
		if (bytes_left > 0) 
		{
			// 内存池内还有一些零头,先配给适当的free list
			// 首先寻找适当的free list
			obj * volatile * my_free_list = free_list + FREELIST_INDEX(bytes_left);
			// 调整free list,将内存池中的残余空间编入
			((obj *)start_free)->free_list_link = *my_free_list;
			*my_free_list = (obj *)start_free;
		}

		// 配置heap空间,用来补充内存池
		start_free = (char *)malloc(bytes_to_get);
		if (0 == start_free) 
		{
			// heap空间不足,malloc失败
			int i;
			obj * volatile * my_free_list, *p;
			// 试着检视我们手上拥有的东西,这不会造成伤害。我们不打算尝试配置
			// 较小的区块,因为那在多进程机器上容器导致灾难
			// 以下搜寻适当的free list
			// 所谓适当是指“尚未用区块,且区块够大”的free list
			for (i = size; i <= __MAX_BYTES; i += __ALIGN) 
			{
				my_free_list = free_list + FREELIST_INDEX(i);
				p = *my_free_list;
				if (0 != p) 
				{ // free list内尚有未用块
							  // 调整free list以释放未用区块
					*my_free_list = p->free_list_link;
					start_free = (char *)p;
					end_free = start_free + i;
					// 递归调用自己,为了修正nobjs
					return chunk_alloc(size, nobjs);
					// 注意,任何残余零头终将被编入适当的free list中备用
				}
			}
			end_free = 0; // 如果出现意外,调用第一级配置器,看看oom机制能否尽力
			start_free = (char *)malloc_alloc::allocate(bytes_to_get);
			// 这会抛出异常 或 内存不足的情况得到改善
		}
		heap_size += bytes_to_get;
		end_free = start_free + bytes_to_get;
		// 递归调用自己,为了修正nobjs
		return chunk_alloc(size, nobjs);
	}
}

我觉得书上举的例子对这段代码的解释再合适不过了,非常透彻:

5.2.5 第二级配置器总的流程框图

自己懒得画了摘了一个:

6 内存基本处理工具

提供的三个工具uninitialized_copy()、uninitialized_fill()、uninitialized_fill_n(),用于将内存的配置与对象的构造分别开来。如何分开的呢?我们先看一下对于一个全区间的构造函数,如何构造对象的:

1,配置内存区块,足以包含范围内的所有元素;

2,在内存上构造对象;

那么这三个函数是如何发挥作用的呢?这里用到了is_POD_type的概念。POD意指Plain Old Data,也就是标量型别或传统的C struct型别。POD必然拥有trivial ctor/dtor/copy/assignment函数,因此我们可以:

对POD型别采取最有效的初值填写法,如:

int a;
a = 5;

而对non-POD型别采取最保险的安全做法:

char* p = new char;
new(p) char(5);

至于怎么判断一个迭代器所指对象的型别,那就是利用__type_trait了,后续再说。

如果is_POD_type是__true_type,那么这几个工具就调用相应的算法copy()、fill()、fill_n()。如果是__false_type则调用第4节提到的construct()。

7 小结

花了三天晚上看书,加上一个周末的下午+晚上串联思想与写这篇博客,总体感觉收获还是蛮多的,对于STL的内存配置以及泛型变成都有了一定得了解,还可以~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

[SGI STL]空间配置器--内存管理 的相关文章

  • 华为人才在线--华为认证进阶路径

    华为人才在线 华为认证进阶路径网址 网址 https e huawei com cn talent cert roadmap 在这个网址可以学习华为的各类技术方向 能下载到培训教程和实验手册 以及在线学习 如学习路由交换的HCIA点击红色的
  • Numpy数组

    NumPy 最重要的一个特点是其 N 维数组对象 ndarray 它是一系列同类型数据的集合 以 0 下标为开始进行集合中元素的索引 ndarray 对象是用于存放同类型元素的多维数组 ndarray 中的每个元素在内存中都有相同存储大小的
  • 预装载功能探究一:比较/捕获寄存器预装载使能的意义

    以沁恒CH32V307VCT6芯片测试为基准 测试开发板 CH32V307V R1 1V0 以下内容均为解析调用此句代码的意义 TIM OC1PreloadConfig TIM1 TIM OCPreload Enable 第一层 解析底层代
  • 使用 Git Extensions 简单入门 Git

    使用 Git Extensions 简单入门 Git 独立观察员 2015 11 25 前言 关于这个主题 之前我录了段视频教程 在本地看清晰度还可以 但传到优酷上就很不清晰了 即使是后来重制后还是一样不清晰 所以现在想整理成文字版 当然
  • 计算机网络基础学习 --- 第四章--网络层(ipv4概述)

    1 网络层 1 1 网络层概述 1 2 网络层提供的2种服务 1 3 IPV4 1 3 1 IPV4概述 1 3 2 分类编址的IPV4地址 1 3 3 划分子网的IPV4地址 1 3 4 无分类编制的IPV4地址 1 3 5 IPV4地址
  • jvm分析工具简介

    根据开源谷粒商城学习总结的笔记 觉得是个很好用的工具 推荐 因为jdk自带不用单独安装且很好用不像jprofiler还得安装和破解 1 jconsole 安装jdk配置好环境变量 win R输入cmd进入dos命令行 启动应用 想要分析的项
  • Go语言面试题--基础语法(26)

    文章目录 1 下面这段代码能否正常结束 2 下面这段代码输出什么 为什么 3 下面代码是否能编译通过 如果通过 输出什么 1 下面这段代码能否正常结束 func main v int 1 2 3 for i range v v append
  • 安装nvm管理node版本详细步骤

    安装nvm管理node版本详细步骤 首先需要把已经安装的node进行卸载 注 卸载node之前 最好把node的版本记录一下 方便安装nvm之后可以下载你需要的node版本 具体卸载步骤如下 打开电脑的控制面板 gt 点击程序 gt 点击程
  • win7+nfs文件服务器,win7如何挂载nfs服务器

    在linux下面 除了samba 一种在局域网内的不同计算机之间提供文件及打印机等资源的共享服务 服务之外 我们还可以通过nfs服务共享文件 以达到跨本台访问的需求 下面是学习啦小编收集整理的win7如何挂载nfs服务器 希望对大家有帮助
  • 学 Rust 最好的图书之一,原版豆瓣 9.7分“封神之作”

    1 瓜王 争霸赛总冠军 开发者版图同步飞涨 Rust 大概是世界上 瓜 最多的编程语言了 最近的一个 瓜 来自微软 用 Rust 重写 Windows 内核 一边是不断有大厂使用 Rust 重构某个版块的老旧代码 一边是 Rust 内部传出
  • 企业场景篇

    企业场景篇 设计模式 简单工厂模式 工厂 factory 处理创建对象的细节 一旦有了SimpleCoffeeFactory CoffeeStore类中的orderCoffee 就变成此对象的客户 后期如果需要Coffee对象直接从工厂中获
  • 一句sql搞定Mysql删除数据后自增列从1开始

    在数据库应用 我们经常要用到唯一编号 以标识记录 在MySQL中可通过数据列的AUTO INCREMENT属性来自动生成 MySQL支持多种数据表 每种数据表的自增属性都有差异 这里将介绍各种数据表里的数据列自增属性 数据库中设置了自增列
  • RuntimeError: CUDA error: an illegal memory access was encountered 解决思路

    问题描述 在跑编译正常通过 CPU上也正常运行的某项目时 在运行到某个epoch时 程序突然出现以下错误 RuntimeError CUDA error an illegal memory access was encountered CU
  • 行业基础概念

    1 SoC称为系统级芯片 2 AHB Advanced High Performance Bus 译作高级高性能总线 3 DMA 全称Direct Memory Access 即直接存储器访问 DMA用来提供在外设和存储器之间或者存储器和存
  • SoloPi APP性能测试用

    这款名为 SoloPi 的小工具 作用在于监测安卓机的运行状态 包括 CPU 内存 乃至大家感受最直观的帧率等等 SoloPi 本身是非常良心的 首先它开源 保证了干净安全 其次它也没有广告和多余的后台进程 非常令人放心 SoloPi ht
  • 【MLOps】第 7 章 : 监控和反馈循环

    大家好 我是Sonhhxg 柒 希望你看完之后 能对你有所帮助 不足请指正 共同学习交流 个人主页 Sonhhxg 柒的博客 CSDN博客 欢迎各位 点赞 收藏 留言 系列专栏 机器学习 ML 自然语言处理 NLP 深度学习 DL fore
  • 加速中产 “返贫” 的4个迹象

    没有消息 就是好消息 这话放在现在的朋友圈子里 似乎很合适 最近接到两个朋友的电话 一个是朋友的诉苦电话 这位朋友曾是某大厂的高管 被裁后失业近1年 虽然当初赔了N 1 但架不住这位朋友 房贷近千万 配偶不工作 孩子送出国 即传说中的 中产
  • Win11系统默认用户名怎么进行修改教学

    Win11系统默认用户名怎么进行修改教学 安装了Win11系统之后 我们电脑的默认用户名是administrator 而有的用户想要将这个用户名进行个性化的修改 把它修改成为自己喜欢的名称 那么如何去修改默认用户名 接下来我们一起来看看具体
  • Python之“诗词大会”游戏

    需求分析 要先设计题库 然后在规定时间内循环随机出题 判断输入的答案是否正确 并统计答对问题的次数 如果答题时间到 则退出循环 结束答题 具体实现步骤 1 创建字典bank保存题库 问题为键 正确答案为值 问题使用元组保存 题干和选项为元组

随机推荐

  • 使用iostat分析IO性能

    对于I O bond类型的进程 我们经常用iostat工具查看进程IO请求下发的数量 系统处理IO请求的耗时 进而分析进程与操作系统的交互过程中IO方面是否存在瓶颈 下面通过iostat命令使用实例 说明使用iostat查看IO请求下发情况
  • 【神经网络】(9) 迁移学习(CNN-InceptionResNetV2),案例:交通标志4分类

    各位同学好 今天和大家分享一下Tensorflow2 0中使用迁移学习的方法 搭载InceptionResNetV2网络进行交通标志分类识别 1 网络简介 网络复现代码见下文 神经网络学习小记录35 Inception ResnetV2模型
  • Linux 中实用但很小众的 11 个炫酷终端命令

    今天给大家分享Linux总结出来的11个炫酷的Linux终端命令大全 通过今天这篇文章将向大家展示一系列的Linux命令 工具和技巧 我希望一开始就有人告诉我这些 而不是曾在我成长道路上绊住我 命令行日常系快捷键 如下的快捷方式非常有用 能
  • HTTP数据包头解析

    HTTP请求模型 一 连接至Web服务器 一个客户端应用 如Web浏览器 打开到Web服务器的HTTP端口的一个套接字 缺省为80 例如 http www myweb com 8080 index html 在Java中 这将等同于代码 S
  • 创建软连接和硬链接

    前言 硬链接的原理 使链接的两个文件共享同样的文件内容 也就是同样的 inode 硬链接有一个缺陷 只能创建指向文件的硬链接 不能创建指向目录的硬链接 但软链接可以指向文件或目录 软链接的原理 就跟我们在window ln 命令 创建链接
  • linux:linux高级命令(三)

    1 查找文件命令 1 1根据名称查找 find 在指定目录下查找文件 包括目录 在这里面递归找 选项 name 根据文件名 包括目录名 查找 eg find home beiyue desktop name 1 xt 不要在根目录下面查找因
  • 【JVM】加载机制

    JVM加载机制 一 类装载子系统 介绍 类加载器ClassLoader角色 类加载的执行过程 加载 链接 初始化 二 类加载器 介绍 分类 三 双亲委派模型 介绍 为什么需要双亲委派模型 如何实现双亲委派模型 四 自定义类加载器 为什么要自
  • Notepad++查看、编辑二进制文件——安装附加组件HexEditor实现

    Notepad 是一款功能强大的编辑器 单独安装的Notepad 不能查看和编辑2进制 16进制文件 只要安装名为HexEditor的插件就可以了 这里分享出来 插件下载地址与安装 gt 有以下几种方式 我用的第3种 1 打开软件 点击菜单
  • 别再头疼反弹Shell失败了,这篇文章带你找到问题根源

    别再头疼反弹Shell失败了 这篇文章带你找到问题根源 在渗透测试中 反弹shell失败的原因可以有多种 以下是一些常见的原因 1 防火墙和网络过滤器 目标系统可能配置了防火墙或网络过滤器 以限制对外部系统的连接 这些过滤器可以阻止反弹sh
  • 登陆接口的的Filter过滤

    目录 一 概述 二 基本操作 三 登陆检查接口 一 概述 什么是Filter Filter表示过滤器 是 JavaWeb三大组件 Servlet Filter Listener 之一 过滤器可以把对资源的请求拦截下来 从而实现一些特殊的功能
  • vue resource jsonp请求

    vue resource jsonp请求 window onload function var app new Vue el box data methods get function this http jsonp https sug s
  • Python3 ID3决策树判断申请贷款是否成功

    目录 1 定义生成树 2 递归产生决策树 3 调用生成树 4 绘制决策树 5 调用函数 1 定义生成树 coding utf 8 生成树的函数 from numpy import import numpy as np import pand
  • Node-解决sequelize配置时区,差8小时问题

    最近进行node sequelize后台开发时 出现一个关于创建时间写入和读取时差8小时问题 默认配置 写入和读取值一样但是时区有问题 配置东八时区 写入的值是正确的但读取值有时区问题 正确配置 写入和读取值都正确 如有疑问或不足之处 欢迎
  • “The xxx field is required“, Nullable引发的bug

    0 背景 最近接到一个报告的bug 有一个前端的功能不好用了 这个项目是由前端和后台服务组成的 前端用的angular 服务用的是 net6 0 从前端开始排查 发现有个api的返回错误 返回中报告 The xxx field is req
  • 基于unityshader的屏幕模糊处理:

    首先我们都知道 我们所看见的都是摄像机将画面投影到视口纹理上面 也就是一张RenderTexture 四舍五入就是一张图 那么我们就只需要对这张图进行模糊处理就可以了 而模糊处理我们就需要用到对图片像素进行操作 那么用shader最好了 模
  • 启动spyder报错PyCapsule_GetPointer called with incorrect name

    C Users Administrator gt spyder Traceback most recent call last File D anaconda Scripts spyder script py line 10 in
  • JavaScript-----函数

    目录 前言 JavaScript函数 1 定义函数 构造函数 2 调用函数 函数的自执行 3 函数的参数 4 函数返回值 5 作用域 6 匿名函数 7 this指向性问题 重点 7 1 this的性质 7 2 call的用法 7 3 app
  • 不定积分24个基本公式_谈论不定积分及其求法

    一 原函数不定积分的概念 原函数的定义 如果区间I上 可导函数F x 的导函数为f x 即对任一x I都有 F x f x 或 dF x f x dx 那么函数F x 就称为f x 或 f x dx 在区间 I 内的一个原函数 原函数存在定
  • 浅谈 Vue 项目优化

    好久不写博文了 本文作为我使用半年 vue 框架的经验小结 随便谈谈 且本文只适用于 vue cli 初始化的项目或依赖于 webpack 打包的项目 前几天看到大家说 vue 项目越大越难优化 带来很多痛苦 这是避免不了的 问题终究要解决
  • [SGI STL]空间配置器--内存管理

    SGI STL 系列文章前言 废话不多说 读侯捷的SGI STL源码分析目的有三个 1 接触c 不久就开始跟STL打交道 一直有个好奇心 这么强大的库到底是谁 咋实现的 2 不熟悉实现就用不好STL 所以想更好的应用STL 就有必要一探其底