STL之二级空间配置器及实现

2023-11-11

之前对于配置器的原理及一级配置器的介绍请看博文
这里写链接内容
下来我们直接介绍二级空间配置器。

二级空间配置器

我们通过之前的学习,已经知道。
如果所要申请的空间大于128字节,则直接交至一级空间配置器处理,如果小于128字节,则使用二级空间配置器。
二级空间配置器对内存的管理减少了小区块造成的内存碎片,它主要通过一个链表管理:
它是用一个16(128/8)个元素的自由链表来管理的,每个位置下挂载着大小为8的倍数个字节(分别为8、16、24、32、48、56、64、72、80、88、96、104、112、120、128字节),每次将所需内存提升到8的倍数。
free_list它的节点结构为一个共用体:

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

二级空间配置器结构如下图所示:
这里写图片描述
如图所示:
在链表中空间没有分配出去之前,该空间中一直存放的是所指向的下一个节点的地址,当该空间分配出去时,让对应的my_free_list指向它的下一个节点,然后将它内部的值改为要放入用户的data值。

如何分配内存

它的内存分配主要分为以下几种情况:
1、对应的free_list不为空
所需空间大小提升为8的倍数后(如需要13bytes空间,我们会给它分配16bytes大小),所对应的free_list不为空时,它会像上图所示那样直接从对应的free_list中拔出,第一位置向后移动指向。
2、对应的free_list为空,其内存池不为空时:
(1)先检验它剩余空间是否够20个节点大小(即所需内存大小(提升后) * 20),若足够则直接从内存池中拿出20个节点大小空间,将其中一个分配给用户使用,另外19个当作自由链表中的区块挂在相应的free_list下,这样下次再有相同大小的内存需求时,可直接从 free-list 中拨出。
(2)如果不够20个节点大小,则看它是否能满足1个节点大小,如果够的话则直接拿出一个分配给用户,然后从剩余的空间中分配尽可能多的节点挂在相应的free_list中。
(3)如果连一个节点内存都不能满足的话,则将内存池中剩余的空间挂在相应的free_list中(找到相应的free_list),然后再给内存池申请内存。
3、内存池为空,申请内存
此时二级空间配置器会使用malloc()从heap上申请内存,(一次所申请的内存大小为2 * 所需节点内存大小(提升后)* 20 + 一段额外空间)。
4、malloc没有成功
在第三种情况下,如果malloc()失败了,说明heap上没有足够空间分配给我们了,这时,二级空间配置器会从比所需节点空间大的free_list中一一搜索,从任意一个比它所需节点空间大的free_list中拔除一个节点来使用。
5、查找失败,调用一级空间配置器
第四种情况下,如果查找失败了,说明比其大的free_list中都没有自由区块了,此时会调动一级空间配置器oom_allocate()。
(一级空间配置器文首给出了博文链接)
内存池的概念请看博文这里写链接内容

部分源码分析

按照上面内存分配的步骤,我们一步步来看:

/*将 __bytes 上调至最邻近的 8 的倍数*/  
static size_t  _S_round_up(size_t __bytes)  
{  
    return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));  
}  

/*返回 __bytes 大小的小额区块位于 free-list 中的编号*/  
static  size_t _S_freelist_index(size_t __bytes) {  
    return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);  
}  

根据用户所需内存块大小,申请内存:

static void* allocate(size_t __n)   //分配大小为 __n 的区块  
{  
    void* __ret = 0;  

    if (__n > (size_t)_MAX_BYTES) {  
        __ret = malloc_alloc::allocate(__n);    //大于128 bytes 就调用第一级配置器  
    }  
    else { //__n 大小区块对应的位置:free-lists 首地址 + __n 位于free-lists 中的编号  
        _Obj* __STL_VOLATILE* __my_free_list    //这里是二级指针,便于调整 free-lists   
            = _S_free_list + _S_freelist_index(__n);          

#     ifndef _NOTHREADS  
        _Lock __lock_instance;  
#     endif  

        _Obj* __RESTRICT __result = *__my_free_list; //将对应位置的区块拨出(第一个)  
        if (__result == 0)                       //如果 free-lists 中没有对应大小的区块  
            __ret = _S_refill(_S_round_up(__n)); //调用 _S_refill()  
        else {  
            *__my_free_list = __result->_M_free_list_link;//这个结构有点类似链式哈希表结构,这里是指向下一块空闲内存块    
            //二级指针调整 free-lists,拨出去的区块就不属于该链表了  
            __ret = __result;                                 
        }  
    }  

    return __ret;  
};  

