一生一芯 PA2 RTFSC

src/isa/riscv32/inst.c出發。

向上搜索,理解宏定義的含義。

R(i)

#define R(i) gpr(i)

  • R(i):訪問第i號通用寄存器

會被替換為:

#define gpr(idx) (cpu.gpr[check_reg_idx(idx)])

分為兩個部分:

  • cpu.gpr
  • check_reg_idx

cpu.gpr的每個含義,在預學習的時候已經接觸過了。

對于check_reg_idx,可見參數為一個int,那么宏定義gprR的參數也是int

static inline int check_reg_idx(int idx) {IFDEF(CONFIG_RT_CHECK, assert(idx >= 0 && idx < MUXDEF(CONFIG_RVE, 16, 32)));return idx;
}

先看IFDEF

#define IFDEF(macro, ...) MUXDEF(macro, __KEEP, __IGNORE)(__VA_ARGS__)

又冒出來新的宏定義。

需要找MUXDEF

#define MUXDEF(macro, X, Y) MUX_MACRO_PROPERTY(__P_DEF_, macro, X, Y)

又又冒出來新的宏定義。

如此遞歸,整理得到:

#define IFDEF(macro, ...) MUXDEF(macro, __KEEP, __IGNORE)(__VA_ARGS__)
#define MUXDEF(macro, X, Y)  MUX_MACRO_PROPERTY(__P_DEF_, macro, X, Y)
#define MUX_MACRO_PROPERTY(p, macro, a, b) MUX_WITH_COMMA(concat(p, macro), a, b)
#define MUX_WITH_COMMA(contain_comma, a, b) CHOOSE2nd(contain_comma a, b)
#define CHOOSE2nd(a, b, ...) b

CHOOSE2nd

遞推的終點是:#define CHOOSE2nd(a, b, ...) b

從宏的名字和定義可以看出,這個宏的作用是:從可變參數中選擇第二個參數。

測試一下,如果參數小于2怎么辦。

error: macro "CHOOSE2nd" requires 3 arguments, but only 1 given6 |     cout << CHOOSE2nd(1) << endl;

報錯信息雖然顯示的是三個參數,但其實兩個就夠了。

MUX_WITH_COMMA

非常細節的逗號:

  • #define MUX_WITH_COMMA(contain_comma, a, b) CHOOSE2nd(contain_comma a, b)

無論如何都會選中b,意義何在?接著看看。

MUX_MACRO_PROPERTY

#define concat_temp(x, y) x ## y
#define concat(x, y) concat_temp(x, y)
#define CHOOSE2nd(a, b, ...) b
#define MUX_WITH_COMMA(contain_comma, a, b) CHOOSE2nd(contain_comma a, b)
#define MUX_MACRO_PROPERTY(p, macro, a, b) MUX_WITH_COMMA(concat(p, macro), a, b)

經測試,無論前兩個參數是啥,結果都是第四個參數。

  • 這個宏的作用是?接著看看

MUXDEF

#define MUXDEF(macro, X, Y) MUX_MACRO_PROPERTY(__P_DEF_, macro, X, Y)

#include <iostream>
using namespace std;
#define concat_temp(x, y) x ## y
#define concat(x, y) concat_temp(x, y)
#define CHOOSE2nd(a, b, ...) b
#define MUX_WITH_COMMA(contain_comma, a, b) CHOOSE2nd(contain_comma a, b)
#define MUX_MACRO_PROPERTY(p, macro, a, b) MUX_WITH_COMMA(concat(p, macro), a, b)
#define __P_DEF_0  X,
#define __P_DEF_1  X,
#define __P_ONE_1  X,
#define __P_ZERO_0 X,
#define MUXDEF(macro, X, Y)  MUX_MACRO_PROPERTY(__P_DEF_, macro, X, Y)
#define A
#define B 1
#define C 2int main() {cout << MUXDEF(A, 1, 2) << endl;cout << MUXDEF(B, 1, 2) << endl;cout << MUXDEF(C, 1, 2) << endl;cout << MUXDEF(1, 1, 2) << endl;cout << MUXDEF(0, 1, 2) << endl;
}

經測試,當拼接后的__P_DEF_macro有定義時,會返回X,否則返回Y

到這里,輸出的結果就不再是固定的了。

回頭看一下,依次展開:

MUXDEF(macro, X, Y)
MUX_MACRO_PROPERTY(__P_DEF_, macro, X, Y)
MUX_WITH_COMMA(concat(__P_DEF_, macro), X, Y)
CHOOSE2nd(__P_DEF_macro X, Y)
  • MUXDEF(macro, X, Y)會展開為:CHOOSE2nd(__P_DEF_macro X, Y)

但似乎還是只返回Y,為什么會返回X?看下面的函數:

#define concat_temp(x, y) x ## y
#define concat(x, y) concat_temp(x, y)

調用兩個函數,結果是不一樣的:

  • concat_temp(__P_DEF_, A)__P_DEF_A
  • concat(__P_DEF_, A)1,

哪來的逗號?

  • #define __P_DEF_1 X,
    • 非常細節的宏定義,X后有一個逗號。

concat(__P_DEF_, A)的展開結果為:

concat(__P_DEF_, A)
concat_temp(__P_DEF_, 1)
__P_DEF_1
X,

這個的X,MUX_WITH_COMMA省略的逗號結合。

如果A被定義為01,那么展開后,contain_comma a會變成X,a,使a成為第二個元素。

實際效果為宏定義下的?:三元運算符。

再回頭看,那個流程圖展開是有問題的。

宏定義不會遞推到最后一層再展開,參考concat(__P_DEF_, A)的展開過程,A在第一步就展開了,它的展開結果會影響下一步展開。

對于整條鏈路的入口:MUXDEF(CONFIG_RVE, 16, 32))

  • 如果定義了CONFIG_RVE10,那么編譯16,否則32

IFDEF

還有一個很費勁的宏定義,出現了三層括號。

#define __IGNORE(...)
#define __KEEP(...) __VA_ARGS__
#define IFDEF(macro, ...) MUXDEF(macro, __KEEP, __IGNORE)(__VA_ARGS__)

有一個非常關鍵的關鍵字:__VA_ARGS__

會取出可變參數的值,也就是...的部分。

比如IFDEF(A, cout<<1<<endl;),會先展開為:

  • MUXDEF(A, __KEEP, __IGNORE)(cout<<1<<endl;)

前文已經知道,MUXDEF在第一個參數定義為10時,會編譯為第二個參數。

那么就變成了:

  • __KEEP(cout<<1<<endl;)

__KEEP會編譯為參數列表,也就是:cout<<1<<endl;

第三個括號等第二個括號解析完成后作為參數傳入。

總結

IFDEF(CONFIG_RT_CHECK, assert(idx >= 0 && idx < MUXDEF(CONFIG_RVE, 16, 32)));

  • 作用是判斷,是否檢查寄存器越界訪問

R(i)

  • 作用是取出第i個寄存器的值。

一串宏定義的作用是判斷取值的時候要不要檢查。

Mr/Mw

#define Mr vaddr_read

這個函數在預學習的時候也用到過,現在順著這個函數把宏定義捋一下。

首先是Mr后面沒帶括號,是給vaddr_read這個函數起了個別名。

vaddr_read是調用了paddr_read這個函數。

word_t paddr_read(paddr_t addr, int len) {if (likely(in_pmem(addr))) return pmem_read(addr, len);IFDEF(CONFIG_DEVICE, return mmio_read(addr, len));out_of_bound(addr);return 0;
}

現在又出現了多個宏定義。

likely

#define likely(cond) __builtin_expect(cond, 1)

告訴編譯器,cond的值期望為1

__builtin_expect(expr, expected) 的返回值就是 expr 的值本身。

它的作用不是改變值,而是告訴編譯器你“預期這個值通常為 expected(通常是 0 或 1)”,以便編譯器做出更好的分支預測和優化。

static inline bool in_pmem(paddr_t addr) {return addr - CONFIG_MBASE < CONFIG_MSIZE;
}

in_pmem的作用是判斷地址是否合法。通過與地址偏移量運算得到。

static word_t pmem_read(paddr_t addr, int len) {word_t ret = host_read(guest_to_host(addr), len);return ret;
}

pmem_read的作用是從客戶機的物理內存地址addr開始,讀取len字節的數據,并返回對應的值。

