內存優化:從堆分配到零拷貝的終極重構

引言

在現代高性能軟件開發中,內存管理往往是性能優化的關鍵戰場。頻繁的堆內存分配(new/delete)不僅會導致性能下降,還會引發內存碎片化問題,嚴重影響系統穩定性。本文將深入剖析高頻調用模塊中堆分配泛濫導致的性能塌方問題,并展示如何通過多種技術手段實現內存優化。

通過本文,讀者將學習到:

  1. 如何診斷和分析內存碎片問題
  2. 內存池預分配技術的實現原理與應用
  3. 智能指針的性能優化技巧
  4. move語義的底層實現與零拷貝數據傳輸
  5. 自定義分配器(allocator)的設計方法

文章大綱

  1. 堆分配的性能代價與診斷
    • new/delete的隱藏成本
    • 內存碎片化問題分析
    • Valgrind工具鏈實戰
  2. 內存池預分配技術
    • 內存池設計原理
    • 實現高性能對象池
    • 內存池的線程安全考量
  3. 智能指針優化策略
    • std::make_shared的優勢分析
    • 控制塊(control block)的內存布局
    • 引用計數的性能影響
  4. 零拷貝與move語義
    • move語義的匯編層解析
    • 完美轉發(perfect forwarding)實現
    • 零拷貝數據傳輸案例
  5. 自定義分配器實戰
    • 標準庫兼容的allocator接口
    • 內存對齊(alignment)處理
    • 性能對比測試

1. 堆分配的性能代價與診斷

new/delete的隱藏成本

堆內存分配看似簡單的操作,實際上包含多個隱藏步驟:

// 看似簡單的new操作背后
void* operator new(size_t size) {void* p = malloc(size);       // 1. 向操作系統申請內存if (p == nullptr) {           // 2. 檢查分配是否成功throw std::bad_alloc();   // 3. 失敗時拋出異常}return p;                     // 4. 返回分配的內存
}

每次new操作平均需要100ns以上的時間,在高頻調用場景下,這將成為性能瓶頸。更糟糕的是,頻繁的分配釋放會導致內存碎片化。

內存碎片化問題分析

內存碎片分為兩種類型:

  • ??外部碎片??:空閑內存分散在不連續的位置,無法滿足大塊內存請求
  • ??內部碎片??:分配的內存塊比實際需要的更大,導致浪費
65%35%內存碎片類型占比外部碎片內部碎片

Valgrind

Valgrind是一個基于動態二進制插樁(DBI)技術的開源內存調試工具,主要用于檢測C/C++程序中的內存泄漏、非法訪問、未初始化使用等內存問題。其核心工具Memcheck通過模擬CPU環境,在程序運行時插入檢測代碼,攔截所有內存操作(如malloc、free、new、delete等),并維護兩個全局表——Valid-Address表(記錄地址合法性)和Valid-Value表(跟蹤值初始化狀態)來驗證每次內存訪問的有效性。程序結束時,Valgrind會分析未釋放的內存塊及其分配調用棧,生成詳細的泄漏報告(如"definitely lost"或"possibly lost"),同時能檢測越界讀寫、重復釋放等問題。盡管其運行時性能損耗較大(降低10-50倍速度),但無需修改源碼即可實現深度檢測,是開發階段排查內存問題的利器。

Valgrind是強大的內存分析工具,可以檢測內存泄漏和碎片問題:

valgrind --tool=memcheck --leak-check=full ./your_program

關鍵指標解讀:

  • ??definitely lost??:確認的內存泄漏
  • ??indirectly lost??:間接泄漏(如數據結構中的泄漏)
  • ??possibly lost??:可能的內存泄漏
  • ??still reachable??:程序結束時仍可訪問的內存

2. 內存池預分配技術

內存池設計原理

內存池(Memory Pool)是一種預先分配并管理固定大小內存塊的高效內存管理技術。其核心原理是程序啟動時一次性向系統申請一大塊連續內存(稱為"池"),將其分割為多個等長的內存塊組成鏈表。當程序需要內存時,直接從池中分配現成的塊,避免了頻繁調用malloc/new的系統開銷;釋放時也不是真正返還系統,而是將塊重新鏈入空閑鏈表供復用。這種設計顯著減少了內存碎片,尤其適合頻繁申請/釋放小對象的場景(如網絡連接、游戲對象),通過以空間換時間的策略,既提升了分配速度(O(1)O(1)O(1)時間復雜度),又保證了內存訪問的局部性。典型的實現會維護空閑塊指針,分配時移動指針并返回地址,釋放時只需將內存塊插回鏈表。

