2_高并發內存池_各層級的框架設計及ThreadCache(線程緩存)申請內存設計

一、高并發內存池框架設計

高并發池框架設計,特別是針對內存池的設計,需要充分考慮多線程環境下

  • 性能問題
  • 鎖競爭問題
  • 內存碎片問題

高并發內存池的整體框架設計旨在提高內存的申請和釋放效率,減少鎖競爭和內存碎片

高并發內存池設計的基本框架包括**ThreadCache(線程緩存)、CentralCache(中心緩存)和PageCache(頁緩存)**三個主要部分。主要組成部分包括:

接下來,我們先粗略介紹一下它們各自的結構和其之間的聯系,有利于后續創建每一層時聯系上下層編寫所需的代碼。

(一)ThreadCache的框架設計

功能:每個線程獨有的內存緩存,用于小于一定大小(如256KB)的內存的分配。線程從這里申請內存不需要加鎖,每個線程獨享一個Cache,大大提高了并發性能

結構:通常設計為哈希桶結構,每個桶是一個按桶位置映射大小的內存塊對象的自由鏈表。這樣,線程在申請或釋放內存時,可以直接在自己的ThreadCache中進行,無需與其他線程競爭,無須加鎖。

內存管理:支持不同大小的內存塊申請,通過哈希映射和對齊規則,將不同大小的內存塊映射到不同的哈希桶中。

1.ThreadCache申請內存

  1. 線程A向ThreadCache申請內存,申請的內存大小≤256KB;
  2. 線程A向ThreadCache申請內存就是從ThreadCache中獲取空閑的內存塊,從而尋找ThreadCache中的空閑內存塊使用;
  3. 通過映射關系找到對應的哈希桶,查看對應的哈希桶中是否擁有空閑內存塊。如果有則直接使用(Pop一個空閑內存塊返回),如果沒有則向上一層CentralCache對應的哈希桶中申請空閑內存塊插入到自由鏈表中,再Pop給線程A;
  4. 由于每個線程都有屬于自己的Thread,因此我們引入一個知識點TLS無鎖訪問使得每個線程都可以有屬于自己的Thread。

2.ThreadCache釋放內存

  1. 線程A釋放內存,ThreadCache回收內存塊,內存大小依舊局限于256KB;
  2. 正常回收到對應的自由鏈表中;
  3. 當某條自由鏈表過長時,將其釋放給CentralCache。

哈希桶的結構:

  1. 由_freeList自由鏈表掛在對應的哈希桶位置組成;
  2. 每個哈希桶與其懸掛自由鏈表中的內存塊存在映射規則(如圖)。

3.如何劃分哈希桶布局-完成ThreadCache中的小組件

比如在向 ThreadCache 申請內存時,我們申請size個字節的內存,需要去有該size字節的對應的哈希桶中查找是否有空閑的內存塊,如果有則使用(如果沒有,則要去CentralCache中申請內存塊)。

因此,我們要考慮的變量一是對應的哈希桶,二是向CentralCache申請內存時,大小應該為多少。

  1. 在ThreadCache中如何通過申請size個字節,找到對應的哈希桶。
  2. 如何通過size個字節,找到向CentralCache申請的內存塊大小(找size對齊后的alignSize)。

256KB劃分情況如下(整體控制在最多10%左右的內存碎片浪費):

字節數對齊數哈希桶下標
[ 1 , 128 ]8[ 0 , 16 )
[ 128+1 , 1024 ]16[ 16 , 72 )
[ 1024+1 , 8 * 1024 ]128[ 72 , 128 )
[ 8 * 1024+1 , 64 * 1024 ]1024[ 128 , 184 )
[ 64 * 1024+1 , 256 * 1024 ]8 * 1024[ 184 , 208 )

對齊數就是我們在分配內存時最后得到的內存大小一定是對齊數的倍數,比如我們申請129個字節,由表得知,它的對齊數是16字節,因此對齊后的大小應該是129÷16=8……1,還需要15個字節才能對齊,對齊后所需要的內存大小為129+15 = 144,也就是說浪費了15個字節。在前篇文章中,我們也已經介紹過內碎片的存在,這份浪費的15字節就是內碎片。

