成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

為什么const無(wú)法讓C代碼跑得更快?

開(kāi)發(fā) 后端
在幾個(gè)月前的一篇文章里,我曾說(shuō)過(guò)“有個(gè)一個(gè)流行的傳言,const 有助于編譯器優(yōu)化 C 和 C++ 代碼”。我覺(jué)得我需要解釋一下,尤其是曾經(jīng)我自己也以為這是顯然對(duì)的。我將會(huì)用一些理論并構(gòu)造一些例子來(lái)論證,然后在一個(gè)真實(shí)的代碼庫(kù) Sqlite 上做一些實(shí)驗(yàn)和基準(zhǔn)測(cè)試。

[[276842]]

在幾個(gè)月前的一篇文章里,我曾說(shuō)過(guò)“有個(gè)一個(gè)流行的傳言,const 有助于編譯器優(yōu)化 C 和 C++ 代碼”。我覺(jué)得我需要解釋一下,尤其是曾經(jīng)我自己也以為這是顯然對(duì)的。我將會(huì)用一些理論并構(gòu)造一些例子來(lái)論證,然后在一個(gè)真實(shí)的代碼庫(kù) Sqlite 上做一些實(shí)驗(yàn)和基準(zhǔn)測(cè)試。

一個(gè)簡(jiǎn)單的測(cè)試

讓我們從一個(gè)最簡(jiǎn)單、最明顯的例子開(kāi)始,以前認(rèn)為這是一個(gè) const 讓 C 代碼跑得更快的例子。首先,假設(shè)我們有如下兩個(gè)函數(shù)聲明:

  1. void func(int *x);
  2. void constFunc(const int *x);

然后假設(shè)我們?nèi)缦聝煞荽a:

  1. void byArg(int *x)
  2. {
  3. printf("%d\n", *x);
  4. func(x);
  5. printf("%d\n", *x);
  6. }
  7.  
  8. void constByArg(const int *x)
  9. {
  10. printf("%d\n", *x);
  11. constFunc(x);
  12. printf("%d\n", *x);
  13. }

調(diào)用 printf() 時(shí),CPU 會(huì)通過(guò)指針從 RAM 中取得 *x 的值。很顯然,constByArg() 會(huì)稍微快一點(diǎn),因?yàn)榫幾g器知道 *x 是常量,因此不需要在調(diào)用 constFunc() 之后再次獲取它的值。它僅是打印相同的東西。沒(méi)問(wèn)題吧?讓我們來(lái)看下 GCC 在如下編譯選項(xiàng)下生成的匯編代碼:

  1. $ gcc -S -Wall -O3 test.c
  2. $ view test.s

以下是函數(shù) byArg() 的完整匯編代碼:

  1. byArg:
  2. .LFB23:
  3. .cfi_startproc
  4. pushq %rbx
  5. .cfi_def_cfa_offset 16
  6. .cfi_offset 3, -16
  7. movl (%rdi), %edx
  8. movq %rdi, %rbx
  9. leaq .LC0(%rip), %rsi
  10. movl $1, %edi
  11. xorl %eax, %eax
  12. call __printf_chk@PLT
  13. movq %rbx, %rdi
  14. call func@PLT # constFoo 中唯一不同的指令
  15. movl (%rbx), %edx
  16. leaq .LC0(%rip), %rsi
  17. xorl %eax, %eax
  18. movl $1, %edi
  19. popq %rbx
  20. .cfi_def_cfa_offset 8
  21. jmp __printf_chk@PLT
  22. .cfi_endproc

函數(shù) byArg() 和函數(shù) constByArg() 生成的匯編代碼中唯一的不同之處是 constByArg() 有一句匯編代碼 call constFunc@PLT,這正是源代碼中的調(diào)用。關(guān)鍵字 const 本身并沒(méi)有造成任何字面上的不同。

