【高并發內存池】一、簡介 定長內存池實現

文章目錄

  • Ⅰ. 項目介紹
    • 1、這個項目要做什么
    • 2、項目的要求
  • Ⅱ. 什么是內存池
    • 1、池化技術
    • 2、內存池
    • 3、`malloc`
  • Ⅲ. 設計一個定長內存池
    • 1、定長內存池的概念
    • 2、實現
      • 如何實現定長???
      • 如何繞開 `malloc` 向堆直接申請空間???
    • 3、性能測試

在這里插入圖片描述

Ⅰ. 項目介紹

1、這個項目要做什么

tcmalloc源代碼

? 當前項目是實現一個高并發的內存池,他的原型是 google 的一個開源項目 tcmalloc,全稱為 Thread-Caching Malloc,即線程緩存的 malloc,實現了高效的多線程內存管理,用于替代系統的內存分配相關的函數(比如 mallocfree)。

? 我們這個項目是把 tcmalloc 最核心的框架簡化后拿出來,模擬實現出一個自己的高并發內存池,目的就是學習 tcamlloc 的精華,這種方式有點類似我們之前學習 STL 容器的方式。但是相比 STL 容器部分,tcmalloc 的代碼量和復雜度上升了很多。

? 另一方面 tcmalloc 是全球大廠 google 開源的,可以認為當時頂尖的 C++ 高手寫出來的,他的知名度也是非常高的,不少公司都在用它,Go 語言直接用它做了自己內存分配器。所以很多程序員是熟悉這個項目的,那么有好處,也有壞處。好處就是把這個項目理解扎實了,會很受面試官的認可。壞處就是面試官可能也比較熟悉項目,對項目會問得比較深,比較細。如果你對項目掌握得不扎實,那么就容易碰釘子。

2、項目的要求

? 這個項目會用到 C/C++、數據結構(鏈表、哈希桶)、操作系統內存管理、單例模式、多線程、互斥鎖等等方面的知識。

Ⅱ. 什么是內存池

1、池化技術

? 所謂 “池化技術”,就是程序先向系統申請過量的資源,然后自己管理,以備不時之需。之所以要申請過量的資源,是因為每次申請該資源都有較大的開銷,不如提前申請好了,這樣使用時就會變得非常快捷,大大提高程序運行效率

? 在計算機中,有很多使用“池”這種技術的地方,比如內存池、連接池、線程池、對象池等。以服務器上的線程池為例,它的主要思想是:先啟動若干數量的線程,讓它們處于睡眠狀態,當接收到客戶端的請求時,喚醒池中某個睡眠的線程,讓它來處理客戶端的請求,當處理完這個請求,線程又進入睡眠狀態

2、內存池

? 內存池(Memory Pool)是指程序預先從操作系統申請一塊足夠大內存,此后,當程序中需要申請內存的時候,不是直接向操作系統申請,而是直接從內存池中獲取;同理,當程序釋放內存的時候,并不真正將內存返回給操作系統,而是返回內存池。當程序退出(或者特定時間)時,內存池才將之前申請的內存真正釋放。

? 內存池通常由兩個部分組成:內存池管理器內存塊分配器,它們的作用如下所示:

  1. 內存池管理器負責管理內存池的大小和內存塊的分配和釋放。
  2. 內存塊分配器則負責分配和釋放內存塊。

內存池的優點包括:

  1. 提高內存分配效率:內存池可以減少頻繁的內存分配和釋放操作,從而提高內存分配效率。
  2. 減少內存碎片:由于內存池中的內存塊大小固定,因此不會產生內存碎片,從而減少了內存碎片的產生。
  3. 提高程序性能:內存池可以減少動態內存分配和釋放操作,從而減少了內存管理的開銷,提高了程序性能。
  4. 簡化代碼實現:內存池可以簡化代碼實現,減少了內存管理相關的代碼量。