如果 free-list 中没有对应大小的区块,转去调用 _S_refill():

template <bool __threads, int __inst>  
void*           //重新填充__n大小的区块进 free-list  
__default_alloc_template<__threads, __inst>::_S_refill(size_t __n)    
{  
    int __nobjs = 20;      //缺省取得 20 个新区块  
    char* __chunk = _S_chunk_alloc(__n, __nobjs);  //调用_S_chunk_alloc()  
    _Obj* __STL_VOLATILE* __my_free_list;  
    _Obj* __result;  
    _Obj* __current_obj;  
    _Obj* __next_obj;  
    int __i;  

    /*如果只获得一个新区块,直接划给用户,free-list 仍然无新节点*/  
    if (1 == __nobjs) return(__chunk);    
    __my_free_list = _S_free_list + _S_freelist_index(__n);  

    __result = (_Obj*)__chunk;   //这一块返回给客端(分配出来的第一块)  
    *__my_free_list = __next_obj = (_Obj*)(__chunk + __n);   
    /*接下来的区块(拨出去了__n大小给用户)填补进 free-list*/  
    for (__i = 1;; __i++) {  
        __current_obj = __next_obj;   
        __next_obj = (_Obj*)((char*)__next_obj + __n);   
        /*将分配的连续大块"分割"成__n bytes大小的区块*/  
        if (__nobjs - 1 == __i) {  //如果新区块填补完毕  
            __current_obj->_M_free_list_link = 0;  //free-list 最后位置指向0  
            break;  
        }  
        else {//把_M_free_list_link当做链表的 next 指针理解  
            __current_obj->_M_free_list_link = __next_obj; //将各节点串联起来填进空闲链表  
        }  
    }  
    return(__result);  
}  

给对应的哈希桶申请空间:

static char* _S_start_free;   //内存池起始位置  
static char* _S_end_free;     //内存池末端位置  
static size_t _S_heap_size;   //堆空间容量  

template <bool __threads, int __inst>  
char*  
__default_alloc_template<__threads, __inst>::_S_chunk_alloc(size_t __size,  
int& __nobjs)  
{  
    char* __result;  
    size_t __total_bytes = __size * __nobjs;       //需要内存的总额大小  
    size_t __bytes_left = _S_end_free - _S_start_free;  //内存池中还剩余多少可用内存  

    if (__bytes_left >= __total_bytes) {    //剩余可用量大于需求量,直接划分出去  
        __result = _S_start_free;           //内存池的首地址  
        _S_start_free += __total_bytes;     //调整内存池位置  
        return(__result);    
    }  
    //内存池剩余空间不能完全满足需求,但至少可供应一个及以上的区块  
    else if (__bytes_left >= __size) {        
        __nobjs = (int)(__bytes_left / __size);  //调整划分个数,划分出去最大量  
        __total_bytes = __size * __nobjs;        //同上  
        __result = _S_start_free;  
        _S_start_free += __total_bytes;  
        return(__result);  
    }  
    else {    //内存池剩余空间连一个区块都无法提供  
        size_t __bytes_to_get =//配置大小为总需求量的两倍再加上一个随配置次数逐渐增加的附加量  
            2 * __total_bytes + _S_round_up(_S_heap_size >> 4); /  

        if (__bytes_left > 0) {   //充分利用内存池中的剩余空间  
            _Obj* __STL_VOLATILE* __my_free_list =   //剩余空间寻找适当的free-list  
                _S_free_list + _S_freelist_index(__bytes_left);     
         //调整 free-list,将内存池中残余空间编入 free-list 对应位置中  
            ((_Obj*)_S_start_free)->_M_free_list_link = *__my_free_list;  
            *__my_free_list = (_Obj*)_S_start_free;  
        }  
        _S_start_free = (char*)malloc(__bytes_to_get);  //配置heap空间,用来补充内存池  
        if (0 == _S_start_free) {         //system heap 空间不足,分配失败  
            size_t __i;  
            _Obj* __STL_VOLATILE* __my_free_list;  
            _Obj* __p;  

            for (__i = __size;                 //起始大小为需求区块大小  
                __i <= (size_t)_MAX_BYTES;        
                __i += (size_t)_ALIGN) {       //以 8 为步长搜寻整个 free-list  
                __my_free_list = _S_free_list + _S_freelist_index(__i);  //找到 __i大小区块在free-list 中的位置  
                __p = *__my_free_list;  
                if (0 != __p) {               //如果 free-list 中该区块未使用  
                    *__my_free_list = __p->_M_free_list_link;   //调整 free-list,释放第一位置块  
                    _S_start_free = (char*)__p;              //编入内存池  
                    _S_end_free = _S_start_free + __i;         
                    return(_S_chunk_alloc(__size, __nobjs));    //递归调用  
        /*该for循环的作用就是从 free-list 中划出大于需求区块(单个)的未用空间区块到内存池,然后再从内存池中取出。 
        由于从大于__size 的区块开始搜寻,所以如果 free-list 中搜寻到,那么只需动用该搜寻区块的第一位置区块即可, 
        最后取出的空间也可能是单个区块,也可能是多个区块(取决于 free-list 未用区块的最小区块(大于__size)的大小)*/  
                }  
            }  
            _S_end_free = 0;    //表示到处无内存可用,for循环中free-list没有搜寻到适当的未用空间  
            _S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get); //调用第一级配置器  
            /*要么内存不足的问题获得改善,要么抛出 bad_alloc 异常*/  
        }  
        _S_heap_size += __bytes_to_get;   //调用第一级配置器后,内存不足问题获得改善,调整堆空间  
        _S_end_free = _S_start_free + __bytes_to_get;    //编入内存池  
        return(_S_chunk_alloc(__size, __nobjs));   //重新调用 _S_chunk_alloc()  
    }  
} 