好了,這是 GCC 的結(jié)果。或許我們需要一個(gè)更聰明的編譯器。Clang 會(huì)有更好的表現(xiàn)嗎?

  1. $ clang -S -Wall -O3 -emit-llvm test.c
  2. $ view test.ll

這是 IR 代碼(LCTT 譯注:LLVM 的中間語(yǔ)言)。它比匯編代碼更加緊湊,所以我可以把兩個(gè)函數(shù)都導(dǎo)出來(lái),讓你可以看清楚我所說(shuō)的“除了調(diào)用外,沒(méi)有任何字面上的不同”是什么意思:

  1. ; Function Attrs: nounwind uwtable
  2. define dso_local void @byArg(i32*) local_unnamed_addr #0 {
  3. %2 = load i32, i32* %0, align 4, !tbaa !2
  4. %3 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i32 %2)
  5. tail call void @func(i32* %0) #4
  6. %4 = load i32, i32* %0, align 4, !tbaa !2
  7. %5 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i32 %4)
  8. ret void
  9. }
  10.  
  11. ; Function Attrs: nounwind uwtable
  12. define dso_local void @constByArg(i32*) local_unnamed_addr #0 {
  13. %2 = load i32, i32* %0, align 4, !tbaa !2
  14. %3 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i32 %2)
  15. tail call void @constFunc(i32* %0) #4
  16. %4 = load i32, i32* %0, align 4, !tbaa !2
  17. %5 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i32 %4)
  18. ret void
  19. }

某些有作用的東西

接下來(lái)是一組 const 能夠真正產(chǎn)生作用的代碼:

  1. void localVar()
  2. {
  3. int x = 42;
  4. printf("%d\n", x);
  5. constFunc(&x);
  6. printf("%d\n", x);
  7. }
  8.  
  9. void constLocalVar()
  10. {
  11. const int x = 42; // 對(duì)本地變量使用 const
  12. printf("%d\n", x);
  13. constFunc(&x);
  14. printf("%d\n", x);
  15. }

下面是 localVar() 的匯編代碼,其中有兩條指令在 constLocalVar() 中會(huì)被優(yōu)化掉:

  1. localVar:
  2. .LFB25:
  3. .cfi_startproc
  4. subq $24, %rsp
  5. .cfi_def_cfa_offset 32
  6. movl $42, %edx
  7. movl $1, %edi
  8. movq %fs:40, %rax
  9. movq %rax, 8(%rsp)
  10. xorl %eax, %eax
  11. leaq .LC0(%rip), %rsi
  12. movl $42, 4(%rsp)
  13. call __printf_chk@PLT
  14. leaq 4(%rsp), %rdi
  15. call constFunc@PLT
  16. movl 4(%rsp), %edx # constLocalVar() 中沒(méi)有
  17. xorl %eax, %eax
  18. movl $1, %edi
  19. leaq .LC0(%rip), %rsi # constLocalVar() 中沒(méi)有
  20. call __printf_chk@PLT
  21. movq 8(%rsp), %rax
  22. xorq %fs:40, %rax
  23. jne .L9
  24. addq $24, %rsp
  25. .cfi_remember_state
  26. .cfi_def_cfa_offset 8
  27. ret
  28. .L9:
  29. .cfi_restore_state
  30. call __stack_chk_fail@PLT
  31. .cfi_endproc

在 LLVM 生成的 IR 代碼中更明顯一點(diǎn)。在 constLocalVar() 中,第二次調(diào)用 printf() 之前的 load 會(huì)被優(yōu)化掉:

  1. ; Function Attrs: nounwind uwtable
  2. define dso_local void @localVar() local_unnamed_addr #0 {
  3. %1 = alloca i32, align 4
  4. %2 = bitcast i32* %1 to i8*
  5. call void @llvm.lifetime.start.p0i8(i64 4, i8* nonnull %2) #4
  6. store i32 42, i32* %1, align 4, !tbaa !2
  7. %3 = tail call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i32 42)
  8. call void @constFunc(i32* nonnull %1) #4
  9. %4 = load i32, i32* %1, align 4, !tbaa !2
  10. %5 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i64 0, i64 0), i32 %4)
  11. call void @llvm.lifetime.end.p0i8(i64 4, i8* nonnull %2) #4
  12. ret void
  13. }