? 盡管內存池可以提高程序性能和減少內存碎片的產生,但是它的 缺點是需要預先分配一定數量的內存,因此會占用一定的內存空間。此外,如果內存池的大小預估不足,可能會導致內存不夠用的情況。因此,在使用內存池時需要根據實際需求進行合理的配置。

? 這里需要補充說明的是內存碎片分為 外碎片內碎片

  • 外碎片是一些空閑的連續內存區域太小,這些內存空間不連續,以至于合計的內存足夠,但是不能滿足一些的內存分配申請需求;
  • 內碎片 是由于一些對齊的需求,導致分配出去的空間中一些內存無法被利用。內碎片的問題,我們后面項目中就會看到。

3、malloc

? C/C++中我們要動態申請內存都是通過 malloc 去申請內存,但是我們要知道,實際上并不是直接去堆中獲取內存的,因為malloc 就是一個內存池。它相當于向操作系統預先申請了一塊較大的內存空間,然后按需分配給程序用。當全部用完或者程序有大量的內存需求時,再根據實際需求向操作系統申請內存。

? malloc 的實現方式有很多種,一般不同編譯器平臺用的都是不同的。比如 Windowsvs 系列用的微軟自己寫的一套,Linux gcc 用的 glibc 中的 ptmalloc。下面有幾篇關于這塊的文章,大概可以去簡單看看了解一下,關于 ptmalloc,學完我們的項目以后,有興趣大家可以去看看他的實現細節。

一文了解,Linux內存管理,malloc、free 實現原理

malloc()背后的實現原理——內存池

malloc的底層實現(ptmalloc)
在這里插入圖片描述

Ⅲ. 設計一個定長內存池

1、定長內存池的概念

? 我們知道申請內存使用的是 malloc,但 malloc 其實就是一個通用的大眾貨,什么場景下都可以用,但是什么場景下都可以用就意味著什么場景下都不會有很高的性能。下面我們就先來設計一個定長內存池做個開胃菜,當然這個定長內存池在我們后面的高并發內存池中也是有價值的,所以學習他目的有兩層,先熟悉一下簡單內存池是如何控制的,第二它會作為我們后面內存池的一個基礎組件。

? 定長內存池是一種內存池技術,它能夠提高內存管理效率,同時避免了動態內存分配和釋放造成的內存碎片問題。與傳統的內存池不同,定長內存池中的內存塊大小是固定的,即所有內存塊的大小都相同定長內存池動態內存分配 是兩種不同的內存分配方式,它們的主要區別如下:

  1. 內存塊大小

    • 定長內存池中的內存塊大小是固定的,而動態內存分配中的內存塊大小可以是不同的。
  2. 內存分配方式

    • 定長內存池在程序啟動時預先分配一定數量的內存塊,并將這些內存塊放入一個空閑內存塊鏈表中。當程序需要分配內存時,從空閑內存塊鏈表中取出一個內存塊。而動態內存分配則是在程序運行時根據需要動態分配內存塊。
  3. 內存分配效率

    • 由于定長內存池中的內存塊大小固定,因此分配和釋放內存塊的效率比動態內存分配要高。動態內存分配需要進行內存分配和釋放的操作,這些操作都需要耗費額外的時間和內存開銷。
  4. 內存碎片

    • 由于定長內存池中的內存塊大小固定,因此不存在內存碎片問題,而動態內存分配中會因為內存塊大小不同,導致內存碎片的產生。

? 總的來說,定長內存池適用于需要頻繁分配和釋放同種類型的內存塊的場景,可以提高內存分配效率,減少內存碎片的產生,但需要預先分配一定數量的內存塊。動態內存分配適用于需要靈活分配內存的場景,可以根據需要動態分配內存,但需要進行動態內存分配和釋放的操作,會產生額外的時間和內存開銷。

? 它們的使用場景如下圖所示:

在這里插入圖片描述

2、實現

如何實現定長???