释放空间

当用户从二级空间配置器中申请的内存被释放时,要将其回收然后插入对应的free_list:

/*空间释放后被编入 free-list*/  
static void deallocate(void* __p, size_t __n)  
{  
    if (__n > (size_t)_MAX_BYTES)  //大于 128 就调用第一级配置器  
        malloc_alloc::deallocate(__p, __n);  
    else {  
        _Obj* __STL_VOLATILE*  __my_free_list  //定位到对应的 free-list  
            = _S_free_list + _S_freelist_index(__n);  
        _Obj* __q = (_Obj*)__p;         

#       ifndef _NOTHREADS  
        _Lock __lock_instance;  
#       endif   

        __q->_M_free_list_link = *__my_free_list;  //调整 free-list 回收区块  
        *__my_free_list = __q;        //回收的区块是挂接到free-list 对应区块的第一位置,而不是添加到尾端  
    }  
}  

模拟实现

我们测试时加入一级空间配置器的代码:

//一级空间配置器  >128
//函数指针
typedef void(*pMallocHandle)();
template<int inst>
class MallocAllocTemplate
{
public:
    static void* Allocate(size_t size)
    {
        void* result = malloc(size);
        //申请失败调用OOM_Malloc()函数
        if (NULL == result)
        {
            result = OOM_Malloc(size);
        }

        __TRACE_DEBUG("一级空间配置器:%d\n", size);
        return result;
    }
    //重新分配空间
    static void* ReAllocate(void* p, size_t, size_t newSize)              //三个参数
    {
        void* result = realloc(p, newSize);
        if (NULL == result)
        {
            result = OOM_Realloc(p, newSize);
        }
        return result;
    }
    static void DeAllocate(void* p, size_t)  //底层是两个参数
    {
        __TRACE_DEBUG("一级释放\n");
        free(p);
    }

private:
    static void* OOM_Malloc(size_t size)
    {
        pMallocHandle mallocHandle;
        void* res;
        for (;;)
        {
            mallocHandle = _mallocHandle;
            if (NULL == mallocHandle)
                throw std::bad_alloc();
            //尝试去释放已经获取,但是不用的堆空间
            mallocHandle();
            //尝试重新分配内存
            res = malloc(size);
            if (res)
                return res;
        }
    }
    static void* OOM_Realloc(void *p, size_t size)
    {
        pMallocHandle mallocHandle;
        void* res;
        __TRACE_DEBUG("一级:第一次申请失败,OOM处理:%d\n", size);
        for (;;)
        {
            mallocHandle = _mallocHandle;
            if (NULL == mallocHandle)
                throw std::bad_alloc();
            //尝试去释放已经获取但是不用的堆空间
            mallocHandle();

            res = realloc(p, size);
            if (res)
                return res;
        }
    }