我們可以通過公式:

浪費率 = 浪費的字節數/對齊后的字節數;

得到 15÷144 ≈ 10%,依次類推,整體控制在10%左右的內存碎片浪費。對于1~128這個區間不做討論。

要想得到某個區間最大浪費率,我們可以通過讓分子變大,分母變小的方式來得到該區域的最大浪費率。

因此可以得到后幾組對齊數的最大浪費率:

對齊數為128,對齊后字節數為1152:127÷1152 ≈ 11.02%;

對齊數為1024,對齊后字節數為9216:1023÷9216 ≈ 11.10%;

對齊數為8*1024=8192,對齊后字節數為1152:8192÷73728 ≈ 11.11%

4.對象的映射規則

通過對齊數得到對齊后的字節數

如申請內存為14字節,在0~128的區間中對齊數為8,得到對齊后的字節數16

通過對齊數得到對應的哈希桶

如申請的內存為15字節,在0~128的區間中對齊數為8,得到(15+(8-1))/8 -1 = 1,對應的哈希桶為一號桶

將兩種映射規則放在類SizeClass中。

// 申請size字節,通過對齊數獲取對齊后的字節數alignNum/*static inline size_t _RoundUp(size_t size, size_t alignNum)      // RoundUp 的子函數
{// 找size對齊后的對齊后的字節數,也就是申請的內存大小alignNumif (size % alignNum == 0)return size;elsereturn (bytes / alignNum + 1)*alignNum;
}*/static inline size_t _RoundUp(size_t size, size_t alignNum)
{return (size + (alignNum - 1)) & ~(alignNum - 1);
}// size  的大小 size>=0  size<=256KB
static inline size_t RoundUp(size_t size)
{assert(size <= MAX_BYTES);         // MAX_BYTES = 256 * 1024;if (size <= 128){return _RoundUp(size, 8);}else if (size <= 1024){return _RoundUp(size, 16);}else if (size <= 8 * 1024){return _RoundUp(size, 128);}else if (size <= 64 * 1024){return _RoundUp(size, 1024);}else if (size <= 256 * 1024){return _RoundUp(size, 8 * 1024);}else{assert(false);// 出錯了return -1;}
}

// 對象申請的內存大小與對應的哈希桶之間的映射//方法一
// 申請內存與對應的哈希桶之間的映射
static inline size_t _Index(size_t size, size_t align_shift)
{ return ((size + (1 << align_shift) - 1) >> align_shift) - 1; 
}static inline size_t Index(size_t size)
{assert(size <= MAX_BYTES);static int group_array[4] = { 16, 56, 56, 56 };if (size <= 128){return _Index(size, 3);}else if (size <= 1024){return _Index(size - 128, 4) + group_array[0];}else if (size <= 8 * 1024){return _Index(size - 1024, 7) + group_array[0] + group_array[1];}else if (size <= 64 * 1024){return _Index(size - 8 * 1024, 10) + group_array[0] + group_array[1] + group_array[2];}else if (size <= 256 * 1024){return _Index(size - 64 * 1024, 13) + group_array[0] + group_array[1] + group_array[2] + group_array[3];}else{assert(false);return -1;}
}//方法二
/*
static inline size_t _Index(size_t bytes, size_t align)
{if (bytes % align == 0)return bytes / align - 1;elsereturn bytes / align;
}static inline size_t Index(size_t Bytes)
{assert(Bytes <= MAX_BYTES);// 每個桶有多少個節點static int group_array[4] = { 16, 56, 56, 56 };if (Bytes <= 128) {return _Index(Bytes, 8);}else if (Bytes <= 1024) {return _Index(Bytes - 128, 16) + group_array[0];}else if (Bytes <= 8 * 1024) {return _Index(Bytes - 1024, 128) + group_array[1] + group_array[0];}else if (Bytes <= 64 * 1024) {return _Index(Bytes - 8 * 1024, 1024) + group_array[2] + group_array[1] + group_array[0];}else if (Bytes <= 256 * 1024) {return _Index(Bytes - 64 * 1024, 8192) + group_array[3] + group_array[2] + group_array[1] + group_array[0];}else {assert(false);}return -1;
}
*/