static inline word_t host_read(void *addr, int len) {switch (len) {case 1: return *(uint8_t  *)addr;case 2: return *(uint16_t *)addr;case 4: return *(uint32_t *)addr;IFDEF(CONFIG_ISA64, case 8: return *(uint64_t *)addr);default: MUXDEF(CONFIG_RT_CHECK, assert(0), return 0);}
}

len只有1,2,4,8四種取值。也就是取出addr開始的1,2,4,8個字節的數據。

default: MUXDEF(CONFIG_RT_CHECK, assert(0), return 0);

  • 如果定義了CONFIG_RT_CHECK,那么非法的len會觸發斷言
  • 如果未定義CONFIG_RT_CHECK,那么非法的len會被忽略,返回0

host_write的函數體與host_read邏輯類似。

static inline void host_write(void *addr, int len, word_t data) {switch (len) {case 1: *(uint8_t  *)addr = data; return;case 2: *(uint16_t *)addr = data; return;case 4: *(uint32_t *)addr = data; return;IFDEF(CONFIG_ISA64, case 8: *(uint64_t *)addr = data; return);IFDEF(CONFIG_RT_CHECK, default: assert(0));}
}

imm*

這段宏定義在下面的decode_operand()中使用。

BITS

#define BITS(x, hi, lo) (((x) >> (lo)) & BITMASK((hi) - (lo) + 1)) // similar to x[hi:lo] in verilog

  • 提取x的第hilo位(閉區間)

運算分為兩個部分:((x) >> (lo))BITMASK((hi) - (lo) + 1)

先把低位干掉,然后取出新的地位。

BITMASK

#define BITMASK(bits) ((1ull << (bits)) - 1)

生成低bits位全是1的掩碼。

ull避免溢出。

SEXT

#define SEXT(x, len) ({ struct { int64_t n : len; } __x = { .n = x }; (uint64_t)__x.n; })

寫個程序測試一下功能。

({ ... })

  • 這是GCCClang支持的一種語法糖,用于將一個代碼塊作為一個表達式返回值。不能在標準C中使用。

在語法糖內部,有兩條語句:

  1. struct { int64_t n : len; } __x = { .n = x };
    1. struct { int64_t n : len; }定義了一個匿名結構體,變量n只取第n位。
    2. __x = { .n = x }創建了一個結構體變量__x.n被賦值為x,高位會被截斷。
  2. (uint64_t)__x.n;
    1. 把階段的位域強轉為uint64_t,并作為表達式結果。

那么SEXT的作用就是:

  • x看作一個len位的有符號整數,對其進行“符號擴展”為64位整數,并以uint64_t類型返回其值。

immI

#define immI() do { *imm = SEXT(BITS(i, 31, 20), 12); } while(0)

  1. 取出32位指令中的位段 [31:20]
  2. 將它作為12位 有符號立即數 符號擴展成64
  3. 然后賦值給*imm

immu

#define immU() do { *imm = SEXT(BITS(i, 31, 12), 20) << 12; } while(0)

  1. 取出32位指令中的位段 [31:12]
  2. 然后左移12位形成最終的32位立即數

imms

#define immS() do { *imm = (SEXT(BITS(i, 31, 25), 7) << 5) | BITS(i, 11, 7); } while(0)

  1. 取出:高7位:i[31:25]和低5位:i[11:7]
  2. 將高7位符號擴展,再左移5
  3. 與低5位做按位或,合并成完整的12位立即數

文獻來源

  • https://drive.google.com/file/d/1uviu1nH-tScFfgrovvFCrj7Omv8tFtkp/view?usp=drive_link
  • Page26

decode_exec

函數里面嵌套宏定義的寫法暫時看不懂。

向上搜索調用鏈:

int isa_exec_once(Decode *s) {s->isa.inst = inst_fetch(&s->snpc, 4);return decode_exec(s);
}

inst_fetch調用到vaddr_ifetch時,可以發現,與vaddr_read接下來的走向如出一轍。

int isa_exec_once(Decode *s) {s->isa.inst = inst_fetch(&s->snpc, 4);return decode_exec(s);
}
static inline uint32_t inst_fetch(vaddr_t *pc, int len) {uint32_t inst = vaddr_ifetch(*pc, len);(*pc) += len;return inst;
}

isa_exec_once的作用是,取出從&s->snpc處,長為4字節的指令。

  • 也就是32位指令。