MemoryPool
+char* m_pool
+size_t m_size
+size_t m_used
+allocate(size_t size)
+deallocate(void* ptr) : void
+~MemoryPool()

實現高性能對象池

以下是線程安全對象池的實現示例:

template <typename T>
class ObjectPool {
public:ObjectPool(size_t chunkSize = 32) : m_chunkSize(chunkSize) {expandPool();}T* acquire() {std::lock_guard<std::mutex> lock(m_mutex);if (m_freeList.empty()) {expandPool();}T* obj = m_freeList.back();m_freeList.pop_back();return new (obj) T(); // placement new}void release(T* obj) {std::lock_guard<std::mutex> lock(m_mutex);obj->~T(); // 顯式調用析構m_freeList.push_back(obj);}private:void expandPool() {size_t size = sizeof(T) * m_chunkSize;char* chunk = static_cast<char*>(::operator new(size));m_chunks.push_back(chunk);for (size_t i = 0; i < m_chunkSize; ++i) {m_freeList.push_back(reinterpret_cast<T*>(chunk + i * sizeof(T)));}}std::vector<char*> m_chunks;std::vector<T*> m_freeList;std::mutex m_mutex;size_t m_chunkSize;
};

內存池的線程安全考量

內存池的線程安全設計通常通過同步機制(如互斥鎖、自旋鎖或原子操作)來保證多線程環境下的正確分配和釋放。核心原則是確保對空閑鏈表等共享數據結構的操作具有原子性:分配內存時需要加鎖獲取空閑塊并移動指針,釋放內存時同樣加鎖將塊插回鏈表。細粒度鎖(如每個內存塊或子池獨立加鎖)可提升并發性能,但會增加實現復雜度;無鎖設計(如CAS原子操作管理鏈表指針)能徹底避免線程阻塞,但對算法要求較高。此外還需注意"偽共享"問題(頻繁操作的指針避免位于同一緩存行),以及線程局部緩存(Thread-Local Storage)的運用——每個線程維護獨立的小內存池,僅當不足時才訪問全局池,可大幅減少鎖競爭。

多線程環境下,內存池需要考慮:

  1. ??鎖粒度??:細粒度鎖 vs 全局鎖
  2. ??線程局部存儲??(TLS):減少鎖爭用
  3. ??無鎖設計??:原子操作實現
線程請求內存
線程局部內存池有空間?
從TLS分配
獲取全局鎖
從全局池分配大塊
分割到TLS

3. 智能指針優化策略

std::make_shared的優勢分析

std::make_shared 相比直接使用 std::shared_ptr 構造函數主要有兩大優勢:??內存效率??和??異常安全??。首先,make_shared 會一次性分配內存,既存儲對象本身,又存儲控制塊(引用計數等),而直接構造 shared_ptr 則需要兩次獨立分配(對象和控制塊),減少了內存碎片和開銷。其次,make_shared 是異常安全的,如果對象構造過程中拋出異常,不會留下懸空的裸指針,而直接構造 shared_ptr 時若 new 成功但 shared_ptr 構造失敗,則會導致內存泄漏。此外,make_shared 語法更簡潔,避免了顯式 new 操作,符合現代 C++ 的 RAII 原則。

// 傳統方式:兩次堆分配
std::shared_ptr<Widget> sp1(new Widget);// 優化方式:單次堆分配
auto sp2 = std::make_shared<Widget>();

內存布局對比:

new Widget
Widget對象
new ControlBlock
引用計數等
make_shared
連續內存塊
Widget對象
ControlBlock

控制塊的內存布局

std::shared_ptr 的控制塊是一個動態分配的內存結構,通常包含兩個引用計數器strong_refsweak_refs)、指向被管理對象的指針ptr)、以及可選的刪除器deleter)和分配器allocator)。強引用計數strong_refs)管理對象的生命周期,當減至零時調用析構函數;弱引用計數weak_refs)僅控制控制塊本身的生命周期,當強弱引用均歸零時才釋放控制塊。控制塊通常位于對象內存附近(若使用 std::make_shared 則可能與對象連續存儲),但獨立于 shared_ptr 實例本身,所有共享同一對象的 shared_ptr 副本都通過原子操作修改同一控制塊,確保線程安全。這種設計使得引用計數的增減和對象析構具有原子性,但也帶來了循環引用的風險(需配合 std::weak_ptr 解決)。