需要注意的是,SizeClass類當中的成員函數最好設置為靜態成員函數,否則我們在調用這些函數時就需要通過對象去調用,并且對于這些可能會頻繁調用的函數,可以考慮將其設置為內聯函數。

(二)CentralCache的框架設計

CentralCache的結構與ThreadCache相似,映射關系也相似,但是由圖得知仍然存在不同。

功能:CentralCache作為所有線程共享的內存池,確保了內存資源在多個線程之間的公平分配和高效利用。也就是整個進程中只有一個CentralCache,可以通過設計一個單例模式來實現,由于每次訪問的都是共同的CentralCache,在進行讀寫的情況下,都要對其加鎖處理(因為線程1在讀的時候,可能線程2正在申請釋放內存塊,導致線程1獲取到的內存塊并不是可利用的空閑內存)

結構:采用哈希桶結構,但映射規則與ThreadCache相同。每個哈希桶下懸掛的是名為Span的變量,而每個span中都會指向一個自由鏈表,自由鏈表下懸掛的內存塊大小與它的桶號一一對應。

內存管理:CentralCache負責為各個線程的ThreadCache分配內存。當ThreadCache中的內存不足時,會向CentralCache申請新的內存塊。CentralCache會在適當的時候回收ThreadCache中不再使用的內存,以避免內存浪費和碎片化。

CentralCache與PageCache的框架結構中具有相似的Span結構,這也意味著又有一個共同點設計在Common.h文件中,在這里我們就不像ThreadCache中的自由鏈表與哈希桶的映射關系一樣單獨拿出來解釋,因為我們對Span的設計,會隨著各層之間的聯系發生變化,比如增加多個變量,如果在這里就進行介紹,在后續的講解中可能會遺忘。

1.CentralCache申請內存

  1. ThreadCache向CentralCache申請內存,首先還是先找到對應哈希桶中的空閑內存塊,也就是說要分析每個Span,找到非空Span后,將一塊(或一批量)內存塊掛在ThreadCache對應自由鏈表下;
  2. 當對應哈希桶下,沒有非空的Span時,意味著ThreadCache無法獲取到空閑內存塊,因此CentralCache需要向上一層的PageCache中申請空閑的Span利用,申請到這份NewSpan后,還需要對這份NewSpan做處理,將其劃分為合適大小的內存塊,懸掛在Span中的自由鏈表下進行管理,然后就可以將這份新的非空Span中的內存塊劃分給ThreadCache;
  3. 需要注意的是我們在申請內存訪問CentralCache時,需要加上對應的桶鎖,避免其他同時間對CentralCache的操作影響我們的結果。

2.CentralCache釋放內存

  1. CentralCache釋放內存有兩個大方向,一個是從ThreadCache中回收內存塊,一個是回收后的內存塊使得部分Span處于完全空閑狀態,可以釋放給PageCache,增加空間利用;
  2. 回收從ThreadCache中釋放的內存:當ThreadCache中某一自由鏈表下懸掛的空閑內存塊過多時(什么樣的判斷標準),會由CentralCache進行回收,這些內存塊都是有對應哈希桶中的Span下的內存塊分配出去的,但是屬于哪個Span是不確定的,因此在我們創建Span時就可以將內存塊的地址與Span做映射關系,確保回收回的內存塊可以找到對應的Span,至于怎么設計這份映射關系,后面再討論;
  3. 將完全空閑的Span釋放給PageCache:如何判斷這個Span是完全空閑的?也就是它管理的自由鏈表中沒有被使用的內存塊,設計一個_usecount變量進行管理,當分配出去對象時,_usecount就++,當_usecount為0時就表示所有對象回到了Span中,則將Span釋放回PageCache,需要相鄰的空閑Span合并,掛在PageCache對應的哈希桶下;
  4. 但是當我們從PageCache中申請的Span時這份Span也是空閑的,如果與要釋放的Span合并起來該怎么辦?在物理空間中,無論我們怎么切分內存空間,它們在物理位置上并沒有發生變化,因此在我們查找相鄰空閑Span的地址時,可能會導致合并后的Span一部分在CentralCache中,一部分在PageCache,一部分難以訪問到的情況,因為如果與CentralCache中相鄰Span合并,這部分Span仍然處于CentralCache中并沒有被釋放到PageCache中(如下圖所示);
  5. 也就是說想要合并Span有兩個條件:空閑的Span和在PageCache中。思考:如何判斷Span時空閑Span,如何判斷Span是在PageCache中的?
  6. 將某個Span合并到PageCache中后,別忘了在CentralCache中對應的SpanList中刪除哦,不然會出錯的。