并更新snpc為下一個位置。

snpcPA2手冊中有提到:

snpc是下一條靜態指令, 而dnpc是下一條動態指令. 對于順序執行的指令, 它們的snpc和dnpc是一樣的; 但對于跳轉指令, snpc和dnpc就會有所不同, dnpc應該指向跳轉目標的指令. 顯然, 我們應該使用s->dnpc來更新PC, 并且在指令執行的過程中正確地維護s->dnpc

decode_exec

static int decode_exec(Decode *s) {s->dnpc = s->snpc;#define INSTPAT_INST(s) ((s)->isa.inst)
#define INSTPAT_MATCH(s, name, type, ... /* execute body */ ) { \int rd = 0; \word_t src1 = 0, src2 = 0, imm = 0; \decode_operand(s, &rd, &src1, &src2, &imm, concat(TYPE_, type)); \__VA_ARGS__ ; \
}INSTPAT_START();INSTPAT("??????? ????? ????? ??? ????? 00101 11", auipc  , U, R(rd) = s->pc + imm);INSTPAT("??????? ????? ????? 100 ????? 00000 11", lbu    , I, R(rd) = Mr(src1 + imm, 1));INSTPAT("??????? ????? ????? 000 ????? 01000 11", sb     , S, Mw(src1 + imm, 1, src2));INSTPAT("0000000 00001 00000 000 00000 11100 11", ebreak , N, NEMUTRAP(s->pc, R(10))); // R(10) is $a0INSTPAT("??????? ????? ????? ??? ????? ????? ??", inv    , N, INV(s->pc));INSTPAT_END();R(0) = 0; // reset $zero to 0return 0;
}

decode_exec的頭部,把dnpc賦值為snpc。表示默認下一條指令就在下一個字節的位置。

中間兩端宏定義暫時看不懂,但是好在暫時沒有調用,只是定義:

#define INSTPAT_INST(s) ((s)->isa.inst)
#define INSTPAT_MATCH(s, name, type, ... /* execute body */ ) { \int rd = 0; \word_t src1 = 0, src2 = 0, imm = 0; \decode_operand(s, &rd, &src1, &src2, &imm, concat(TYPE_, type)); \__VA_ARGS__ ; \
}

可以接著往下看:

INSTPAT_START

第二條要執行的語句是:INSTPAT_START();

#define INSTPAT_START(name) { const void * __instpat_end = &&concat(__instpat_end_, name);

&&label是標簽地址,官方文檔鏈接:https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html

具體的功能可以寫一個函數測試一下:

int main() {void *ptr = &&label1;goto *ptr;printf("hello world\n");
label1:printf("Jumped to label1!\n");return 0;
}

INSTPAT_START()傳入的是空參數,展開的結果為:

{const void *__instpat_end = &&__instpat_end_;

非常細節的大括號,作用需要搭配INSTPAT_END()來理解:

__instpat_end_ :;
}

強制地提示,INSTPAT_START應與INSTPAT_END成對出現。

并且限制了作用域。

INSTPAT_END放置在函數體結尾。

INSTPAT

#define INSTPAT(pattern, ...) do { \uint64_t key, mask, shift; \pattern_decode(pattern, STRLEN(pattern), &key, &mask, &shift); \if ((((uint64_t)INSTPAT_INST(s) >> shift) & mask) == key) { \INSTPAT_MATCH(s, ##__VA_ARGS__); \goto *(__instpat_end); \} \
} while (0)

這段宏定義的內容是定義了一段代碼,do...wihile保證按期望運行。

  • uint64_t key, mask, shift;聲明了一些變量

pattern_decode(pattern, STRLEN(pattern), &key, &mask, &shift);

進到這個函數看下是如何運作的。

pattern_decode

定義了一堆宏定義,看起來比較復雜。

macro

#define macro(i) \if ((i) >= len) goto finish; \else { \char c = str[i]; \if (c != ' ') { \Assert(c == '0' || c == '1' || c == '?', \"invalid character '%c' in pattern string", c); \__key  = (__key  << 1) | (c == '1' ? 1 : 0); \__mask = (__mask << 1) | (c == '?' ? 0 : 1); \__shift = (c == '?' ? __shift + 1 : 0); \} \}

if ((i) >= len) goto finish;