std::shared_ptr的控制塊包含:

  • 強引用計數
  • 弱引用計數
  • 刪除器(deleter)
  • 分配器(allocator)
  • 指向對象的指針

引用計數的性能影響

引用計數(Reference Counting)雖然簡化了內存管理,但會帶來顯著的性能開銷:每次拷貝、賦值或銷毀智能指針時都需要執行??原子操作??修改引用計數,這會導致??緩存一致性同步??(CPU核心間頻繁同步緩存行),在高并發場景下可能引發??競爭瓶頸??。此外,循環引用會導致對象無法釋放(內存泄漏),而弱引用(weak_ptr)的引入又增加了額外的控制塊訪問開銷。對于頻繁傳遞的小對象,引用計數的開銷可能超過對象本身的操作成本,此時更適合使用移動語義(如unique_ptr)或棧分配。優化手段包括局部性優化(如make_shared合并內存分配)、減少不必要的拷貝,或在確定性場景中改用作用域指針(如RAII管理)。

引用計數操作需要原子操作,在多核CPU上可能引發緩存一致性問題:

; x86匯編示例
lock inc dword [rcx]  ; 原子遞增操作

優化策略:

  1. 減少std::shared_ptr的拷貝
  2. 使用std::move轉移所有權
  3. 考慮std::weak_ptr打破循環引用

4. 零拷貝與move語義

move語義的匯編層解析

move語義的本質是資源所有權的轉移,而非數據的物理移動。

Move語義在匯編層面的本質是??避免不必要的內存拷貝??,通過將源對象的資源指針/句柄直接轉移給目標對象實現高效傳遞。

std::string為例:傳統拷貝構造在匯編中會調用memcpy復制堆內存(生成mov指令序列),而move構造僅傳遞內部指針(如mov rax, [src]將堆地址存入目標對象,并置空源對象指針如mov [src], 0)。

關鍵區別在于move操作不觸發資源實際復制,僅重組指針所有權,其匯編代碼通常僅包含寄存器操作(如xchg)和指針清零,無堆內存訪問(如call malloc)。

編譯器對右值引用(T&&)的優化會消除臨時對象,最終生成的匯編指令數可能比拷貝少一個數量級,尤其在傳遞容器(如std::vector)時,move僅交換3個指針(首/尾/容量),而拷貝需遍歷所有元素。

以下代碼展示move前后的變化:

std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1);

對應的匯編偽代碼:

; v1的原始狀態
mov rdi, [v1._M_start]
mov rsi, [v1._M_finish]
mov rdx, [v1._M_end_of_storage]; move操作后
mov [v2._M_start], rdi
mov [v2._M_finish], rsi
mov [v2._M_end_of_storage], rdx
xor edi, edi
mov [v1._M_start], rdi
mov [v1._M_finish], rdi
mov [v1._M_end_of_storage], rdi

完美轉發實現

完美轉發(Perfect Forwarding)是 C++11 引入的核心技術,通過??右值引用??(T&&)和 std::forward 實現函數模板將參數??原樣轉發??給其他函數,保留其值類別(左值/右值)和 const 屬性。

其本質是引用折疊規則T& &T&T&& &&T&&)與模板類型推導的配合:當模板參數 T 接收左值時推導為 T&,接收右值時推導為 T&&std::forward 則根據 T 的實際類型決定轉發為左值(static_cast<T&>)或右值(static_cast<T&&>)。

template <typename T>
void wrapper(T&& arg) {target(std::forward<T>(arg));
}