(三)PageCache的框架設計

功能:作為內存池的最上層緩存,以頁為單位存儲和分配內存。當CentralCache內存不足時,PageCache會向系統申請新的內存頁,并切割成小塊內存分配給CentralCache。當一個Span的幾個跨度頁的對象都回收以后,PageCache會回收CentralCache滿足條件的Span對象,并且合并相鄰的頁組成更大的頁,緩解內存碎片的問題。

結構:通常也采用哈希桶結構,但映射規則與ThreadCache和CentralCache不同,主要按頁號進行映射。

內存管理:PageCache負責大塊內存的分配和回收,以及與系統的內存交互。同時,也會合并相鄰的空閑頁,以減少內存碎片。

1.PageCach申請內存

  1. CentralCache從PageCache中申請內存,對應哈希桶恰好有Span,切割好內存后交給CentralCache;
  2. 向頁數更大的哈希桶中查找Span,找到并切割合適的Span給CentralCache,要記得對切割前的Span和切割后的Span要清理和掛在對應的位置。比如從100頁切割了42頁大小的Span給CentralCache,那么原先的100頁Span要從100page的哈希桶中刪除,并將58頁大小Span重新掛在58page的哈希桶中,記得更新Span的自身信息;
  3. 當PageCache中無合適的Span時,需要從系統中申請內存,通過使用brk、mmp或者是VirtualAlloc等方式從操作系統中申請128頁Span,掛在對應哈希桶中。
  4. 注意PageCache與前兩者的映射關系不同,PageCache中的第i號桶中掛的Span都是i頁內存。

2.PageCach釋放內存

  1. PageCache回收CentralCache釋放的內存,無可合并的Span,將釋放回的Span掛在對應哈希桶中;
  2. PageCache中有可合并的相鄰的Span,合并直到無相鄰Span或最大為128頁后不再合并,減少內存碎片問題
  3. PageCache釋放內存給操作系統。

二、ThreadCache(線程緩存)_內存設計

(一)處理哈希桶相關問題

首先創建一個ThreadCache哈希桶,我們需要先創建它其中的小組件。

從上列文字和圖形中,我們了解到ThreadCache是由不同內存塊組成的自由鏈表掛在對應哈希桶上的結構。

創建哈希桶中每個桶所屬的自由鏈表,由于CentralCache也是哈希桶結構,我們的自由鏈表創建可以放在一個公共的頭文件中,方便使用。將自由鏈表創建為類,管理切分好的小對象的自由鏈表。