好吧,現(xiàn)在,constLocalVar() 成功的省略了對(duì) *x 的重新讀取,但是可能你已經(jīng)注意到一些問(wèn)題:localVar()constLocalVar() 在函數(shù)體中做了同樣的 constFunc() 調(diào)用。如果編譯器能夠推斷出 constFunc() 沒(méi)有修改 constLocalVar() 中的 *x,那為什么不能推斷出完全一樣的函數(shù)調(diào)用也沒(méi)有修改 localVar() 中的 *x

這個(gè)解釋更貼近于為什么 C 語(yǔ)言的 const 不能作為優(yōu)化手段的核心原因。C 語(yǔ)言的 const 有兩個(gè)有效的含義:它可以表示這個(gè)變量是某個(gè)可能是常數(shù)也可能不是常數(shù)的數(shù)據(jù)的一個(gè)只讀別名,或者它可以表示該變量是真正的常量。如果你移除了一個(gè)指向常量的指針的 const 屬性并寫(xiě)入數(shù)據(jù),那結(jié)果將是一個(gè)未定義行為。另一方面,如果是一個(gè)指向非常量值的 const 指針,將就沒(méi)問(wèn)題。

這份 constFunc() 的可能實(shí)現(xiàn)揭示了這意味著什么:

  1. // x 是一個(gè)指向某個(gè)可能是常數(shù)也可能不是常數(shù)的數(shù)據(jù)的只讀指針
  2. void constFunc(const int *x)
  3. {
  4. // local_var 是一個(gè)真正的常數(shù)
  5. const int local_var = 42;
  6.  
  7. // C 語(yǔ)言規(guī)定的未定義行為
  8. doubleIt((int*)&local_var);
  9. // 誰(shuí)知道這是不是一個(gè)未定義行為呢?
  10. doubleIt((int*)x);
  11. }
  12.  
  13. void doubleIt(int *x)
  14. {
  15. *x *= 2;
  16. }

localVar() 傳遞給 constFunc() 一個(gè)指向非 const 變量的 const 指針。因?yàn)檫@個(gè)變量并非常量,constFunc() 可以撒個(gè)謊并強(qiáng)行修改它而不觸發(fā)未定義行為。所以,編譯器不能斷定變量在調(diào)用 constFunc() 后仍是同樣的值。在 constLocalVar() 中的變量是真正的常量,因此,編譯器可以斷定它不會(huì)改變 —— 因?yàn)樵?constFunc() 去除變量的 const 屬性并寫(xiě)入它會(huì)是一個(gè)未定義行為。

第一個(gè)例子中的函數(shù) byArg()constByArg() 是沒(méi)有可能優(yōu)化的,因?yàn)榫幾g器沒(méi)有任何方法能知道 *x 是否真的是 const 常量。

補(bǔ)充(和題外話):相當(dāng)多的讀者已經(jīng)正確地指出,使用 const int *x,該指針本身不是限定的常量,只是該數(shù)據(jù)被加個(gè)了別名,而 const int * const extra_const 是一個(gè)“雙向”限定為常量的指針。但是因?yàn)橹羔槺旧淼某A颗c別名數(shù)據(jù)的常量無(wú)關(guān),所以結(jié)果是相同的。僅在 extra_const 指向使用 const 定義的對(duì)象時(shí),*(int*const)extra_const = 0 才是未定義行為。(實(shí)際上,*(int*)extra_const = 0 也不會(huì)更糟。)因?yàn)樗鼈冎g的區(qū)別可以一句話說(shuō)明白,一個(gè)是完全的 const 指針,另外一個(gè)可能是也可能不是常量本身的指針,而是一個(gè)可能是也可能不是常量的對(duì)象的只讀別名,我將繼續(xù)不嚴(yán)謹(jǐn)?shù)匾?ldquo;常量指針”。(題外話結(jié)束)