? 在實現定長內存池時要做到“定長”有很多種方法,比如我們可以使用非類型模板參數,使得在該內存池中申請到的對象的大小都是 N

template<size_t N>
class fixed_size_pool
{};

? 此外,定長內存池也叫做對象池,在創建對象池時,對象池可以根據傳入的對象類型的大小來實現“定長”,因此我們可以通過使用模板參數來實現“定長”,比如創建定長內存池時傳入的對象類型是 int,那么該內存池就只支持 4 字節大小內存的申請和釋放。

template<class T>
class fixed_size_pool
{};

如何繞開 malloc 向堆直接申請空間???

? 既然是內存池,那么我們首先得向系統申請一塊內存空間,然后對其進行管理。要想直接向堆申請內存空間,在 Windows 下,可以調用 VirtualAlloc 函數;在 Linux 下,可以調用 brkmmap 函數。

這里我們可以通過條件編譯將對應平臺下向堆申請內存的函數進行封裝,此后我們就不必再關心當前所在平臺,當我們需要直接向堆申請內存時直接調用我們封裝后的 SystemAlloc 函數即可。

#ifdef _WIN32#include <Windows.h>
#else//...
#endif//直接去堆上申請按頁申請空間
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage<<13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else// linux下brk mmap等
#endifif (ptr == nullptr)throw std::bad_alloc();return ptr;
}

在這里插入圖片描述

首先我們來思考一下,如何讓一個指針在 32 位平臺下解引用后能向后訪問 4 個字節,在 64 位平臺下解引用后能向后訪問 8 個字節 ???

首先我們得知道,32 位平臺下指針的大小是 4 個字節,64 位平臺下指針的大小是 8 個字節。而指針指向數據的類型,決定了指針解引用后能向后訪問的空間大小,因此我們這里需要的是一個指向指針的指針,這里使用二級指針就行了。

當我們需要訪問一個內存塊的前 4/8 個字節時,我們可以 將內存塊的地址強轉為二級指針,由于二級指針存儲的是一級指針的地址,二級指針解引用能向后訪問一個指針的大小,因此在 32 位平臺下訪問的就是 4 個字節,在 64 位平臺下訪問的就是 8 個字節,此時我們訪問到了該內存塊的前 4/8 個字節。

void*& get_next(void* ptr)
{// 函數返回值為void*&類型的引用,是因為這個函數返回的是指針的引用,即返回的是指針本身的地址,而不是指針所指向的對象的地址。// 這樣做的好處是可以通過修改函數返回值來修改指針本身所指向的地址,從而實現對空閑鏈表的修改(頭插和頭刪的需要)return (*(void**)ptr);
}

需要注意的是,在釋放對象時,我們應該顯示調用該對象的析構函數清理該對象,因為該對象可能還管理著其他某些資源,如果不對其進行清理那么這些資源將無法被釋放,就會導致內存泄漏。下面的 release() 會講到!