模板類型 T 推導:

  • 左值參數:T推導為T&,T&&為T&(引用折疊
  • 右值參數:T推導為T,T&&為T&&

典型應用場景是工廠函數或包裝器(如 emplace_back),確保參數在多層傳遞中保持原始語義,避免不必要的拷貝或丟失移動機會。例如 logAndCreate(T&& arg)arg 完美轉發給構造函數時,若原始參數是右值則觸發移動語義,左值則保持拷貝,實現零開銷抽象。

零拷貝數據傳輸案例

網絡編程中的零拷貝示例:

// 傳統方式:多次拷貝
void sendPacket(const std::string& data) {char* buffer = new char[data.size()];std::copy(data.begin(), data.end(), buffer);socket.write(buffer, data.size());delete[] buffer;
}// 零拷貝方式
void sendPacket(std::string&& data) {socket.write(data.data(), data.size());// 無需拷貝,直接使用內部緩沖區
}

這段代碼展示了??零拷貝優化??的核心思想:通過移動語義避免不必要的數據復制。傳統方式中,sendPacket 接收 const std::string& 時無法修改源數據,必須分配新緩沖區并逐字節拷貝(std::copy),導致兩次內存操作(堆分配+復制)。而零拷貝版本接收右值引用(std::string&&),直接訪問源字符串的內部緩沖區(data.data()),由于調用者已聲明放棄所有權(如傳遞臨時對象或顯式 std::move),函數可以安全"竊取"其內存資源而不破壞語義。這不僅省去了堆分配和復制的開銷(從 O(n) 降至 O(1)),還保持了原始數據的連續性,尤其對大容量數據(如網絡包)性能提升顯著。關鍵點在于移動后的字符串處于有效但未定義狀態,適合立即銷毀或重新賦值的場景。

5. 自定義分配器實戰

標準庫兼容的allocator接口

標準庫兼容的分配器(Allocator)接口是一組用于內存管理的??泛型契約??,要求實現 allocatedeallocate 等核心方法,并滿足 rebind 模板機制以適配不同類型。其核心規范包括:1) 類型定義(如 value_typepointer);2) 內存操作allocate(n) 分配未構造內存,deallocate(p, n) 釋放時需大小匹配);3) 構造/析構工具construct(p, args)destroy(p),C++20 后通常省略);4) 傳播特性(通過 propagate_on_container_* 類型控制容器拷貝時的分配器行為)。

標準分配器需保證線程安全,且允許自定義實現(如內存池或共享內存分配器),只要滿足接口約束即可無縫替換 std::allocator,使容器(如 vector)自動采用定制策略。關鍵是通過統一接口解耦內存分配與對象生命周期管理,支持從默認 new/delete 到復雜內存模型的靈活擴展。

符合C++標準的allocator需要實現以下關鍵接口:

template <typename T>
class CustomAllocator {
public:using value_type = T;CustomAllocator() noexcept = default;template <typename U>CustomAllocator(const CustomAllocator<U>&) noexcept {}T* allocate(size_t n) {return static_cast<T*>(::operator new(n * sizeof(T)));}void deallocate(T* p, size_t) {::operator delete(p);}template <typename U>bool operator==(const CustomAllocator<U>&) { return true; }template <typename U>bool operator!=(const CustomAllocator<U>&) { return false; }
};

內存對齊處理

內存對齊(Memory Alignment)是指數據在內存中的存儲地址按照特定字節邊界(如4、8、16字節)排列,以匹配CPU訪問內存的最優粒度

現代處理器通常要求特定類型的數據(如double或SSE指令操作數)必須對齊到其大小的整數倍地址,否則可能引發性能下降(如x86上的非對齊訪問懲罰)或直接錯誤(如ARM的硬件異常)。

編譯器默認通過插入填充字節(Padding)實現結構體成員對齊(如struct { char c; int i; }會在c后填充3字節),也可用alignas關鍵字顯式指定對齊方式(如alignas(16) float arr[4])。

對齊處理的關鍵在于平衡內存利用率與CPU訪問效率,高性能場景(如SIMD或緩存行優化)常需手動調整對齊策略,而C++11引入的alignofstd::aligned_storage等工具則提供了跨平臺的對齊控制能力。

template <size_t Alignment>
class AlignedAllocator {static_assert(Alignment > 0, "Alignment must be positive");void* allocate(size_t size) {return aligned_alloc(Alignment, size);}void deallocate(void* p) {free(p);}
};

性能對比

系統 malloc 作為通用內存分配器,依賴操作系統管理,適合通用場景但性能較低(頻繁系統調用、鎖競爭和內存碎片)。

??內存池??通過預分配和復用內存塊,減少系統調用和碎片,提升分配速度,但仍有全局鎖開銷。

?無鎖內存池??基于原子操作(如CAS)實現并發安全,兼顧多線程性能與內存利用率,但實現復雜且需處理ABA問題。

??TLS內存池??(線程本地存儲)為每個線程維護獨立內存池,徹底消除鎖競爭,適合高頻分配場景,但可能造成線程間內存利用率不均。

綜合來看,性能排序通常為:TLS內存池 > 無鎖內存池 > 普通內存池 > 系統 malloc,但選擇需權衡場景特性(如線程數、分配頻率和實時性要求)。

結論