但是為什么不一致呢?如果編譯器能夠推斷出 constLocalVar() 中調(diào)用的 constFunc() 不會(huì)修改它的參數(shù),那么肯定也能繼續(xù)在其他 constFunc() 的調(diào)用上實(shí)施相同的優(yōu)化,是嗎?并不。編譯器不能假設(shè) constLocalVar() 根本沒(méi)有運(yùn)行。如果不是這樣(例如,它只是代碼生成器或者宏的一些未使用的額外輸出),constFunc() 就能偷偷地修改數(shù)據(jù)而不觸發(fā)未定義行為。

你可能需要重復(fù)閱讀幾次上述說(shuō)明和示例,但不要擔(dān)心,它聽(tīng)起來(lái)很荒謬,它確實(shí)是正確的。不幸的是,對(duì) const 變量進(jìn)行寫(xiě)入是最糟糕的未定義行為:大多數(shù)情況下,編譯器無(wú)法知道它是否將會(huì)是未定義行為。所以,大多數(shù)情況下,編譯器看見(jiàn) const 時(shí)必須假設(shè)它未來(lái)可能會(huì)被移除掉,這意味著編譯器不能使用它進(jìn)行優(yōu)化。這在實(shí)踐中是正確的,因?yàn)檎鎸?shí)的 C 代碼會(huì)在“深思熟慮”后移除 const

簡(jiǎn)而言之,很多事情都可以阻止編譯器使用 const 進(jìn)行優(yōu)化,包括使用指針從另一內(nèi)存空間接受數(shù)據(jù),或者在堆空間上分配數(shù)據(jù)。更糟糕的是,在大部分編譯器能夠使用 const 進(jìn)行優(yōu)化的情況,它都不是必須的。例如,任何像樣的編譯器都能推斷出下面代碼中的 x 是一個(gè)常量,甚至都不需要 const

  1. int x = 42, y = 0;
  2. printf("%d %d\n", x, y);
  3. y += x;
  4. printf("%d %d\n", x, y);

總結(jié),const 對(duì)優(yōu)化而言幾乎無(wú)用,因?yàn)椋?/p>

  1. 除了特殊情況,編譯器需要忽略它,因?yàn)槠渌a可能合法地移除它
  2. 在 #1 以外的大多數(shù)例外中,編譯器無(wú)論如何都能推斷出該變量是常量

C++

如果你在使用 C++ 那么有另外一個(gè)方法讓 const 能夠影響到代碼的生成:函數(shù)重載。你可以用 const 和非 const 的參數(shù)重載同一個(gè)函數(shù),而非 const 版本的代碼可能可以被優(yōu)化(由程序員優(yōu)化而不是編譯器),減少某些拷貝或者其他事情。

  1. void foo(int *p)
  2. {
  3. // 需要做更多的數(shù)據(jù)拷貝
  4. }
  5.  
  6. void foo(const int *p)
  7. {
  8. // 不需要保護(hù)性的拷貝副本
  9. }
  10.  
  11. int main()
  12. {
  13. const int x = 42;
  14. // const 影響被調(diào)用的是哪一個(gè)版本的重載函數(shù)
  15. foo(&x);
  16. return 0;
  17. }

一方面,我不認(rèn)為這會(huì)在實(shí)際的 C++ 代碼中大量使用。另一方面,為了導(dǎo)致差異,程序員需要假設(shè)編譯器無(wú)法做出,因?yàn)樗鼈儾皇苷Z(yǔ)言保護(hù)。

用 Sqlite3 進(jìn)行實(shí)驗(yàn)