// 這里是一個共同訪問的Common.h頭文件,會被TCMalloc不同層使用到的相同變量放在此處
#include <iostream>
#include <vector>
#include<assert.h>
using std::cout;
using std::endl;void*& NextObj(void* obj) {return *(void**)obj;
}// 創建ThreadCache和CentralCache都會使用到的_freelist
// 線程會從自由鏈表從申請到內存或釋放內存塊回到自由鏈表
// 
class FreeList
{
public:// 線程申請內存就是從對應的自由鏈表中取出內存塊(頭刪)void Push(void* obj){NextObj(obj) = _freeList;_freeList = obj;}// 線程釋放內存就是將內存塊釋放到對應的自由鏈表中(頭插)void* Pop(){assert(_freeList);	// 自由鏈表為空時,無法取出空閑內存塊void* obj = _freeList;_freeList = NextObj(_freeList);return obj;}// 判斷自由鏈表中是否存在內存bool Empty(){if (_freeList == nullptr)return true;return false;}private:void* _freeList = nullptr;};

我們現在可以通過FreeLsit創建一個哈希桶了。

接下來是對哈希桶進行管理,對于哈希桶的映射關系,如果還是不了解,去查看ThreadCache的框架設計中的講解

(二)ThreadCache–申請內存

// 放在公用頭文件中// thread cache和central cache自由鏈表哈希桶的表大小
static const int NFREELISTS = 208;// 小于 256 * 1024 向 ThreadCache 申請
static const int MAX_BYTES = 256 * 1024;
// 大于則向 PageCache 或者 堆上申請,這里我們暫時不多思考,一步一步來
  • 當內存申請size<=256KB,先獲取到線程本地存儲的ThreadCache對象,計算size映射的對齊后的對象大小和哈希桶自由鏈表下標 Index 。
  • 申請內存時,如果ThreadCache對應哈希桶中的自由鏈表**(_freeLists[index])**有空閑內存塊時,則直接Pop()一個內存對象返回。
  • 申請內存時,如果ThreadCache對應哈希桶中的自由鏈表**(_freeLists[index])**沒有空閑內存塊時,則批量從CentralCache中獲取對齊后大小的數量的對象,插入到自由鏈表并返回一個對象。
// ThreadCache.h#pragma once#include "CommonPool.h"class ThreadCache
{
private:FreeList _freeLists[NFREELISTS]; // 哈希桶public:// 申請和釋放對象內存void* ThreadAlloc(size_t size);void* ThreadFree(void* obj,size_t size);// 從CentalCache中申請size內存void* FetchFromCentralCache(size_t index,size_t size);
};

定義ThreadCache 中的成員函數,并且完善其他類。

/* 申請內存 */
/* ThreadCache.cpp */
void* ThreadCache::ThreadAlloc(size_t size)
{// size 一定是小于等于256KB,且申請獲得的空間大小一定是向上對齊的assert(size <= MAX_BYTES);// 根據對象申請的大小,在對應的哈希桶中找到對應的內存塊// 如果有就直接使用,如果沒有就需要先從CentralCache中申請對應內存塊給ThreadCachesize_t alignBytes = SizeClass::RoundUp(size);size_t index = SizeClass::Index(size);// 接下來申請內存,注意我們要在對應的哈希桶中找到空閑的內存塊使用if (!_freeLists[index].Empty()){return _freeLists[index].Pop();// 從該哈希桶中的存放空閑內存塊的自由鏈表中取出空閑內存塊給對象}else{// 如果對應的哈希桶中沒有空閑內存塊,需要向centralcache中申請內存塊分配給threadcache// 申請到的內存塊要分配給以alignBytes為對齊數,放在在下標位index的哈希桶中return FetchFromCentralCache(index, alignBytes);}
}

關于 FetchFromCentralCache(index, alignSize); ,需要聯系到 CentralCache ,后續再講解。

(三)線程局部存儲–無鎖訪問ThreadCache

Thread Local Storage(線程局部存儲)TLS

線程本地存儲(Thread Local Storage) - 坦坦蕩蕩 - 博客園

如果一個變量是全局的,那么所有線程訪問的是同一份,某一個線程對其修改會影響其他所有線程。如果希望每個線程訪問到這個變量,并且不會影響其他線程中的這個變量,那么我們該怎么創建?

我們的高并發內存池就是 每個線程獨有一個ThreadCache線程緩存,互不影響,也無需加鎖,那么如何創建每個線程各自的ThreadCache?

因此我們引入一個概念:

如果我們需要一個變量在每個線程中都能訪問,并且這個變量在每個線程中互不影響,這就是TLS。

線程局部存儲TLS(Thread Local Storage)是一種變量的存儲方法,這個變量在它所在的線程內是全局可訪問的,但是不能被其他線程訪問到,這樣就保持了數據的線程獨立性。

而熟知的全局變量,是所有線程都可以訪問的,這樣就不可避免需要鎖來控制,增加了控制成本和代碼復雜度。

TLS依情況而定有Windows下的,也有Linux下的。既有動態也有靜態,這里我們選擇靜態TLS:

_declspec(thread) DWORD data=0;

聲明了_declspec(thread)的變量,會為每一個線程創建一個單獨的拷貝。

// TLS thread local storage(線程局部存儲、線程本地存儲)
// 通過TLS 每個線程無鎖的獲取自己的專屬的ThreadCache對象
static _declspec(thread) ThreadCache* pTLSThreadCache = nullptr;// thread 用于聲明一個線程本地變量, 
// _declspec(thread)的前綴是Microsoft添加給Visual C++編譯器的一個修改符。

上列代碼在ThreadCache.h頭文件中聲明了為每個線程創建一份ThreadCache。但也僅僅是聲明,并不是為每個線程創建了屬于自己的ThreadCache。

下列代碼是對每個線程創建屬于自己的ThreadCache,當該線程調用相關申請內存的接口時才會創建自己的ThreadCache。

//通過TLS,每個線程無鎖的獲取自己專屬的ThreadCache對象
if (pTLSThreadCache == nullptr)
{pTLSThreadCache = new ThreadCache;
}

(四)ThreadCache–封裝使用及測試

1.對ThreadCache對象的使用進行封裝

在使用過TLS后,對每一個線程創建一份ThreadCache時,將申請(釋放)的功能進行再一次的封裝,有利于后續對象申請(釋放)空間直接使用。

// 我們創建一個新的頭文件 Concurrent.h
// 在這里將threadcache封裝起來以便對象申請釋放內存時更方便使用
#include "Common.h"
#include "ThreadCache.h"static void* concurrentAlloc(size_t size)
{// 為每一個線程申請屬于他自己的threadcacheif (PTLSThreadCache == nullptr){PTLSThreadCache = new ThreadCache;// 這里可以連同后面的測試 編寫一段代碼// cout<< "Thread x creates a threadcache object."<< endl	;}// get_id()它用于獲取當前線程的唯一標識符。// 每個線程在程序執行期間都有一個唯一的標識符,這個函數返回一個表示當前線程ID的對象。//cout << std::this_thread::get_id() << ":" << pTLSThreadCache << endl;// 調用threadcache申請內存return PTLSThreadCache->Allocate(size);
}

2.測試每個線程有屬于自己的ThreadCache對象

在Test.cpp文件中,我們對上列代碼進行測試,觀察線程之間使用同一變量是否會互相影響,測試每一個線程是否獲得了屬于自己的ThreadCache對象。

// Test.cpp#include <thread>
void Alloc1()
{for (size_t i = 0; i < 5; ++i){//申請6字節大小的內存對象void* ptr = concurrentAlloc(6);}
}void Alloc2()
{for (size_t i = 0; i < 5; ++i){//申請9字節大小的內存對象void* ptr = concurrentAlloc(9);}
}void TextpTLSThreadCache()
{std::thread t1(Alloc1);std::thread t2(Alloc2);t1.join();t2.join();
}int main()
{//測試每個線程是否有屬于自己的ThreadCacheTextpTLSThreadCache();return 0;
}

這種錯誤通常是因為在多個源文件中定義了相同的函數或變量,導致鏈接器在鏈接階段發現重復定義而報錯。造成原因:重復定義,頭文件包含,靜態庫沖突。

可以通過使用 inline 關鍵字、static 關鍵字、分離聲明和定義以及檢查頭文件保護機制來解決這個問題。

測試結果:

調試過程中可以看到兩個線程互不影響地運行著。

從測試結果看,兩個線程是同時進行的,并且互不影響。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/66963.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/66963.shtml
英文地址,請注明出處:http://en.pswp.cn/web/66963.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JAVA 使用反射比較對象屬性的變化,記錄修改日志。使用注解【策略模式】,來進行不同屬性枚舉值到中英文描述的切換,支持前端國際化。

1.首先定義一個接口&#xff0c;接口中有兩個方法&#xff0c;分別是將屬性轉換成英文描述和中文描述。 其實就是將數據庫中記錄的 0 1 &#xff0c;轉換成后面的描述 這邊定義了中文轉換為默認方法&#xff0c;是因為有些屬性不需要進行中文轉換&#xff0c;或者該屬性的枚舉…

webrtc入門系列(五)amazon-kinesis-video-streams-webrtc-sdk-c編譯

《webrtc入門系列&#xff08;一&#xff09;easy_webrtc_server 入門環境搭建》 《webrtc入門系列&#xff08;二&#xff09;easy_webrtc_server 入門example測試》 《webrtc入門系列&#xff08;三&#xff09;云服務器coturn環境搭建》 《webrtc入門系列&#xff08;四&…

AIGC大模型詳解(ChatGPT,Cursor,豆包,文心一格)

定義與概念 AIGC&#xff08;AI Generated Content&#xff09;大模型是基于人工智能技術&#xff0c;具有海量參數、強大算力支持&#xff0c;能處理和生成多種類型內容的深度學習模型。可自主學習數據中的模式和規律&#xff0c;生成文本、圖像、音頻等內容&#xff0c;如Ch…

.NET9增強OpenAPI規范,不再內置swagger

ASP.NETCore in .NET 9.0 OpenAPI官方文檔ASP.NET Core API 應用中的 OpenAPI 支持概述 | Microsoft Learnhttps://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/openapi/overview?viewaspnetcore-9.0https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/ope…

第38周:貓狗識別 (Tensorflow實戰第八周)

目錄 前言 一、前期工作 1.1 設置GPU 1.2 導入數據 輸出 二、數據預處理 2.1 加載數據 2.2 再次檢查數據 2.3 配置數據集 2.4 可視化數據 三、構建VGG-16網絡 3.1 VGG-16網絡介紹 3.2 搭建VGG-16模型 四、編譯 五、訓練模型 六、模型評估 七、預測 總結 前言…

我的2024年年度總結

序言 在前不久&#xff08;應該是上周&#xff09;的博客之星入圍賽中鎩羽而歸了。雖然心中頗為不甘&#xff0c;覺得這一年兢兢業業&#xff0c;每天都在發文章&#xff0c;不應該是這樣的結果&#xff08;連前300名都進不了&#xff09;。但人不能總抱怨&#xff0c;總要向前…

Trimble三維激光掃描-地下公共設施維護的新途徑【滬敖3D】

三維激光掃描技術生成了復雜隧道網絡的高度詳細的三維模型 項目背景 紐約州北部的地下通道網絡已有100年歷史&#xff0c;其中包含供暖系統、電線和其他公用設施&#xff0c;現在已經開始顯露出老化跡象。由于安全原因&#xff0c;第三方的進入受到限制&#xff0c;在沒有現成紙…

QT 中 UDP 的使用

目錄 一、UDP 簡介 二、QT 中 UDP 編程的基本步驟 &#xff08;一&#xff09;包含頭文件 &#xff08;二&#xff09;創建 UDP 套接字對象 &#xff08;三&#xff09;綁定端口 &#xff08;四&#xff09;發送數據 &#xff08;五&#xff09;接收數據 三、完整示例代…

開源鴻蒙開發者社區記錄

lava鴻蒙社區可提問 Laval社區 開源鴻蒙項目 OpenHarmony 開源鴻蒙開發者論壇 OpenHarmony 開源鴻蒙開發者論壇

Git上傳了秘鑰如何徹底修改包括歷史記錄【從安裝到實戰詳細版】

使用 BFG Repo-Cleaner 清除 Git 倉庫中的敏感信息 1. 背景介紹 在使用 Git 進行版本控制時&#xff0c;有時會不小心將敏感信息&#xff08;如 API 密鑰、密碼等&#xff09;提交到倉庫中。即使后續刪除&#xff0c;這些信息仍然存在于 Git 的歷史記錄中。本文將介紹如何使用…

多層 RNN原理以及實現

數學原理 多層 RNN 的核心思想是堆疊多個 RNN 層&#xff0c;每一層的輸出作為下一層的輸入&#xff0c;從而逐層提取更高層次的抽象特征。 1. 單層 RNN 的數學表示 首先&#xff0c;單層 RNN 的計算過程如下。對于一個時間步 t t t&#xff0c;單層 RNN 的隱藏狀態 h t h_t…

RNA 測序技術概覽(RNA-seq)

前言 轉錄組測序&#xff08;RNA-seq&#xff09;是當下最流行的二代測序&#xff08;NGS&#xff09;方法之一&#xff0c;使科研工作者實現在轉錄水平上定量、定性的研究&#xff0c;它的出現已經革命性地改變了人們研究基因表達調控的方式。然而&#xff0c;轉錄組測序&…

C語言練習(16)

猴子吃桃問題。猴子第一天摘下若干個桃子&#xff0c;當即吃了一半&#xff0c;還不過癮&#xff0c;又多吃了一個。第二天早上又將剩下的桃子吃掉一半&#xff0c;又多吃了一個。以后每天早上都吃了前一天剩下的一半加一個。到第10天早上想再吃時&#xff0c;見只剩一個桃子了…

【機器學習】自定義數據集使用框架的線性回歸方法對其進行擬合

一、使用框架的線性回歸方法 1. 基礎原理 在自求導線性回歸中&#xff0c;我們需要先自定義參數&#xff0c;并且需要通過數學公式來對w和b進行求導&#xff0c;然后在反向傳播過程中通過梯度下降的方式來更新參數&#xff0c;從而降低損失值。 2. 實現步驟 ① 散點輸入 有一…

pytest執行報錯:found no collectors

今天在嘗試使用pytest運行用例的時候出現報錯&#xff1a;found no collectors&#xff1b;從兩個方向進行排查&#xff0c;一是看文件名和函數名是不是符合規范&#xff0c;命名要是"test_*"格式&#xff1b;二是是否存在修改文件名的情況&#xff0c;如果修改過文件…

mysql-06.JDBC

目錄 什么是JDBC: 為啥存在JDBC: JDBC工作原理&#xff1a; JDBC的優勢&#xff1a; 下載mysql驅動包&#xff1a; 用java程序操作數據庫 1.創建dataSource: 2.與服務端建立連接 3.構造sql語句 4.執行sql 5.關閉連接&#xff0c;釋放資源 參考代碼&#xff1a; 插…

微信小程序wxs實現UTC轉北京時間

微信小程序實現UTC轉北京時間 打臉一刻&#xff1a;最近在迭代原生微信小程序&#xff0c;好一段時間沒寫原生的&#xff0c;有點不習慣&#xff1b; 咦&#xff0c;更新數據咋不生效呢&#xff1f;原來還停留在 this.xxx&#xff1b; 喲&#xff0c;事件又沒反應了&#xff1f…

機器學習-線性回歸(對于f(x;w)=w^Tx+b理解)

一、&#x1d453;(&#x1d499;;&#x1d498;) &#x1d498;T&#x1d499;的推導 學習線性回歸&#xff0c;我們那先要對于線性回歸的表達公示&#xff0c;有所認識。 我們先假設空間是一組參數化的線性函數&#xff1a; 其中權重向量&#x1d498; ∈ R&#x1d437; …

R語言學習筆記之語言入門基礎

一、R語言基礎 快速熟悉R語言中的基本概念&#xff0c;先入個門。 1、運算符 運算符含義例子加1 1-減3 - 2*乘3 * 2/除9 / 3^(**)乘方2 ^ 3 2 ** 3%%取余5 %% 2%/%取整5 %/% 2 2、賦值符號 等號a 1三者等價&#xff1a;把1賦值給變量a左箭頭<?a <- 1右箭頭?&g…

計算機網絡三張表(ARP表、MAC表、路由表)總結

參考&#xff1a; 網絡三張表&#xff1a;ARP表, MAC表, 路由表&#xff0c;實現你的網絡自由&#xff01;&#xff01;_mac表、arp表、路由表-CSDN博客 網絡中的三張表&#xff1a;ARP表、MAC表、路由表 首先要明確一件事&#xff0c;如果一個主機要發送數據&#xff0c;那么必…