通過內存池預分配、智能指針優化和move語義的應用,我們可以顯著減少高頻調用場景下的內存分配開銷。關鍵優化點包括:

  1. 使用內存池減少系統調用和碎片化
  2. 優先選擇std::make_shared創建智能指針
  3. 利用move語義實現零拷貝數據傳輸
  4. 為特定場景設計自定義分配器

實際項目中,建議結合性能分析工具(如perf、VTune)進行量化評估,確保優化措施確實帶來預期收益。

參考資料

  1. C++標準庫allocator要求
  2. Intel TBB內存分配器
  3. C++ Core Guidelines: 資源管理

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

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

相關文章

【GoLang#2】:基礎入門(工具鏈 | 基礎語法 | 內置函數)

前言&#xff1a;Go 的一些必備知識 1. Go 語言命名 Go的函數、變量、常量、自定義類型、包(package)的命名方式遵循以下規則&#xff1a; 首字符可以是任意的Unicode字符或者下劃線剩余字符可以是Unicode字符、下劃線、數字字符長度不限 Go 語言代碼風格及開發事項代碼每一行結…

Bert項目--新聞標題文本分類

目錄 技術細節 1、下載模型 2、config文件 3、BERT 文本分類數據預處理流程 4、對輸入文本進行分類 5、計算模型的分類性能指標 6、模型訓練 7、基于BERT的文本分類預測接口 問題總結 技術細節 1、下載模型 文件名稱--a0_download_model.py 使用 ModelScope 庫從模型倉…

sendfile系統調用及示例

好的&#xff0c;我們繼續學習 Linux 系統編程中的重要函數。這次我們介紹 sendfile 函數&#xff0c;它是一個高效的系統調用&#xff0c;用于在兩個文件描述符之間直接傳輸數據&#xff0c;通常用于將文件內容發送到網絡套接字&#xff0c;而無需將數據從內核空間復制到用戶空…

數據結構習題--刪除排序數組中的重復項

數據結構習題–刪除排序數組中的重復項 給你一個 非嚴格遞增排列 的數組 nums &#xff0c;請你 原地 刪除重復出現的元素&#xff0c;使每個元素 只出現一次 &#xff0c;返回刪除后數組的新長度。元素的 相對順序 應該保持 一致 。然后返回 nums 中唯一元素的個數。 方法&…

Docker的容器設置隨Docker的啟動而啟動

原因也比較簡單&#xff0c;在docker run 的時候沒有設置–restartalways參數。 容器啟動時&#xff0c;需要增加參數 –restartalways no - 容器退出時&#xff0c;不重啟容器&#xff1b; on-failure - 只有在非0狀態退出時才從新啟動容器&#xff1b; always - 無論退出狀態…

JWT安全機制與最佳實踐詳解

JWT&#xff08;JSON Web Token&#xff09; 是一種開放標準&#xff08;RFC 7519&#xff09;&#xff0c;用于在各方之間安全地傳輸信息作為緊湊且自包含的 JSON 對象。它被廣泛用于身份驗證&#xff08;Authentication&#xff09;和授權&#xff08;Authorization&#xff…

如何解決pip安裝報錯ModuleNotFoundError: No module named ‘ipython’問題

【Python系列Bug修復PyCharm控制臺pip install報錯】如何解決pip安裝報錯ModuleNotFoundError: No module named ‘ipython’問題 摘要 在開發過程中&#xff0c;我們常常會遇到pip install報錯的問題&#xff0c;其中一個常見的報錯是 ModuleNotFoundError: No module named…

從三維Coulomb勢到二維對數勢的下降法推導

題目 問題 7. 應用 9.1.4 小節描述的下降法&#xff0c;但針對二維的拉普拉斯方程&#xff0c;并從三維的 Coulomb 勢出發 KaTeX parse error: Invalid delimiter: {"type":"ordgroup","mode":"math","loc":{"lexer&qu…

直播一體機技術方案解析:基于RK3588S的硬件架構特性?

硬件配置??主控平臺??? 搭載瑞芯微RK3588S旗艦處理器&#xff08;四核A762.4GHz 四核A55&#xff09;? 集成ARM Mali-G610 MP4 GPU 6TOPS算力NPU? 雙通道LPDDR5內存 UFS3.1存儲組合??專用加速單元??→ 板載視頻采集模塊&#xff1a;支持4K60fps HDMI環出采集→ 集…

【氮化鎵】GaN取代GaAs作為空間激光無線能量傳輸光伏轉換器材料