    //若申请失败,释放方法   
    static pMallocHandle SetMallocHandle(pMallocHandle mallocHandle)
    {
        pMallocHandle old = _mallocHandle;
        _mallocHandle = mallocHandle;
        return old;
    }
private:
    static pMallocHandle _mallocHandle;
};

pMallocHandle MallocAllocTemplate<0>::_mallocHandle = NULL;

根据上面的分析,二级空间配置器的代码如下:

//<=128个字节
//交给二级配置器
//小的内存块,减少频繁的向系统malloc

template<int inst>
class DefaultAllocateTemplate
{
public:
    //申请内存
    static void* Allocate(size_t size)
    {
        if (size > 128)
            return MallocAllocTemplate<0>::Allocate(size);

        __TRACE_DEBUG("二级空间配置器:%d\n", size);
        size_t index = FreeListIndex(size);      //求出对应的索引
        if (_freeList[index] == NULL)            //链表没有,从内存池申请填充
        {
            __TRACE_DEBUG("二级:%d桶中(%d)没有可用空间,需要内存池补充\n", index, size);
            return ReFill(ROUND_UP(size));
        }

        //将对应哈希桶中链表中第一块拨出给用户
        void* result = _freeList[index];
        _freeList[index] = _freeList[index]->_freeListLink;
        return result;
    }
    //释放内存
    static void DeAllocate(void *ptr, size_t size)
    {
        if (size > 128)
            return MallocAllocTemplate<0>::DeAllocate(ptr, size);

        __TRACE_DEBUG("二级空间配置器释放:%d\n", size);
        size_t index = FreeListIndex(size);

        ((OBJ*)ptr)->_freeListLink = _freeList[index];
        _freeList[index] = (OBJ*)ptr;
    }
private:
    //向上对齐为8的整数倍
    static size_t ROUND_UP(size_t bytes)
    {
        return (((bytes)+_ALIGN - 1)&~(_ALIGN - 1));
    }
    //bytes对应的哈希桶的下标
    static size_t FreeListIndex(size_t bytes)
    {
        return (((bytes)+_ALIGN - 1) / _ALIGN - 1);
    }
    //给对应的桶里填充内存块
    static void* ReFill(size_t size)
    {
        size_t nobjs = 20;
        char* chunk = (char*)ChunkAlloc(size, nobjs);

        if (nobjs == 1)       //只有一个直接给用户
            return chunk;

        size_t index = FreeListIndex(size);
        //将剩下的内存块管理起来,链接在对应的桶里
        OBJ* cur = (OBJ*)(chunk + size);   
        __TRACE_DEBUG("二级:内存池补充%d个小块内存\n", nobjs);
        while (--nobjs)
        {
            cur->_freeListLink = _freeList[index];  //头插每个size大小的内存块
            _freeList[index] = cur;

            cur = (OBJ*)((char*)cur + size);
        }
        return chunk;
    }
    //给桶申请空间
    static void* ChunkAlloc(size_t size, size_t& nobjs)
    {
        size_t TotalBytes = size*nobjs;
        size_t LeftBytes = _endFree - _startFree; //内存池剩余空间
        char *result;

        //内存池空间足够 nobjs=20
        if (LeftBytes >= TotalBytes)
        {
            __TRACE_DEBUG("二级:内存池可以提供20个%d字节的内存块\n", size)
            result = _startFree;
            _startFree += TotalBytes;
            return result;
        }
        //小于需要的20块,只能提供至少一块内存  1<=nobjs<20
        else if (LeftBytes >= size)
        {
            nobjs = LeftBytes / size;
            __TRACE_DEBUG("二级:内存池不太够,可以提供%d个%d字节的内存块\n", nobjs, size);

            result = _startFree;
            _startFree += nobjs*size;
            return result;
        }
        //一块内存都不够,给内存池补充空间
        else
        {
            __TRACE_DEBUG("二级:内存池一个%d字节的小块内存都不能提供\n", size);
            //1.将内存池剩余的空间挂到对应的链表中
            size_t index = FreeListIndex(LeftBytes);
            if (LeftBytes > 0)
            {
                OBJ* cur = (OBJ*)_startFree;
                cur->_freeListLink = _freeList[index];
                _freeList[index] = cur;
            }
            //2.在堆中给内存池申请空间
            size_t Getbytes = 2 * TotalBytes + ROUND_UP(_heapSize >> 4); //系统堆给内存池分配空间的大小
            __TRACE_DEBUG("二级:内存池不够了,需要在堆申请%d字节的内存块\n", Getbytes);
            _startFree = (char*)malloc(Getbytes);
            //2.1申请失败
            if (_startFree == NULL)
            {
                __TRACE_DEBUG("二级:内存池不够且在堆中申请失败,在链表中找更大的内存块,%d字节\n", (index + 1)*_ALIGN);
                //在二级空间配置器中找更大的内存块
                size_t index = FreeListIndex(size);
                for (int i = index; i < _NFREELISTS; i++)
                {
                    OBJ* Cur = _freeList[index];
                    if (Cur != NULL)
                    {
                        _startFree = (char*)Cur;
                        _freeList[index] = Cur->_freeListLink;
                        _endFree = _startFree + (index + 1)*_ALIGN;
                        return ChunkAlloc(size, nobjs);
                    }
                }

                __TRACE_DEBUG("二级:山穷水尽了,向一级空间配置器申请,%d字节的内存块\n", Getbytes);
                _endFree = NULL;       //避免异常,置空
                _startFree = (char*)MallocAllocTemplate<0>::Allocate(Getbytes);

            }
            //2.2在堆中申请成功
            __TRACE_DEBUG("二级:在堆中申请成功了%d字节的内存块\n", Getbytes);
            _heapSize += Getbytes;   
            _endFree = _startFree + Getbytes;
            return (ChunkAlloc(size, nobjs));
        }
    }
private:
    enum { _ALIGN = 8 };
    enum { _MAX_BYTES = 128 };
    enum { _NFREELISTS = _MAX_BYTES / _ALIGN };