len定義自:pattern_decode(pattern, STRLEN(pattern), &key, &mask, &shift);

也就是str的長度。

思考一下,macro64展開后能覆蓋0-63,但字符串長度是64,支持的最大長度是63還是64

可以寫個程序測試下。

字符串長度為64時輸出了pattern too long

pattern_decode函數的作用是,從一個長度為len的字符串str中解析出三種信息:

  • key:把所有'0''1'字符組成一個位串,表示匹配值
  • mask:每一位如果是'0''1'則為1,如果是'?'則為0,表示哪些位需要匹配
  • shift:表示尾部連續'?'的數量,這些位會被右移舍棄掉

回到INSTPAT

if ((((uint64_t)INSTPAT_INST(s) >> shift) & mask) == key)

這里為什么key不用右移?

因為pattern_decode中已經右移過了:

finish:*key = __key >> __shift;*mask = __mask >> __shift;*shift = __shift;

指令匹配成功之后,會執行INSTPAT_MATCH,然后goto到結尾的位置。

類似一堆if-else

    INSTPAT_MATCH(s, ##__VA_ARGS__); \goto *(__instpat_end); \

INSTPAT_MATCH

INSTPAT_MATCH的入參為##__VA_ARGS__,在參數為空時,會自動去掉前面的逗號,避免編譯報錯。

#define INSTPAT_MATCH(s, name, type, ... /* execute body */ ) { \int rd = 0; \word_t src1 = 0, src2 = 0, imm = 0; \decode_operand(s, &rd, &src1, &src2, &imm, concat(TYPE_, type)); \__VA_ARGS__ ; \
}

發現nametype即使傳入空置也不會影響目前的編譯。

decode_operand

static void decode_operand(Decode *s, int *rd, word_t *src1, word_t *src2, word_t *imm, int type) {uint32_t i = s->isa.inst;int rs1 = BITS(i, 19, 15);int rs2 = BITS(i, 24, 20);*rd     = BITS(i, 11, 7);switch (type) {case TYPE_I: src1R();          immI(); break;case TYPE_U:                   immU(); break;case TYPE_S: src1R(); src2R(); immS(); break;case TYPE_N: break;default: panic("unsupported type = %d", type);}
}

第一個參數就是當前正在解碼的指令上下文,封裝了機器碼值、指令地址等參數。

uint32_t i = s->isa.inst;
int rs1 = BITS(i, 19, 15);
int rs2 = BITS(i, 24, 20);
*rd     = BITS(i, 11, 7);

分別取出源寄存器1、源寄存器2和目的寄存器。

這三個寄存器的位置是固定的,在RSICV官方手冊中的出處:

還是這張圖。

每個寄存器不一定都能用到。但是每種類型的指令,只要用到了,位置就是固定的。

有個細節,上面的代碼取寄存器的時候,只有rd是指針解引用賦值,其他參數是局部變量,對函數外暫時沒有產生影響。

對于I型指令,需要immIrs1rd

對于U型指令,需要immUrd

對于S型指令,需要immSrs2rs1rd

  • rd對應手冊中的imm[4:0],可以發現位置完全一樣。

對于R型指令,看手冊定義,格式與S型一致,猜測后續執行時會復用S型指令的邏輯。

到這里,decode_operand函數的意義已經非常明確了:

  • 根據不同的指令類型,取出操作數。

VA_ARGS

把可變參數展開。

結合已有代碼:INSTPAT("??????? ????? ????? ??? ????? 00101 11", auipc , U, R(rd) = s->pc + imm);

首先會嘗試與字符串模板匹配:"??????? ????? ????? ??? ????? 00101 11"

如果匹配成功,會展開INSTPAT_MATCH

  • s,在decode_exec函數入參中傳入
  • nameINSTPAT的第二個參數auipc
  • typeINSTPAT的第三個參數U
  • ...INSTPAT的第四個參數R(rd) = s->pc + imm)

name目前來看無關緊要。

展開后會先根據type取出操作數。

然后展開...,操作取出的操作數。

總結

INSTPAT_STARTINSTPAT_END成對出現。

中間處理指令,某條規則匹配成功后,會立即執行并不再繼續向下匹配。

INSTPAT的參數是:

  • 匹配規則
  • 指令名字
  • 指令類型
  • 執行語義,傳入的應該是一系列函數。