有了足夠的理論和例子。那么 const 在一個(gè)真正的代碼庫(kù)中有多大的影響呢?我將會(huì)在代碼庫(kù) Sqlite(版本:3.30.0)上做一個(gè)測(cè)試,因?yàn)椋?/p>

  • 它真正地使用了 const
  • 它不是一個(gè)簡(jiǎn)單的代碼庫(kù)(超過(guò) 20 萬(wàn)行代碼)
  • 作為一個(gè)數(shù)據(jù)庫(kù),它包括了字符串處理、數(shù)學(xué)計(jì)算、日期處理等一系列內(nèi)容
  • 它能夠在綁定 CPU 的情況下進(jìn)行負(fù)載測(cè)試

此外,作者和貢獻(xiàn)者們已經(jīng)進(jìn)行了多年的性能優(yōu)化工作,因此我能確定他們沒(méi)有錯(cuò)過(guò)任何有顯著效果的優(yōu)化。

配置

我做了兩份源碼拷貝,并且正常編譯其中一份。而對(duì)于另一份拷貝,我插入了這個(gè)特殊的預(yù)處理代碼段,將 const 變成一個(gè)空操作:

  1. #define const

(GNU) sed 可以將一些東西添加到每個(gè)文件的頂端,比如 sed -i '1i#define const' *.c *.h

在編譯期間使用腳本生成 Sqlite 代碼稍微有點(diǎn)復(fù)雜。幸運(yùn)的是當(dāng) const 代碼和非 const 代碼混合時(shí),編譯器會(huì)產(chǎn)生了大量的提醒,因此很容易發(fā)現(xiàn)它并調(diào)整腳本來(lái)包含我的反 const 代碼段。

直接比較編譯結(jié)果毫無(wú)意義,因?yàn)槿我馕⑿〉母淖兙蜁?huì)影響整個(gè)內(nèi)存布局,這可能會(huì)改變整個(gè)代碼中的指針和函數(shù)調(diào)用。因此,我用每個(gè)指令的二進(jìn)制大小和匯編代碼作為識(shí)別碼(objdump -d libsqlite3.so.0.8.6)。舉個(gè)例子,這個(gè)函數(shù):

  1. 000000000005d570 <sqlite3_blob_read>:
  2. 5d570: 4c 8d 05 59 a2 ff ff lea -0x5da7(%rip),%r8 # 577d0 <sqlite3BtreePayloadChecked>
  3. 5d577: e9 04 fe ff ff jmpq 5d380 <blobReadWrite>
  4. 5d57c: 0f 1f 40 00 nopl 0x0(%rax)

將會(huì)變成這樣:

  1. sqlite3_blob_read 7lea 5jmpq 4nopl

在編譯時(shí),我保留了所有 Sqlite 的編譯設(shè)置。

分析編譯結(jié)果

const 版本的 libsqlite3.so 的大小是 4,740,704 字節(jié),大約比 4,736,712 字節(jié)的非 const 版本大了 0.1% 。在全部 1374 個(gè)導(dǎo)出函數(shù)(不包括類似 PLT 里的底層輔助函數(shù))中,一共有 13 個(gè)函數(shù)的識(shí)別碼不一致。

其中的一些改變是由于插入的預(yù)處理代碼。舉個(gè)例子,這里有一個(gè)發(fā)生了更改的函數(shù)(已經(jīng)刪去一些 Sqlite 特有的定義):

  1. #define LARGEST_INT64 (0xffffffff|(((int64_t)0x7fffffff)<<32))
  2. #define SMALLEST_INT64 (((int64_t)-1) - LARGEST_INT64)
  3.  
  4. static int64_t doubleToInt64(double r){
  5. /*
  6. ** Many compilers we encounter do not define constants for the
  7. ** minimum and maximum 64-bit integers, or they define them
  8. ** inconsistently. And many do not understand the "LL" notation.
  9. ** So we define our own static constants here using nothing
  10. ** larger than a 32-bit integer constant.
  11. */
  12. static const int64_t maxInt = LARGEST_INT64;
  13. static const int64_t minInt = SMALLEST_INT64;
  14.  
  15. if( r<=(double)minInt ){
  16. return minInt;
  17. }else if( r>=(double)maxInt ){
  18. return maxInt;
  19. }else{
  20. return (int64_t)r;
  21. }
  22. }