下面我們設計的簡易定長內存池包括以下幾個方面:

  1. 成員變量:

    private:size_t _left_size = 0;     // 定長內存池可用部分的剩余大小char* _memory = nullptr;   // 定長內存池可用部分的起始指針(使用char類型是為了切內存塊的時候更精細方便)void* _freelist = nullptr; // 空閑鏈表的頭指針
    
    1. _left_size
      • 表示定長內存池可用部分的剩余大小,因為我們需要向內存池申請空間,那么內存池的空間是不斷變小的,那么最后一次申請的時候,可能會出現越界的情況,所以我們可以使用一個整型變量來記錄當前內存池的大小。
    2. _memory
      • 表示定長內存池可用部分的起始指針,為什么是可用部分而不是整個內存池的起始位置呢,其實也是起始位置,但是這個起始位置是不斷在變化的,這和我們下面的實現有關系,我們需要挪動 _memory 來表示申請了空間!
    3. _freelist
    • 表示空閑鏈表的頭指針,它用來鏈接未被使用的內存塊,以方便我們在申請空間的時候無需向內存池拿取,并且達到反復利用的效果,提高利用率和效率!
    • 這里我們還要注意一個點,就是因為我們要使用模板,所以傳進來的類型是什么我們并不知道,而 _freelist 是一個指針,32 位為 4 個字節,64 位為 8 個字節,但是如果傳進來的類型是個結構體,大于我們指針可以指的范圍,此時就不對了,有人可能說那么就用一個 T* 指針來指向結構體不就好了,但是我們要明白的是我們以后申請內存塊,可能是不同大小的內存塊,所以不一定是 T 大小結構的內存塊,所以在鏈接自由鏈表的時候,我們統一規則,將內存塊的開頭,大小為一個指針的空間作為鏈接部分,所以要保證這個內存塊的大小要超過 4/8 個字節,否則會出現越界,一般我們都會采取內存對其來防止這種小于 4/8 個字節的情況!連接方式如上圖中所連接的方式是一樣的,下面我們在講成員函數的時候會說到!
  2. 成員函數:

    • apply()

      1. 這個函數負責申請內存塊,但是首先要判斷一下自由鏈表是否有未使用的內存塊,有的話直接拿來用即可;沒有的話我們需要就得從內存池中拿,注意也是要先判斷,判斷申請的內存塊是否超過了內存池的大小,當大塊內存已經不足以切分出一個對象時,我們就應該調用我們封裝的 SystemAlloc 函數,再次向堆申請一塊內存空間,此時也要注意及時更新 _memory 指針的指向,以及 _leftbytes 的值。

      2. 若成功申請到了內存塊,則使用 定位new 進行初始化,因為我們是先分配了堆空間,所以初始化一般都使用 定位new,以便將內存分配交給內存池管理器來管理。

      3. 而具體在實現如何從空閑鏈表中拿取這個內存塊并不難,就是一個**頭刪操作**!而對于從內存池中拿取就需要配合 _memory 來移動,具體可以看下圖:
        在這里插入圖片描述

      4. 另外還有一個就是當自由鏈表中拿取內存塊的時候,我們之前規定了讓內存塊的前 4 個字節或前 8 個字節作為指針,存儲后面內存塊的起始地址。所以訪問的時候需要一個二級指針或者用判斷語句,這里使用二級指針的方式!
        在這里插入圖片描述

      // 向定長內存池申請一個T對象的空間
      T* apply()
      {T* obj = nullptr;// 1. 如果可以的話,直接從空閑鏈表中獲取空閑的空間if (_freelist != nullptr){// 就是一個鏈表頭刪的操作obj = (T*)_freelist;_freelist = get_next(_freelist);return obj;}// 2. 剩余內存不夠一個對象大小時,則重新開大塊空間int size = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T); // 防止申請空間不足一個指針的大小,要最少開辟一個指針大小的空間if (_left_size < size){// 剩余內存不夠一個對象大小時,則重新開大塊空間_left_size = 128 * 1024;_memory = (char*)SystemAlloc(_left_size >> 13);if (_memory == nullptr)throw std::bad_alloc(); // 申請空間失敗的話就拋異常}// 3. 從內存池中取出一個對象的空間,然后處理一下指針和大小即可obj = (T*)_memory;_left_size -= size;_memory += size;// 4. 進行定位new操作調用對象的構造函數進行初始化,最后進行返回即可new(obj)T;return obj;
      }
      
    • release()

      1. 釋放已經不想使用的內存塊空間,防止內存塊中一些數據的內存泄漏

      2. 對于還回來的定長內存塊,我們可以用空閑鏈表將其鏈接起來,但我們并不需要為其專門定義鏈式結構,我們可以讓內存塊的前 4 個字節或前 8 個字節作為指針,存儲后面內存塊的起始地址即可。因此在向空閑鏈表插入被釋放的內存塊時,先讓該內存塊的前 4 個字節或 8 個字節存儲空閑鏈表中第一個內存塊的地址(可存儲的原因是它們已經是空閑的不被使用的,所以才能直接對它們的內存塊進行操作),然后再讓 _freeList 指向該內存塊即可,也就是一個簡單的鏈表頭插操作。如下圖所示:

      在這里插入圖片描述

      // 釋放T對象的空間
      void release(T* obj)
      {// 1. 先調用對象的析構函數進行內部數據的釋放obj->~T();// 2. 再進行空閑鏈表的頭插操作get_next(obj) = _freelist;_freelist = obj;
      }
      