    union OBJ
    {
        OBJ* _freeListLink;
        char clientData[1];      //
    };
private:
    static char* _startFree;     //标记内存池的起始地址  
    static char* _endFree;       //标记内存池的结束地址  
    static size_t _heapSize;     //之前向堆中申请的字节数  
    static OBJ* _freeList[_NFREELISTS];    //存放OBJ的指针数组  
};
//静态成员变量需要在类外初始化 
template<int inst>
char* DefaultAllocateTemplate<inst>::_startFree = NULL;   

template<int inst>
char* DefaultAllocateTemplate<inst>::_endFree = NULL;

template<int inst>
size_t DefaultAllocateTemplate<inst>::_heapSize = NULL;

template<int inst>
typename DefaultAllocateTemplate<inst>::OBJ*  DefaultAllocateTemplate<inst>::\
_freeList[DefaultAllocateTemplate<inst>::_NFREELISTS] = { 0 };

为了方便测试,我们将空间配置器封装为一个类:

//给空间配置器起别名
#ifdef USE_MALLOC  
typedef MallocAllocTemplate<0> _Alloc;
#else  
typedef DefaultAllocateTemplate<0> _Alloc;
#endif  

template <class T, class Alloc>
class SimpleAllocate
{
public:
    static void* Allocate(size_t n)      //申请n个类型为T的字节大小  
    {
        return (0 == n) ? 0 : Alloc::Allocate(n*sizeof(T));
    }
    static void* Allocate(void)          //申请T字节大小  
    {
        return (0 == n) ? 0 : Alloc::Allocate(sizeof(T));
    }
    static void DeAllocate(void* p, size_t n)
    {
        Alloc::DeAllocate(p, n*sizeof(T));
    }
    static void deallocate(void *p)
    {
        Alloc::Deallocate(p, sizeof(T));
    }
};

我们用一个特殊的测试函数测试,上述代码中有很多__TRACE_DEBUG包含的信息,这样测试方便我们知道代码的运行及数据的变化是否正确:

#define _DEBUG_  
static string GetFileName(const string& path)
{
    char ch = '/';
#ifdef _WIN32         
    ch = '\\';
#endif  
    size_t pos = path.rfind(ch);
    if (pos == string::npos)
        return path;
    else
        return path.substr(pos + 1);
}

//用于调试追踪的trace log
inline static void _trace_debug(const char * funcName, const char * fileName, int line, char* format, ...)
{
#ifdef _DEBUG_  
    fprintf(stdout, "[%s:%d]%s", GetFileName(fileName).c_str(), line, funcName);
    // 输出用户信息          
    va_list args;
    va_start(args, format);
    vfprintf(stdout, format, args);
    va_end(args);
#endif  
}
#define __TRACE_DEBUG(...) _trace_debug(__FUNCDNAME__, __FILE__, __LINE__, __VA_ARGS__);  

测试:

//测试
void TestAlloc()
{
    //一级
    int *p1 = (int*)SimpleAllocate<int, _Alloc>::Allocate(100);
    SimpleAllocate<int, _Alloc>::DeAllocate(p1, 100);

    //二级
    p1 = (int*)SimpleAllocate<int, _Alloc>::Allocate(2);
    SimpleAllocate<int, _Alloc>::DeAllocate(p1, 2);

    p1 = (int*)SimpleAllocate<int, _Alloc>::Allocate(5);   //内存池--->16

    int *p2 = (int*)SimpleAllocate <int, _Alloc>::Allocate(10);
    SimpleAllocate<int, _Alloc>::DeAllocate(p1, 5);
    SimpleAllocate<int, _Alloc>::DeAllocate(p2, 10);
}

观察结果:
这里写图片描述
读者可以自行计算一下。

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