參考

  • https://ysyx.oscc.cc/docs/ics-pa/2.2.html#rtfsc-2

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

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

相關文章

深度學習——手寫數字識別

深度學習——手寫數字識別 學習深度學習的朋友應該對MNIST數據集不陌生吧&#xff0c;相信很多人在剛開始學習深度學習的時候都會用到MNIST數據集進行書寫數字識別。本篇文章參考魚書創建一個深度網絡來進行書寫數字識別的任務。 如上圖所示&#xff0c;這里使用的卷積層全都是…

HashMap算法高級應用實戰:頻率類子數組問題的5種破解模式

本文將深入剖析5種基于HashMap的高級模式&#xff0c;通過原理詳解、多語言實現、性能對比和工業級應用&#xff0c;助您徹底掌握頻率類子數組問題。 1. 深入解析&#xff1a;頻率類子數組問題 1.1 問題定義與分類 頻率類子數組問題是指需要統計或查找滿足特定元素頻率條件的…

【精選】計算機畢業設計HTML5智能寵物尋找與領養系統 跨平臺寵物匹配 地圖定位找寵 領養申請審核系統源碼+論文+PPT+講解

博主介紹&#xff1a; ?我是阿龍&#xff0c;一名專注于Java技術領域的程序員&#xff0c;全網擁有10W粉絲。作為CSDN特邀作者、博客專家、新星計劃導師&#xff0c;我在計算機畢業設計開發方面積累了豐富的經驗。同時&#xff0c;我也是掘金、華為云、阿里云、InfoQ等平臺…

拼多多商家端 anti_content 補環境分析

聲明 本文章中所有內容僅供學習交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包內容、敏感網址、數據接口等均已做脫敏處理&#xff0c;嚴禁用于商業用途和非法用途&#xff0c;否則由此產生的一切后果均與作者無關&#xff01; 部分python代碼 import execjs impor…

電腦、手機長時間不關機可以嗎

電腦和手機常年處于開機狀態&#xff0c;只有在沒電或者系統提示更新的時候才會關機。那問題來了&#xff0c;電腦、手機長時間不關機到底可不可以呢&#xff1f;今天咱們就來好好嘮嘮。 手機長時間不關機的情況 先來說說手機。現在的智能手機功能越來越強大&#xff0c;我們…

「AI大數據」| 《華為:面向智能制造的工業大模型標準化研究報告》

今天給大家介紹 《華為:面向智能制造的工業大模型標準化研究報告》&#xff0c;這是一份由中國電子技術標準化研究院聯合華為等多家企業編寫的權威報告&#xff0c;聚焦工業大模型在智能制造領域的標準化發展。報告詳細分析了工業大模型的技術架構、應用場景、標準化現狀與挑戰…

Dinky1.2.3基于Kubernetes Application模式提交Flink作業

前言 Dinky 是一個開箱即用、易擴展&#xff0c;以 Apache Flink 為基礎&#xff0c;連接 OLAP 和數據湖等眾多框架的一站式實時計算平臺&#xff0c;致力于流批一體和湖倉一體的探索與實踐。 致力于簡化Flink任務開發&#xff0c;提升Flink任務運維能力&#xff0c;降低Flink…

【軟考高級架構設計師】——2025年上半年軟考真題(回憶版)