? 空閑鏈表 FreeList 是一種常見的內存管理技術,主要用于管理動態內存分配的空閑內存塊。空閑鏈表通常是一種鏈表結構,存儲著當前未被使用的內存塊,當需要分配內存時,可以從空閑鏈表中取出一個內存塊進行使用,當需要釋放內存時,將內存塊放回空閑鏈表中。

? 空閑鏈表的實現方式通常是在程序啟動時,預先分配一定數量的內存塊,并將這些內存塊放入一個空閑內存塊鏈表中。當程序需要分配內存時,從空閑內存塊鏈表中取出一個內存塊,當程序需要釋放內存時,將內存塊放回空閑內存塊鏈表中。在使用空閑鏈表時,需要注意內存塊的大小和對齊方式,以避免內存浪費和對齊問題

優點:

  1. 避免內存碎片:由于空閑鏈表中的內存塊大小相同,因此不會產生內存碎片問題。
  2. 提高內存分配效率:由于空閑鏈表中的內存塊已經被預先分配,因此內存分配時無需再進行耗時的系統調用,可以提高內存分配效率。
  3. 簡單易用:空閑鏈表的實現相對簡單,易于理解和使用。

缺點:

  1. 內存浪費:由于空閑鏈表中的內存塊大小是固定的,因此可能會出現內存浪費的問題,即內存塊大小與應用程序需要的大小不匹配。
  2. 內存池管理:由于空閑鏈表需要預先分配一定數量的內存塊,因此需要對內存池進行管理,包括內存池大小、內存塊大小、內存塊對齊方式等。
  3. 靜態內存分配:空閑鏈表通常需要在程序啟動時進行預先分配,因此不適用于需要動態分配內存的場景。

完整代碼:

#pragma once
#include <iostream>
#include <vector>
using std::cout;
using std::endl;#ifdef _WIN32
#include <Windows.h>
#else
//...
#endif//直接去堆上申請按頁申請空間
inline static void* SystemAlloc(size_t kpage)
{
#ifdef _WIN32void* ptr = VirtualAlloc(0, kpage << 13, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
#else// linux下brk mmap等
#endifif (ptr == nullptr)throw std::bad_alloc();return ptr;
}template <class T>
class fixed_size_pool
{
private:size_t _left_size = 0;     // 定長內存池可用部分的剩余大小char* _memory = nullptr;   // 定長內存池可用部分的起始指針(使用char類型是為了切內存塊的時候更精細方便)void* _freelist = nullptr; // 空閑鏈表的頭指針
public:// 向定長內存池申請一個T對象的空間T* apply(){T* obj = nullptr;// 1. 如果可以的話,直接從空閑鏈表中獲取空閑的空間if (_freelist != nullptr){// 就是一個鏈表頭刪的操作obj = (T*)_freelist;_freelist = get_next(_freelist);return obj;}// 2. 剩余內存不夠一個對象大小時,則重新開大塊空間int size = sizeof(T) < sizeof(void*) ? sizeof(void*) : sizeof(T); // 防止申請空間不足一個指針的大小,要最少開辟一個指針大小的空間if (_left_size < size){// 剩余內存不夠一個對象大小時,則重新開大塊空間_left_size = 128 * 1024;_memory = (char*)SystemAlloc(_left_size >> 13);if (_memory == nullptr)throw std::bad_alloc(); // 申請空間失敗的話就拋異常}// 3. 從內存池中取出一個對象的空間,然后處理一下指針和大小即可obj = (T*)_memory;_left_size -= size;_memory += size;// 4. 進行定位new操作調用對象的構造函數進行初始化,最后進行返回即可new(obj)T;return obj;}// 釋放T對象的空間void release(T* obj){// 1. 先調用對象的析構函數進行內部數據的釋放obj->~T();// 2. 再進行空閑鏈表的頭插操作get_next(obj) = _freelist;_freelist = obj;}void*& get_next(void* ptr){// 函數返回值為void*&類型的引用,是因為這個函數返回的是指針的引用,即返回的是指針本身的地址,而不是指針所指向的對象的地址。// 這樣做的好處是可以通過修改函數返回值來修改指針本身所指向的地址,從而實現對空閑鏈表的修改(頭插和頭刪的需要)return (*(void**)ptr);}
};