STL之二级空间配置器及实现 的相关文章

  • Java 读取freemarker模板(html)转换成String

    2019独角兽企业重金招聘Python工程师标准 gt gt gt Java代码 package com main util import freemarker template Template import org slf4j Logg
  • springboot日期时间格式全局处理

    import com fasterxml jackson datatype jsr310 deser LocalDateTimeDeserializer import com fasterxml jackson datatype jsr31
  • Mysql进阶优化篇01——四万字详解数据库性能分析工具(深入、全面、详细,收藏备用)

    前 言 作者简介 半旧518 长跑型选手 立志坚持写10年博客 专注于java后端 专栏简介 mysql进阶 主要讲解mysql数据库进阶知识 包括索引 数据库调优 分库分表等 文章简介 本文将介绍数据库优化的步骤 思路 性能分析工具 比如
  • js 中的隐士转换 + ==规则

    ToString 1 数组中的null或undefined 会被当做空字符串处理 2 普通对象 转为字符串相当于直接使用Object prototype toString 返回 object Object ToNumber 1 null 转
  • Linux学习之基础工具一

    1 Linux 软件包管理器 yum 首先我们需要知道的是在Linux下 现存的软件和指令是一定的 而有的时候我们想需要更多的指令或者软件 而这在Linux本身下是没有的 故我们可以利用指令yum指令安装或卸载你想要或者不需要的软件 ubu
  • k8s学习pod第七天

    init Container 初始化容器是一类只运行一次的容器 本质是也是容器 不同容器间启动有先后顺序 只有前面的容器运行成功了 后面的容器才能运行 初始化容器的场景 在其他容器运行之前做个初始化 比如配置文件生成 环境变量生成 有先后顺
  • OpenCV——分水岭算法

    目录 一 分水岭算法 1 概述 2 图像分割概念 3 分水岭算法原理 二 主要函数 三 C 代码 四 结果展示 1 原始图像 2 分割结果 五 参考链接 一 分水岭算法 1 概述 分水岭算法是一种图像分割常用的算法 可以有效地将图像中的目标
  • Javascript高级程序设计——15-1.匿名函数和闭包

    1 匿名函数 表示没有定义函数名的函数 案例1 1 简单的匿名函数 function 单独的匿名函数无法执行 alert Lee 案例1 2 将匿名函数赋值给一个变量 var box function return Lee alert bo
  • 复数矩阵计算行列式

    项目上需要对复矩阵的行列式计算 根据计算一般矩阵行列式的代码改成了复矩阵行列式计算 include
  • 性能测试中TPS上不去的几种原因

    中TPS一直上不去 是什么原因 这篇文章 就具体说说在实际压力测试中 为什么有时候TPS上不去的原因 先来解释下什么叫TPS TPS Transaction Per Second 每秒事务数 指服务器在单位时间内 秒 可以处理的事务数量 一
  • Python库的使用说明

    目录 1 第三方库索引网站 2 第三方安装 2 1 pip工具介绍 2 2 pip工具安装 2 2 1 list 命令查看已安装的库列表 2 2 2 uninstall 命令 2 2 3 show 命令 2 2 4 download 命令
  • C++标准模板库 迭代器 iterator 详解(二)

    迭代器提供对一个容器中的对象的访问方法 并且定义了容器中对象的范围 迭代器就如同一个指针 事实上 C 的指针也是一种迭代器 但是 迭代器不仅仅是指针 因此你不能认为他们一定具有地址值 例如 一个数组索引 也可以认为是一种迭代器 迭代器有各种
  • [NOI2009]植物大战僵尸【拓扑+最大权闭合子图】

    题目链接 BZOJ 1565 看到这道题之后很容易想到的就是最大权闭合子图了 但是却有个问题就是要去除掉那些环 因为构成了环之后 相当于是无敌的状态 它们就永远不会得到贡献 并且环之后的点也是得不到贡献的 所以 这里利用拓扑 知道哪些点是可
  • 「Qt」事件概念

    0 引言 在本文所属专栏的前面的文章里 我们介绍了Qt的 信号 Signal 与 槽 Slot 机制 信号 Signal 与 槽 Slot 机制是 Qt 框架用于多个对象之间通信的 是 Qt 的核心特性 也是 Qt 与其他框架最大的不同之处
  • anaconda中spyder改变背景颜色(黑色)

    spyder挺好用的 但是未定义的背景颜色实在不好看 纯属个人审美 下面开始更换背景图 打开spyder 依此点击 Tools 再点击preference 喜爱 选择Syntax coloring Scheme调成Monokai 这是我喜欢
  • python+selenium+unittest自动化测试框架

    前言 关于自动化测试的介绍 网上已有很多资料 这里不再赘述 UI自动化测试是自动化测试的一种 也是测试金字塔最上面的一层 selenium是应用于web的自动化测试工具 支持多平台 多浏览器 多语言来实现自动化 优点如下 开源 免费且对we
  • pyecharts在数据可视化中的应用 (二)(pyecharts绘制树图、矩形树图、地理热力图、词云图、相关性矩阵等图)

    1 使用以下JSON数据绘制树图 矩形树图 from pyecharts import options as opts from pyecharts charts import Tree data name flare children n