目錄 一、綜合知識1.1、計算機基礎與操作系統(15道單選)1.2、軟件工程與架構(16道單選)1.3、數據與網絡(8道單選)1.4、數學與邏輯(4道單選)1.5、其他(27道單選)1.6、英文題(質量屬性)(5道單選)二、案例分析2.1、大模型訓練系統(必選題)2.2、醫院知識圖譜(可選…

哈夫曼樹Python實現

哈夫曼樹構建原則&#xff1a; .統計頻率&#xff1a;對待編碼字符&#xff08;或數據塊&#xff09;的頻率進行統計。.初始化森林&#xff1a;將每個字符視為一棵只有根節點的二叉樹&#xff0c;權值為頻率。.合并樹&#xff1a;重復以下操作&#xff0c;直到只剩一棵樹&…

Dockerfile的學習與實踐

Dockerfile通過一系列的命令和參數&#xff0c;構建自定義鏡像。一般步驟如下&#xff1a; 一. 常用命令說明 基礎命令具體命令描述例子FROMFROM[基礎鏡像:版本號]基于指定的基礎鏡像構建自定義鏡像FROM eclipse-temurin:17-jdk-alpineRUNRUN構建容器需要運行的命令&#xff0…

【三大前端語言之一】靜態網頁語言:HTML詳解

你知道你在瀏覽器中所看到的每一個按鈕&#xff0c;每一個框&#xff0c;都是怎么創造出來的嗎&#xff1f;它們并非魔法&#xff0c;而是由一種被稱為HTML的語言精心構建的骨架。作為前端世界的三大基石之一&#xff08;HTML、CSS、JavaScript&#xff09;&#xff0c;HTML是萬…

04、誰發明了深度學習的方法,是怎么發明的?

深度學習的發展是多位研究者長期探索的結果,其核心方法的形成并非由單一人物 “發明”,而是歷經數十年理論積累與技術突破的產物。以下從關鍵人物、核心技術突破及歷史背景三個維度,梳理深度學習方法的起源與發展脈絡: 一、深度學習的奠基者與關鍵貢獻者 1. Geoffrey Hin…

Jmeter ServerAgent在arm環境啟動報錯no libsigar-aarch64-linux.so in java.library.path

使用Jmeter壓測的時候&#xff0c;用ServerAgent監測arm服務器的性能指標&#xff0c;在啟動ServerAgent時&#xff0c;報錯了&#xff1a;no libsigar-aarch64-linux.so in java.library.path 解決方案&#xff1a; 下載libsigar-aarch64-linux.so文件&#xff0c;放置在Serv…

AJAX攔截器失效排查指南:當你的beforeSend有效但error/complete沉默時

問題現象 開發者常遇到這樣的場景&#xff1a; $.ajaxSetup({beforeSend: () > console.log("? 觸發"), // 正常執行error: () > console.log("? 未觸發"), // 靜默失效complete: () > console.log("? 未觸發") // 同樣沉默 })…

【模型微調】負樣本選擇

1.核心設計理念 非對稱檢索任務&#xff08;例如&#xff0c;用一個簡短的問題去文檔庫里查找答案&#xff09;的一個核心挑戰是查詢&#xff08;query&#xff09;和文檔&#xff08;passage&#xff09;在文本特征上的巨大差異。以往的研究發現&#xff0c;為查詢和文檔提供…

下載安裝redis

有任何問題&#xff0c;都可以私信博主&#xff0c;共同探討學習。 正文開始 一、下載安裝redis一、啟動redis總結 一、下載安裝redis redis官方下載地址是github&#xff0c;有條件的同學可以自行搜索下載。針對部分網速不太好的同學&#xff0c;可以通過網盤獲取&#xff0c…

flutter 項目配置Gradle下載代理

如圖&#xff0c; 在Android Studio中配置代理是不生效的。 需要在flutter sdk的Gradle中去配置代理

世冠科技亮相TMC,以國產MBD工具鏈賦能汽車電控系統開發新未來

2025年6月12日至13日&#xff0c;第十七屆國際汽車動力系統技術年會&#xff08;TMC2025&#xff09;在南通國際會展中心盛大召開。作為全球汽車動力系統領域規模最大、規格最高、內容最前沿的標桿性國際盛會&#xff0c;匯聚了來自全球整車企業、核心零部件供應商、頂尖科研機…

將本地項目與遠程 Git 倉庫關聯的完整步驟

將本地項目與遠程 Git 倉庫關聯的完整步驟 現在的情景是&#xff1a;本地文件項目已經寫好了&#xff0c;亦或者遠程倉庫已經建好了&#xff0c;需要與本地項目關聯起來 以下是詳細的操作流程&#xff0c;我會用清晰的步驟說明如何將你的本地項目與遠程 Git 倉庫關聯&#xf…

3DS 轉換為 STP 全攻略:迪威模型網在線轉換詳解

在三維模型創作與應用的多元場景中&#xff0c;不同格式的文件承擔著獨特的角色。3DS&#xff08;3D Studio&#xff09;格式是 Autodesk 3ds Max 早期廣泛使用的文件格式&#xff0c;常用于游戲開發、影視特效制作等領域&#xff0c;能夠存儲模型的幾何形狀、材質、動畫等信息…