刪去 const 使得這些常量變成了 static 變量。我不明白為什么會(huì)有不了解 const 的人讓這些變量加上 static。同時(shí)刪去 staticconst 會(huì)讓 GCC 再次認(rèn)為它們是常量,而我們將得到同樣的編譯輸出。由于類似這樣的局部的 static const 變量,使得 13 個(gè)函數(shù)中有 3 個(gè)函數(shù)產(chǎn)生假的變化,但我一個(gè)都不打算修復(fù)它們。

Sqlite 使用了很多全局變量,而這正是大多數(shù)真正的 const 優(yōu)化產(chǎn)生的地方。通常情況下,它們類似于將一個(gè)變量比較代替成一個(gè)常量比較,或者一個(gè)循環(huán)在部分展開(kāi)的一步。(Radare toolkit 可以很方便的找出這些優(yōu)化措施。)一些變化則令人失望。sqlite3ParseUri() 有 487 個(gè)指令,但 const 產(chǎn)生的唯一區(qū)別是進(jìn)行了這個(gè)比較:

  1. test %al, %al
  2. je <sqlite3ParseUri+0x717>
  3. cmp $0x23, %al
  4. je <sqlite3ParseUri+0x717>

并交換了它們的順序:

  1. cmp $0x23, %al
  2. je <sqlite3ParseUri+0x717>
  3. test %al, %al
  4. je <sqlite3ParseUri+0x717>

基準(zhǔn)測(cè)試

Sqlite 自帶了一個(gè)性能回歸測(cè)試,因此我嘗試每個(gè)版本的代碼執(zhí)行一百次,仍然使用默認(rèn)的 Sqlite 編譯設(shè)置。以秒為單位的測(cè)試結(jié)果如下:

  const 非 const
最小值 10.658s 10.803s
中間值 11.571s 11.519s
最大值 11.832s 11.658s
平均值 11.531s 11.492s

就我個(gè)人看來(lái),我沒(méi)有發(fā)現(xiàn)足夠的證據(jù)來(lái)說(shuō)明這個(gè)差異值得關(guān)注。我是說(shuō),我從整個(gè)程序中刪去 const,所以如果它有明顯的差別,那么我希望它是顯而易見(jiàn)的。但也許你關(guān)心任何微小的差異,因?yàn)槟阏谧鲆恍┙^對(duì)性能非常重要的事。那讓我們?cè)囈幌陆y(tǒng)計(jì)分析。

我喜歡使用類似 Mann-Whitney U 檢驗(yàn)這樣的東西。它類似于更著名的 T 檢驗(yàn),但對(duì)你在機(jī)器上計(jì)時(shí)時(shí)產(chǎn)生的復(fù)雜隨機(jī)變量(由于不可預(yù)測(cè)的上下文切換、頁(yè)錯(cuò)誤等)更加健壯。以下是結(jié)果:

  const 非 const
N 100 100
Mean rank 121.38 79.62

 

   
Mann-Whitney U 2912
Z -5.10
2-sided p value <10-6
HL median difference -0.056s
95% confidence interval -0.077s – -0.038s

U 檢驗(yàn)已經(jīng)發(fā)現(xiàn)統(tǒng)計(jì)意義上具有顯著的性能差異。但是,令人驚訝的是,實(shí)際上是非 const 版本更快——大約 60ms,0.5%。似乎 const 啟用的少量“優(yōu)化”不值得額外代碼的開(kāi)銷。這不像是 const 啟用了任何類似于自動(dòng)矢量化的重要的優(yōu)化。當(dāng)然,你的結(jié)果可能因?yàn)榫幾g器配置、編譯器版本或者代碼庫(kù)等等而有所不同,但是我覺(jué)得這已經(jīng)說(shuō)明了 const 是否能夠有效地提高 C 的性能,我們現(xiàn)在已經(jīng)看到答案了。