3、性能測試

// 測試數據
struct TreeNode
{int _val;TreeNode* _left;TreeNode* _right;TreeNode(): _val(0), _left(nullptr), _right(nullptr){}
};void TestObjectPool()
{// 申請釋放的輪次const size_t Rounds = 5;// 每輪申請釋放多少次const size_t N = 100000;// 測試new和delete的速度std::vector<TreeNode*> v1;v1.reserve(N);size_t begin1 = clock();for (size_t j = 0; j < Rounds; ++j){for (int i = 0; i < N; ++i)v1.push_back(new TreeNode);for (int i = 0; i < N; ++i)delete v1[i];v1.clear();}size_t end1 = clock();// 測試我們實現的內存池的速度std::vector<TreeNode*> v2;v2.reserve(N);fixed_size_pool<TreeNode> TNPool;size_t begin2 = clock();for (size_t j = 0; j < Rounds; ++j){for (int i = 0; i < N; ++i)v2.push_back(TNPool.apply());for (int i = 0; i < N; ++i)TNPool.release(v2[i]);v2.clear();}size_t end2 = clock();cout << "new cost time:" << end1 - begin1 << endl;cout << "fixed_size_pool cost time:" << end2 - begin2 << endl;
}// 運行結果:
// debug下:
new cost time:149
fixed_size_pool cost time:37// release下:
new cost time:31
fixed_size_pool cost time:2