随机推荐

  • Android 系统性能优化(57)---MTK 平台开关机、重启时间优化

    MTK 平台开关机 重启时间优化 开关机 重启时间优化 开机性能优化 是用功能和其它因素多方面平衡的结果 片面追求单方面的性能没有太大意义 有些产品设计开机动画非常酷炫 动画图片过多 高帧率会影响开机速度 这时就需要看是开机速度优先还是体验
  • 人工智能(pytorch)搭建模型8-利用pytorch搭建一个BiLSTM+CRF模型,实现简单的命名实体识别

    大家好 我是微学AI 今天给大家介绍一下人工智能 pytorch 搭建模型8 利用pytorch搭建一个BiLSTM CRF模型 实现简单的命名实体识别 BiLSTM CRF 模型是一种常用的序列标注算法 可用于词性标注 分词 命名实体识别
  • kubernetes资源控制器【一】- ReplicaSet控制器

    一 Pod控制器 Master的各组件中 API Server仅负责将资源存储于etcd中 并将其变动通知给各相关的客户端程序 如kubelet kube scheduler kube proxy和kube controller manag
  • id和instancetype的应用场景区别

    在 Objective C 中 id 是一个通用的指针类型 可以用来表示任何类型的对象 而instancetype是一个表示当前类类型的指针类型 通常用于方法的返回值类型 下面是它们的一些使用场景 使用id的情况 当你需要一个指向任何对象的
  • ubuntu 触摸板失灵解决

    ubuntu 触摸板失灵解决 Ubuntu 20 04 开机发现触摸板只能单击 经常漂移影响打字输入 操作 sudo modprobe r psmouse sudo modprobe psmouse 目的在于重新加载内核触摸板模块 重新加载
  • jquery ui 实现table的sortable功能以及过滤记录功能

    本人在工作中曾使用js实现过用鼠标拖动表格的行实现重新排序的功能 当时写了不少的js代码 最近发现jquery ui也能实现这个功能 而且很方便 真后悔当时不知道有这么个好东东 好 现在介绍下如何使用jquery ui来实现 引入的js文件
  • 邻结矩阵的创建

    图的邻结矩阵是储存图数据的一个手段 储存方式是用两个数组来表示圆 一个一维数组储存图中的顶点信息 一个二维数组 称为邻结矩阵 储存图中边或弧的信息 代码展示 include
  • Kotlin筑基

    Kotlin筑基 本文链接 核心思路 每个知识点都要和源码结合起来讲 文章目录 Kotlin筑基 编译时常量 基本类型 range 访问权修饰符 Unit Nothing 反引号 函数内联 函数引用 具名函数 判空和安全调用 断言操作 空合
  • ARM uboot 源码分析5 -启动第二阶段

    一 start armboot 解析6 1 console init f 1 console init f 是 console 控制台 的第一阶段初始化 f 表示是第一阶段初始化 r 表示第二阶段初始化 有时候初始化函数不能一次一起完成 中
  • 记录Android13权限适配和遇到的问题

    项目场景 修改 Android 13版本中需要修改以下2个权限
  • 后台退出功能开发

    代码开发 代码分析 我们看看后台首页 backend index html 退出按钮绑定的单击事件处理函数logout url employee logout 与 method post 告诉我们应该在雇员控制器EmployeeContro
  • div 固定不动,不随滚动条滚动且不闪动

  • 高并发场景下的 HttpClient 优化方案,QPS 大大提升!

    HttpClient优化思路 池化 长连接 httpclient和httpget复用 合理的配置参数 最大并发请求数 各种超时时间 重试次数 异步 多读源码 1 背景 我们有个业务 会调用其他部门提供的一个基于http的服务 日调用量在千万
  • C++11-14 第9讲 Alias Template(化名)

    template
  • 接口测试——PyTest自动化测试框架(八)

    1 PyTest介绍与安装 PyTest介绍 PyTest是python的一个第三方的单元测试库 自动识别测试模块和测试函数 支持非常丰富的断言 assert 语句 PyTest中的使用约束 测试文件的文件名必须以 test 或 test
  • Java项目 log4j2 配置日志写入指定文件

    一 背景 由于业务需要 需要将服务部分埋点日志写入指定文件 然后进行日志收集 进行数据分析统计 需要通过修改log4j2配置 引入对应logger打印日志实现 二 log4j2 xml配置
  • java 代理(静态代理、动态代理的不同实现)详解及示例

    文章目录 一 代理构成 1 代理介绍 2 应用场景介绍 二 静态代理 1 示例 1 售票服务 2 售票 3 代售点服务 4 静态代理实现 1 maven 依赖 2 实现 三 动态代理 1 InvocationHandler角色 2 Invo
  • 2023年高教社杯数学建模国赛C题详细版思路

    C 题 蔬菜类商品的自动定价与补货决策 2023年国赛如期而至 为了方便大家尽快确定选题 这里将对C题进行解题思路说明 以分析C题的主要难点 出题思路以及选择之后可能遇到的难点进行说明 方便大家尽快找到C题的解题思路 难度排序 B gt A
  • 常见的防火墙有哪几种类型

    防火墙对于游戏 金融 视频等等易受到攻击的行业来说 其部署是相当重要的 虽说不能百分百防御所有攻击 但在其中也起了很大的作用 防火墙是为加强网络安全防护能力在网络中部署的硬件设备 有多种部署方式 常见的主要有以下几种方式 1 桥模式 桥模式
  • STL之二级空间配置器及实现

    之前对于配置器的原理及一级配置器的介绍请看博文 这里写链接内容 下来我们直接介绍二级空间配置器 二级空间配置器 我们通过之前的学习 已经知道 如果所要申请的空间大于128字节 则直接交至一级空间配置器处理 如果小于128字节 则使用二级空间