那么,const 有什么用呢?

盡管存在缺陷,C/C++ 的 const 仍有助于類型安全。特別是,結(jié)合 C++ 的移動(dòng)語(yǔ)義和 std::unique_pointerconst 可以使指針?biāo)袡?quán)顯式化。在超過(guò)十萬(wàn)行代碼的 C++ 舊代碼庫(kù)里,指針?biāo)袡?quán)模糊是一個(gè)大難題,我對(duì)此深有感觸。

但是,我以前常常使用 const 來(lái)實(shí)現(xiàn)有意義的類型安全。我曾聽(tīng)說(shuō)過(guò)基于性能上的原因,最好是盡可能多地使用 const。我曾聽(tīng)說(shuō)過(guò)當(dāng)性能很重要時(shí),重構(gòu)代碼并添加更多的 const 非常重要,即使以降低代碼可讀性的方式。當(dāng)時(shí)覺(jué)得這沒(méi)問(wèn)題,但后來(lái)我才知道這并不對(duì)。 

責(zé)任編輯:龐桂玉 來(lái)源: Linux中國(guó)
相關(guān)推薦

2012-06-13 01:53:23

Java代碼

2022-05-05 09:31:58

JOIN數(shù)據(jù)庫(kù)

2009-06-23 18:00:11

微軟Windows 7瘦身

2021-08-06 22:51:45

CPU限流容器

2012-05-17 14:37:33

SAPHANA邁凱輪

2011-07-06 10:48:12

ADSL

2011-07-06 10:27:32

ADSL

2022-03-21 15:31:52

人工智能機(jī)器人機(jī)器學(xué)習(xí)

2011-07-06 10:48:42

ADSL

2009-12-08 18:34:49

Windows 7 w

2020-10-18 07:21:34

CPU代碼執(zhí)行效率

2014-08-28 09:35:32

Node.js前端開(kāi)發(fā)

2017-05-26 08:23:23

路由器WiFi重啟

2009-11-16 08:54:42

Windows 7系統(tǒng)加速

2023-09-20 00:06:30

Python代碼函數(shù)

2021-01-13 10:51:08

PromissetTimeout(函數(shù)

2023-09-14 15:48:53

排序測(cè)試

2023-09-22 16:28:34

C++編程

2025-05-29 01:53:22

前端代碼開(kāi)發(fā)

2025-03-06 00:24:43

C#編程代碼
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 精品美女视频在线观看免费软件 | 日韩精品免费播放 | 国产偷录叫床高潮录音 | 涩涩操| 亚洲一区二区不卡在线观看 | 亚洲午夜在线 | 亚洲欧美日韩久久 | 欧美一区二区三区四区五区无卡码 | 久久精品国产99国产精品 | 日韩中文字幕在线观看 | 一区二区三区免费网站 | 91精品国产色综合久久不卡98口 | 久久久久国产一区二区三区 | 久久三区| 视频一区在线 | 天天宗合网 | 国产精品一卡 | av一级一片 | 在线观看免费黄色片 | 久久综合一区 | 在线观看国产h | 国产精品视频区 | 欧美国产日韩在线观看成人 | 国产精品一区一区三区 | 精品久久久久久久久久久下田 | www..com18午夜观看 | 成人欧美一区二区三区在线观看 | 亚洲国产精品99久久久久久久久 | 日韩精品在线网站 | 欧美久久一区二区 | 日本精品视频在线 | 成人免费观看男女羞羞视频 | 动漫www.被爆羞羞av44 | 免费成年网站 | 日韩精品免费视频 | 人人玩人人干 | 亚洲精品久久国产高清情趣图文 | caoporn免费 | 国产a一区二区 | 不卡欧美 | 欧美伊人久久久久久久久影院 |