在代碼中,我們先用new申請若干個 TreeNode 對象,然后再用 delete 將這些對象再釋放,通過 clock 函數得到整個過程消耗的時間。(newdelete 底層就是封裝的 mallocfree

然后再重復該過程,只不過將其中的 newdelete 替換為定長內存池當中的 applyrelease,此時再通過 clock 函數得到該過程消耗的時間。

? 可以看到在這個過程中,定長內存池消耗的時間比 malloc/free 消耗的時間要短。這就是因為 malloc 是一個通用的內存池,而定長內存池是專門針對申請定長對象而設計的,因此在這種特殊場景下定長內存池的效率更高,正所謂 “尺有所短,寸有所長”。

在這里插入圖片描述

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

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

相關文章

產品設計.原型設計

產品思維&#xff1a; 1. 產品定位&#xff1a;產品的具體的、用戶畫像&#xff1b; --什么樣的人在什么環境下做什么事情的場景 2. 范圍層: 發現、識別和決策需求。--識別真假需求&#xff0c;做ROI判斷 3. 可復用的、MVP產品方案--要能復用的解決方案&#xff0c;最小可用產品…

vue3+element-plus 輸入框el-input設置背景顏色和字體顏色,樣式效果等同于不可編輯的效果

應用效果&#xff1a;代碼&#xff1a;<template> ......<el-form-item label"文件編號" label-position"right"><el-input v-model"qualityFileForm.fileNo" clearable :disabled"!props.isNew" /></el-form-it…

[ CSS 前端 ] 網頁內容的修飾

目錄 一. CSS 1. 概述 2. 基本語法 (1)行內樣式表 (2)內嵌樣式表 (3)外部樣式表 3. 選擇器 (1)標簽選擇器: (2)類選擇器: (3)通配選擇器: (4)后代選擇器: 4. 基礎樣式 (1). 文本樣式 (2). 背景樣式 (3). 列表樣式 5. 偽類 (1)定義: (2)偽類的語法&#xff1a; …

全面深入了解榛樹游戲引擎

本文還有配套的精品資源&#xff0c;點擊獲取 簡介&#xff1a;榛樹游戲引擎&#xff08;Hazel&#xff09;是一款專為游戲開發設計的先進軟件工具&#xff0c;它集成了多種功能&#xff0c;支持現代圖形API&#xff0c;具有高性能的物理模擬系統和易學易用的腳本語言&#…

“大模型”技術專欄 | 淺談基于 Kubernetes 的 LLM 分布式推理框架架構:概覽

編者按&#xff1a;人工智能正以前所未有的滲透力重塑生產與生活圖景。作為國內領先的數據智能科技企業&#xff0c;和鯨科技自 2015 年成立以來&#xff0c;深耕人工智能與數據科學&#xff0c;歷經十年發展&#xff0c;已在氣象、教育、醫療、航空航天、金融、通信、能源、零…

【JS】認識并實現一個chrome擴展程序

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 這篇文章主要介紹chrome擴展程序。 學其所用&#xff0c;用其所學。——梁啟超 歡迎來到我的博客&#xff0c;一起學習&#xff0c;共同進步。 喜歡的朋友可以關注一下&#xff0c;下次更新不迷路&#…

jeecgboot項目遇見的一些問題:

1.當你想修改項目的標題&#xff0c;前端將jeecgboot改成你想要的標題的時候&#xff0c;去前端的.env文件中進行修改。圖1 修改標題根據路徑找到文件&#xff0c;將網站標題改成自己需要的就可以正常顯示了。圖2 顯示前圖3 顯示后2.在動態數組中&#xff0c;如果你知道數組需要…

項目里程碑設定有哪些方法

要科學設定項目里程碑&#xff0c;可采用以下幾種方法&#xff1a;基于項目階段劃分法、關鍵交付物導向法、依賴關系鏈分析法、時間驅動法、風險節點識別法、目標成果導向法、資源約束分析法、客戶驗收節點設定法。其中&#xff0c;關鍵交付物導向法尤為實用。該方法以項目中必…

英偉達顯卡驅動怎么更新 詳細步驟教程

英偉達顯卡驅動程序對于電腦的圖形性能至關重要&#xff0c;它能確保顯卡在游戲、設計、視頻渲染等方面發揮最大性能。如果驅動過舊&#xff0c;可能會導致游戲運行不暢、軟件不兼容&#xff0c;甚至系統出現錯誤。因此&#xff0c;定期更新英偉達顯卡驅動非常必要。下面將為大…

基于單片機智能拐杖/導盲杖/老人防摔倒設計

傳送門 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目速選一覽表 &#x1f449;&#x1f449;&#x1f449;&#x1f449;其他作品題目功能速覽 概述 該設計針對老年人及行動不便人群的需求&#xff0c;開發了一款集成單片機控制的智能拐杖。拐杖采…

Node.js完整安裝配置指南(包含國內鏡像配置)

Node.js完整安裝配置指南&#xff08;包含國內鏡像配置&#xff09; 一、Node.js安裝 方法1&#xff1a;使用Chocolatey安裝&#xff08;推薦&#xff09; # 安裝最新LTS版本 choco install nodejs# 或安裝指定版本 choco install nodejs --version20.11.0方法2&#xff1a;…

AI硬件 - AMD顯卡架構演進及產品線

目錄 一、AMD顯卡架構演進總結 二、典型AMD AI顯卡歷代型號參數對比表 關鍵參數說明: 三、AMD 特供中國AI顯卡產品線全覽 1. 企業級Instinct系列(數據中心/科研) 2. 消費級AI加速顯卡(開發/本地推理) 四、與NVIDIA顯卡的AI性能對比 關鍵指標實測數據 五、模型框架…

論文閱讀-Gated CRF Loss for Weakly Supervised Semantic Image Segmentation

文章目錄1 背景2 模塊2.1 部分交叉熵損失2.2 弱標簽&#xff08;線/點&#xff09;2.3 Gated CRF Loss3 效果3.1 總體效果3.2 消融實驗4 總結參考文獻1 背景 全監督的語義分割需要對全圖進行完全而精確的標注。當需要標注的目標在圖像中較多&#xff0c;又或形狀不規則&#x…

零墨云A4mini打印機設置電腦通過局域網絡進行打印

文檔時間&#xff1a;2025年8月 1.演示環境 操作系統版本&#xff1a;Windows11 打印機版本&#xff1a;零墨云A4mini 這款打印機打印的方式有藍牙、遠程云和局域網&#xff0c;這里演示的是電腦通過局域網打印 通過電腦版局域網(這個局域網是網絡可達)打印之前&#xff0c…

ESP8266 入門(第 3 部分):使用 Arduino IDE 對 ESP8266 進行編程并刷新其內存

使用 Arduino IDE 對 ESP8266 進行編程并刷新其內存 這是我們之前 ESP 教程的延續的第三個教程,其中我們將學習使用 Arduino IDE(不使用 Arduino)對 ESP8266 進行編程和燒錄 ESP8266。在前面的教程中,我們介紹了 WiFi 收發器ESP8266簡介以及將 AT 命令與 ESP8266 結合使用。…

如何成功初始化一個模塊

一、如何保證成功初始化一個模塊&#xff08;以 UART 為例&#xff09;要成功初始化一個模塊&#xff0c;請遵循以下步驟&#xff1a;在圖形化界面中&#xff0c;首先配置外設模塊。緊接著&#xff0c;配置使用到的外設模塊的引腳&#xff08;這一點很重要&#xff0c;容易忘記…

Hive 存儲管理測試用例設計指南

一、測試范圍界定Hive 存儲管理測試主要覆蓋以下核心模塊&#xff1a;內部表 / 外部表存儲特性驗證分區表 / 分桶表管理功能測試存儲格式兼容性測試&#xff08;TextFile/ORC/Parquet 等&#xff09;數據加載與導出機制驗證元數據與 HDFS 存儲一致性校驗異常場景與邊界條件處理…

智芯微ZX6N60A—N溝道增強型功率MOSFET

主要特征&#xff1a; ID 6A Vdss 600V RDSON-typ &#xff08;VGS10V&#xff09; 1.4Ω特點&#xff1a; ? 快速切換 ? 低導通電阻 ? 低門費 ? 100%單脈沖雪崩能量測試應用范圍&#xff1a; ? 適配器和充電器的電源開關電路。芯片數據 ? 外殼&#xff1a;模壓塑料 …

latex|算法algorithm宏包和注意事項

LaTeX 中 algorithm 環境完整指南 在科研論文里&#xff0c;寫清楚算法步驟通常需要用到 偽代碼環境。最常見的選擇有兩個包&#xff1a; algorithm2e —— 功能最強大&#xff0c;適合期刊/學位論文algorithmicx algpseudocode —— 更靈活、可定制&#xff0c;常用于會議模板…

純Qt結合ffmpeg實現本地攝像頭采集/桌面采集/應用程序窗口采集/指定采集幀率和分辨率等

一、前言說明 ffmpeg的功能真的是包羅萬象&#xff0c;除了基本的編解碼&#xff0c;還有個專門的avdevice模塊用來對本地設備的采集支持&#xff0c;最開始用到ffmpeg采集本地攝像頭的緣由&#xff0c;還不是因為Qt不給力&#xff0c;Qt5開始有個qcamera類&#xff0c;但是只…