2025年7月1日,西班牙圣地亞哥-德孔波斯特拉大學的Javier F. Lozano等人在《Optics and Laser Technology》期刊發表了題為《Gallium nitride: a strong candidate to replace GaAs as base material for optical photovoltaic converters in space exploration》的文章,基于T…

直播美顏SDK動態貼紙模塊開發指南:從人臉關鍵點識別到3D貼合

很多美顏技術開發者好奇&#xff0c;如何在直播美顏SDK中實現一個高質量的動態貼紙模塊&#xff1f;這不是簡單地“貼圖貼臉”&#xff0c;而是一個融合人臉關鍵點識別、實時渲染、貼紙驅動邏輯、3D骨骼動畫與跨平臺性能優化的系統工程。今天&#xff0c;就讓我們從底層技術出發…

學習游戲制作記錄(劍投擲技能)7.26

1.實現瞄準狀態和接劍狀態準備好瞄準動畫&#xff0c;投擲動畫和接劍動畫&#xff0c;并設置參數AimSword和CatchSword投擲動畫在瞄準動畫后&#xff0c;瞄準結束后才能投擲創建PlayerAimSwordState腳本和PlayerCatchSwordState腳本并在Player中初始化&#xff1a;PlayerAimSwo…

【c++】問答系統代碼改進解析:新增日志系統提升可維護性——關于我用AI編寫了一個聊天機器人……(14)

在軟件開發中&#xff0c;代碼的迭代優化往往從提升可維護性、可追蹤性入手。本文將詳細解析新增的日志系統改進&#xff0c;以及這些改進如何提升系統的實用性和可調試性。一、代碼整體背景代碼實現了一個基于 TF-IDF 算法的問答系統&#xff0c;核心功能包括&#xff1a;加載…

visual studio2022編譯unreal engine5.4.4源碼

UE5系列文章目錄 文章目錄 UE5系列文章目錄 前言 一、ue5官網 二.編譯源碼中遇到的問題 前言 一、ue5官網 UE5官網 UE5源碼下載地址 這樣雖然下載比較快,但是不能進行代碼git管理,以后如何虛幻官方有大的版本變動需要重新下載源碼,所以我們還是最好需要visual studio2022…

vulhub Earth靶場攻略

靶場下載 下載鏈接&#xff1a;https://download.vulnhub.com/theplanets/Earth.ova 靶場使用 將壓縮包解壓到一個文件夾中&#xff0c;右鍵&#xff0c;用虛擬機打開&#xff0c;就創建成功了&#xff0c;然后啟動虛擬機&#xff1a; 這時候靶場已經啟動了&#xff0c;咱們現…

Python訓練Day24

浙大疏錦行 元組可迭代對象os模塊

Spring核心:Bean生命周期、外部化配置與組件掃描深度解析

Bean生命周期 說明 程序中的每個對象都有生命周期&#xff0c;對象的創建、初始化、應用、銷毀的整個過程稱之為對象的生命周期&#xff1b; 在對象創建以后需要初始化&#xff0c;應用完成以后需要銷毀時執行的一些方法&#xff0c;可以稱之為是生命周期方法&#xff1b; 在sp…

日語學習-日語知識點小記-進階-JLPT-真題訓練-N1階段(1):2017年12月-JLPT-N1

日語學習-日語知識點小記-進階-JLPT-真題訓練-N1階段&#xff08;1&#xff09;&#xff1a;2017年12月-JLPT-N1 1、前言&#xff08;1&#xff09;情況說明&#xff08;2&#xff09;工程師的信仰&#xff08;3&#xff09;真題訓練2、真題-2017年12月-JLPT-N1&#xff08;1&a…

(一)使用 LangChain 從零開始構建 RAG 系統|RAG From Scratch

RAG 的主要動機 大模型訓練的時候雖然使用了龐大的世界數據&#xff0c;但是并沒有涵蓋用戶關心的所有數據&#xff0c; 其預訓練令牌&#xff08;token&#xff09;數量雖大但相對這些數據仍有限。另外大模型輸入的上下文窗口越來越大&#xff0c;從幾千個token到幾萬個token,…

OpenCV學習探秘之一 :了解opencv技術及架構解析、數據結構與內存管理?等基礎

?一、OpenCV概述與技術演進? 1.1技術歷史? OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是由Intel于1999年發起創建的開源計算機視覺庫&#xff0c;后來交由OpenCV開源社區維護&#xff0c;旨在為計算機視覺應用提供通用基礎設施。經歷20余年發展&…