精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

V8 引擎:基于類型推測的性能優化原理

開發 前端
本文的會介紹一些關于V8內基于推測的優化的技術,以此來告訴大家,為什么需要TypeScript。

介紹

本文的會介紹一些關于V8內基于推測的優化的技術,以此來告訴大家,為什么需要TypeScript。

我們將以一段函數的執行未展開,從函數執行的角度來看看,一段代碼如何被執行,優化,再最后,你會了解,為什么TypeScript更好。

看完本文后,你不需要記住文章中出現的繁雜的指令和代碼,只需要在你的腦海中存在一個印象,避免寫出糟糕的代碼,以及,盡量使用TypeScript。

如何執行代碼?

作為介紹的第一部分,我們會用一段簡短的篇幅帶大家看看,你的代碼如何被執行

圖片

當然,如果用簡單的流程圖表示,你可以把上面的過程理解為這樣一個線性的執行過程,當然可能并不嚴謹,稍后我們會繼續介紹。

圖片

下面讓我們從一段具體的代碼來看一下這個過程。

一段簡單的代碼?

function add(x, y) {
return x + y;
}
console.log(add(1, 2))

如果你在chrome的DevTools console中運行這段代碼,你可以看到預期的輸出值3。

圖片

根據上面的流程圖,這段代碼被執行的第一步,是被解析器解析為AST,這一步我們用d8 shell 的Debug版本中使用 –print-ast 命令來查看V8內部生成的AST。

$ out/Debug/d8 --print-ast add.js
-
-- AST ---
FUNC at 12
. KIND 0
. SUSPEND COUNT 0
. NAME "add"
. PARAMS
. . VAR (0x7fbd5e818210) (mode = VAR) "x"
. . VAR (0x7fbd5e818240) (mode = VAR) "y"
. RETURN at 23
. . ADD at 32
. . . VAR PROXY parameter[0] (0x7fbd5e818210) (mode = VAR) "x"
. . . VAR PROXY parameter[1] (0x7fbd5e818240) (mode = VAR) "y

很多人可能或多或少接觸過AST的概念,這里不多贅述,只是用一張簡單的圖表示下上面的過程。

圖片

最開始,函數字面量add被解析為樹形表示,其中一個子樹用于參數聲明,另外一個子樹用于實際的的函數體。在解析階段,不可能知道程序中名稱和變量的綁定關系,這主要是因為“有趣的變量聲明提升規則”以及JavaScript中的eval,此外還有其他原因。

一旦我們構建完成了AST,它便包含了從中生成可執行字節碼的所有必要信息。AST隨后被傳遞給BytecodeGenerator ,BytecodeGenerator 是屬于Ignition 的一部分,它以函數為單位生成字節碼(_其他引擎并不一定以函數為單位生成的_)。你也可以在d8中使用命令–print-bytecode來查看V8生成的字節碼(或者用node端)

$ out/Debug/d8 --print-bytecode add.js
[
generated bytecode for function: add]
Parameter count 3
Frame size 0
12 E> 0x37738712a02a @ 0 : 94 StackCheck
23 S> 0x37738712a02b @ 1 : 1d 02 Ldar a1
32 E> 0x37738712a02d @ 3 : 29 03 00 Add a0, [0]
36 S> 0x37738712a030 @ 6 : 98 Return
Constant pool (size = 0)
Handler Table (size = 16)

上面過程中為函數add生成了一個新的字節碼對象,它接受三個參數,一個內部的this引用,以及兩個顯式形參x和y。該函數不需要任何的局部變量(所以棧幀大小為0),并且包含下面這四個字節碼指令組成的序列

StackCheck
Ldar a1
Add a0, [0]
Return

為了解釋這段字節碼,我們首先需要從較高的層面來認知解釋器如何工作。V8的解釋器是基于寄存器架構(register machine)的(相對的是基于棧架構,也是早期V8版本中使用的 FullCodegen 編譯器)。Ignition 會把指令序列都保存在解釋器自身的(虛擬)寄存器中,這些寄存器部分被映射到實際CPU的寄存器中,而另外一部分會用實際機器的棧內存來模擬。

圖片

有兩個特殊寄存器a0和a1對應著函數在機器棧(即內存棧)上的形式參數(在函數add這個例子中,有兩個形參)。形參是在源代碼中聲名的參數,它可能與在運行時傳遞給函數的實際參數數量不同。每個字節碼指令執行得到的最終值通常被保存在一個稱作累加器(accumulator)的特殊寄存器中。堆棧指針(stack pointer )指向當前的棧幀或者說激活記錄,程序計數器( program counter)指向指令字節碼中當前正在執行的指令。下面我們看看這個例子中每條字節碼指令都做了什么。

  • StackCheck 會將堆棧指針與一些已知的上限比較(實際上在V8中應該稱作下限,因為棧是從高地址到低地址向下生長的)。如果棧的增長超過了某個閾值,就會放棄函數的執行,同時拋出一個 RangeError 來告訴我們棧溢出了。
  • Ldar a1將寄存器a1的值加載到累加器寄存器中(Ladr 表示 LoaD Accumulator Register)
  • Add a0, [0] 讀取寄存器a0里的值,并把它加到累加器的值上。結果被再次放到累加器中。

為什么這條指令是這樣的?以一條JS 語句為例

var dest = src1 + src2 // op dest, src1,src2
var dest += src; //op dest, src
+src; // op src

分別表示三地址指令,二地址指令,一地址指令,我在后面分別標注了轉換后的機器指令。三地址和二地址指令都指定了運算后儲存結果的位置。

但在一地址指令中,沒有指定目標源。實際上,它會被默認存在一個累加器”(accumulator)的專用寄存器,保存計算結果。

其中Add運算符的[0]操作數指向一個「反饋向量槽( feedback vector slot)」,它是解釋器用來儲存有關函數執行期間看到的值的分析信息。在后面講解TurboFan 如何優化函數的時候會再次回到這。

  • Return 結束當前函數的執行,并把控制權交給調用者。返回值是累加器中的當前值。

當最終生成了上面這段字節碼后,會被送入的VM ,一般會由解釋器進行執行,這種執行方式是最原始也是效率最低的。我們可以在下一部分了解到,這種原始的執行會經歷什么。

關于字節碼的解釋,這里不會做過多的贅述,如果你感興趣,可以擴展閱讀 「Understanding V8’s Bytecode」 (https://medium.com/dailyjs/understanding-v8s-bytecode-317d46c94775) 一文,這篇字節碼對V8的字節碼的工作原理提供了一些深入的了解。

為什么需要優化?

現在,我相信你已經對V8如何執行一段代碼有了一個簡單的認識。在正式進入我們的主題之前,還需要解釋一個很關鍵的問題,為什么我們需要優化。為了回答這個問題,我們需要先看下下規范

關于規范的更多了解,可以去這里查找 https://tc39.es/ecma262/#sec-toprimitive

圖片

圖片

我們再以看最常見的 ToPrimitive 為例,需要經過非常繁瑣的求值過程,而這些過程都是為了解決操作類型的動態性

圖片

在JavaScript中的“+”運算符已經是一個相當復雜的操作了,在最終執行一個數值相加之前必須進行大量的檢查

而如果引擎要想讓這些步驟是能夠在幾個機器指令內完成以達到峰值性能(與C++相媲美),這里有一個關鍵能力—-推測優化,通過假設可能的輸入。例如,當我們知道表達式x+y中,x和y都是數字,那么我們就不需要處理任何一個是字符串或者其他更糟糕的情況—-操作數是任意類型的JavaScript對象,也就不需要對所有參數調用一遍 ToPrimitive 了。

換句話說,如果我們能夠確定x,y 都是數字類型,我們自然就很容易對這個函數執行進行優化,消除冗余的IR指令。

「而從執行的角度來說,動態類型性能瓶頸很大程度是因為它的動態的類型系統,與靜態類型的語言相比,JavaScript 程序需要額外的操作來處理類型的動態性,所以執行效率比較低。」

那么如何確認x,y都是數字,我們又如何優化呢?

基于推測的優化?

因為 JavaScript 動態語言的特性,我們通常直到運行時才知道值的確切類型,僅僅觀察源代碼,往往不可能知道某個操作的可能輸入值。所以這就是為什么我們需要推測,根據之前運行收集到的值的反饋,然后假設將來總會看到類似的值。這種方法聽起來可能作用相當有限,但它已被證明適用于JavaScript這樣的動態語言。

你可能會聯想到CPU的分支預測能力,如果是這樣,那么恭喜你,你并沒有想錯。

我們再回到這段代碼

function add(x, y) {
return x + y;
}

你可能已經想到了,作為一個動態類型的語言,推測的第一步,就是要收集到足夠多的信息,來預測 ??add?? 在今后的執行中會遇到的類型。

所以,首先向你介紹反饋向量(Feedback Vector),它是我們執行預測最核心的成員之一:負責儲存我們收集到的信息。

反饋向量(Feedback Vector)

當一段代碼被初次執行時,它所執行的往往是解釋器產生的字節碼。當這段字節碼每次的執行后,都會會產生一些反饋信息,這些反饋信息會被儲存在「反饋向量」(過去叫類型反饋向量) 中,這個特殊的數據結構會被鏈接在閉包上。如果從對象結構的角度來看,反饋向量和其他相關的內容會是這樣。

圖片

其中 SharedFunctionInfo,它包含了函數的一般信息,比如源位置,字節碼,嚴格或一般模式。除此之外,還有一個指向上下文的指針,其中包含自由變量的值以及對全局對象的訪問。

關于自由變量和約束變量的概念, 閉包 (計算機科學)

反饋向量的大致結構如下,slot是一個槽,表示向量表里面的一項,包含了操作類型和傳入的值類型,

IC Slot

IC Type

Value

1

Call

UNINIT

2

BinaryOp

SignedSmall

比如,第二個是一個 BinaryOp 槽,二元操作符類似“+,-”等能夠記錄迄今為止看到的輸入和輸出的反饋。先不用糾結它的含義,后面我們會具體介紹。

如果你想查看你的函數所對應的反饋向量,可以在你的代碼中加上專門的內部函數 ??%DebugPrint???  ,并且在d8中加上命令 ??–allow-natives-syntax?? 來檢查特定閉包的反饋向量的內容。

源代碼:

function add(x, y) {
return x + y;
}
console.log(add(1, 2));
%DebugPrint(add);

在d8 使用這個命令 –allow-natives-syntax 運行,我們看到 :

$ out/Debug/d8 --allow-natives-syntax add.js
DebugPrint: 0xb5101ea9d89: [Function] in OldSpace
- feedback vector: 0xb5101eaa091: [FeedbackVector] in OldSpace
- length: 1
SharedFunctionInfo: 0xb5101ea99c9 <SharedFunctionInfo add>
Optimized Code: 0
Invocation Count: 1
Profiler Ticks: 0
Slot #0 BinaryOp BinaryOp:SignedSmall

我們看到調用次數(Invocation Count)是1,因為我們只調用了一次函數add。此時還沒有優化代碼(根據Optimized Code的值為0)。反饋向量的長度為1,說明里面只有一個槽,就是我們上面說到的二元操作符槽(BinaryOp Slot),當前反饋為 SignedSmall。

這個反饋SignedSmall代表什么?這表明指令Add只看到了SignedSmall類型的輸入,并且直到現在也只產生了SignedSmall類型的輸出。

但是什么是SignedSmall類型?JavaScript里面并不存在這種類型。實際上,SignedSmall來自己V8中的一種優化策略,它表示在程序中經常使用的小的有符號整數(V8將高位的32位表示整數,低位的全部置0來表示SignedSmall),這種類型能夠獲得特殊處理(其他JavaScript引擎也有類似的優化策略)。

「值的表示」

V8通常使用一種叫做指針標記(Pointer Tagging)的技術來表示值,應用這種技術,V8在每個值里面都設置一個標識。我們處理的大部分值都分配在JavaScript堆上,并且由垃圾回收器(GC)來管理。但是對某些值來說,總是將它們分配在內存里開銷會很大。尤其是對于小整數,它們通常會用在數組索引和一些臨時計算結果。

圖片

在V8中存在兩種指針標識類型:分別是是Smi(即 Small Integer的縮寫)和堆對象( HeapObject,就是JavaScript的引用類型),其中堆對象是分配在內存的堆中,圖中的地址即指向堆中的某塊地方。

我們用最低有效位來區分堆對象(標志是1)和小整數(標志是0)。對于64位結構上的Smi,至少有32位有效位(低半部)是一直被置為0。另外32位,也就是Word的上半部,是被用來儲存32位有符號小整數的值。

僅僅是一次的執行,還不足以讓引擎這么快下定決心,相信add 函數隨后的執行都是Smi 類型。那么我們先來看看,如果在隨后的執行中,我們傳入不一樣的類型會怎么樣。

反饋向量的變化

反饋類型SignedSmall是指所有能用小整數表示的值。對于add操作而言,這意味著目前為止它只能看到輸入類型為Smi,并且所產生的輸出值也都是Smi(也就是說,所有的值都沒有超過32位整數的范圍)。下面我們來看看,當我們調用add的時候傳入一個不是Smi的值會發生什么。

function add(x, y) {
return x + y;
}
console.log(add(1, 2));
console.log(add(1.1, 2.2));
//調用100ci
%DebugPrint(add);

在d8加入命令 –allow-natives-syntax ,然后看到下面結果。

$ out/Debug/d8 --allow-natives-syntax add.js
DebugPrint: 0xb5101ea9d89: [Function] in OldSpace

- feedback vector: 0x3fd6ea9ef9: [FeedbackVector] in OldSpace
- length: 1
SharedFunctionInfo: 0x3fd6ea9989 <SharedFunctionInfo add>
Optimized Code: 0
Invocation Count: 2
Profiler Ticks: 0
Slot #0 BinaryOp BinaryOp:Number

首先,我們看到調用次數現在是2,因為運行了兩次函數add。然后發現BinaryOp 槽的值現在變成了Number,這表明對于這個加法已經有傳入了任意類型的數值(即非整數)。此外,這有一個反饋向量的狀態遷移圖,大致如下所示:

圖片

反饋狀態從 None 開始,這表明目前還沒有看到任何輸入,所以什么都不知道。狀態Any表明我們看到了不兼容的(比如number和string)輸入和輸出的組合。狀態Any意味著Add(字節碼中的)是多態。相比之下,其余的狀態表明Add都是單態(monomorphic),因為看到的輸入和產生的都是相同類型的值。下面是圖中名詞解釋:

  • SignedSmall 表示所有的值都是小整數(有效數值為是32位或者31位,取決于Word的在不同架構上的大小),均表示為Smi。
  • Number 表明所有的值都常規數字 (這包括小整數)。
  • NumberOrOddball 包括其他能被轉換成 Number 的 undefined, null, true 和 false 。
  • String :所有輸入值都是字符串
  • BigInt 表示輸入都是大整數。

需要注意一點,反饋只能在這個圖中前進(從 None 到 Any),不能回退。如果真的那樣做,那么我們就會有陷入去優化循環的風險。那樣情況下,優化編譯器發現輸入值與之前得到反饋內容不同,比如之前解釋器生成的反饋是 Number,但現在輸入值出現了 String,這時候已經生成的反饋和優化代碼就會失效,并回退到解釋器生成的字節碼版本。當下一次函數再次變熱(hot,多次運行),我們將再次優化它,如果允許回退,這時候優化編譯器會再次生成相同的代碼,這意味著會再次回到 Number 的情況。如果這樣無限制的回退去優化,再優化,編譯器將會忙于優化和去優化,而不是高速運行 JavaScript 代碼。

優化管道(The Optimization Pipeline)

現在我們知道了解釋器Ignition 是如何為函數add收集反饋,下面來看看優化編譯器如何利用反饋生成最小的代碼,因為_越小的機器指令代碼塊,意味著更快的速度_。為了觀察,我將使用一個特殊的內部函數OptimizeFunctionOnNextCall()在特定的時間點觸發V8對函數的優化。我們經常使用這些內部函數以非常特定的方式對引擎進行測試。

function add(x, y) {
return x + y;
}
add(1, 2); // Warm up with SignedSmall feedback.
%OptimizeFunctionOnNextCall(add);
add(1, 2); // Optimize and run generated code

在這里,給函數add傳遞兩個整數型值來明確call site “x + y”的反饋會被預熱為小整數(表示_這個call site全部傳遞的都是小整數,對于優化引擎來說將來得到的輸入也會是小整數_),并且結果也是屬于小整數范圍。然后我們告訴V8應該在下次調用函數add的時候去優化它(用TurboFan ),最終再次調用add,觸發優化編譯器運行生成機器碼。

圖片

TurboFan 拿到之前為函數add生成的字節碼,并從函數add的反饋向量表里提取出相關的反饋。優化編譯器將這些信息轉換成一個圖表示,再將這個圖表示傳遞給前端,優化以及后端的各個階段(見上圖)。在本文不會詳細展開這部分內容,這是另一個系列的內容了。我們要了解的是最終生成的機器碼,并看看優化推測是如何工作的。你可以在d8中加上命令 –print-opt-code來查看由TurboFan 生成的優化代碼。

圖片

這是由TurboFan 在x64架構上生成的機器碼,這里省略了一些無關緊要的技術細節(,下面就來看看這些代碼做了什么。

# Prologue
leaq rcx,[rip+0x0]
movq rcx,[rcx-0x37]
testb [rcx+0xf],0x1
jnz CompileLazyDeoptimizedCode
push rbp
movq rbp,rsp
push rsi
push rdi
cmpq rsp,[r13+0xdb0]
jna StackCheck

第一段代碼檢查對象是否仍然有效(對象的形狀是否符合之前生成機器碼的那個),或者某些條件是否發生了改變,這就需要丟棄這個優化代碼。這部分具體內容可以參考 Juliana Franco 的 “Internship on Laziness“。一旦我們知道這段代碼仍然有效,就會建立一個棧幀并且檢查堆棧上是否有足夠的空間來執行代碼。

# Check x is a small integer
movq rax,[rbp+0x18]
test al,0x1
jnz Deoptimize
# Check y is a small integer
movq rbx,[rbp+0x10]
testb rbx,0x1
jnz Deoptimize
# Convert y from Smi to Word32
movq rdx,rbx
shrq rdx, 32
# Convert x from Smi to Word32
movq rcx,rax
shrq rcx, 32

然后從函數主體開始。我們從棧中讀取參數x和y的值(相對于幀指針rbp,比如rbp+1這樣的地址,請參考棧幀概念),然后檢查兩個參數是否都是 Smi 類型(因為根據“+”得到的反饋,兩個輸入總是Smi)。這一步是通過測試最低有效位來完成。一旦確定了參數都是Smi,我們需要將它轉換成32位表示,這是通過將值右移32位來完成的。如果x或y不是Smi,則會立即終止執行優化代碼,接著負責去優化的模塊就會恢復到之前解釋器生成的函數add的代碼(即字節碼)。

# Add x and y (incl. overflow check)
addl rdx,rcx
jo Deoptimize
# Convert result to Smi
shlq rdx, 32
movq rax,rdx
# Epilogue
movq rsp,rbp
pop rbp
ret 0x18

然后我們繼續執行對輸入值的整數加法,這時需要明確地測試溢出,因為加法的結果可能超出32位整數的范圍,在這種情況下就要返回到解釋器版本,并在隨后將add的反饋類型提升為Number(之前說過,反饋類型的改變只能前進)。

最后我們通過將帶符號的32位值向上移動32位,將結果轉換回Smi表示,并將結果返回存到累加器rax 。

我們現在可以看到生成的代碼是高度優化的,并且適用于專門的反饋。它完全不去處理其他數字,字符串,大整數或任意JavaScript對象,只關注目前為止我們所看到的那種類型的值。這是使許多JavaScript應用程序達到最佳性能的關鍵因素。

為什么需要TypeScript?

在上面的介紹中,我們竭力避免了對JavaScript 對象的訪問,如果有對象加入,這將會變成一個很復雜的話題。但為了更好的展開這個話題,我們還是需要提一下,關于對象的優化是V8中極其重要的一部分。例如,以下面這個對象為例

var o = {
x: ''
}
var o1 = {
x: ''
y
}
//o1. o2

對于像 o.x這樣的屬性訪問,若o始終具有相同的形狀(形狀同結構,即相同的屬性以及屬性是相同的順序,例如o的結構一直是{x:v},其中v的類型是String),我們會把如何獲得o.x的過程信息緩存起來,構造成一個隱藏類( Hidden Class)。在隨后執行相同的字節碼時,不需要再次搜索對象o中x的位置。這種底層實現被稱為內聯緩存– inline cache (IC)。

你可以在Vyacheslav Egoro寫的這篇文章 “What’s up with monomorphism?” 中了解更多關于ICs和屬性訪問的細節。

總而言之,你現在應該了解到,作為一門弱類型的語言,從最早的SELF和smalltalk 語言開始,研究者就在不斷去優化這種弱類型語言的執行效率。

「從執行的角度來說,動態類型性能瓶頸很大程度是因為它的動態的類型系統,與靜態類型的語言相比, JavaScript 程序需要額外的操作來處理類型的動態性,所以執行效率比較低。」

說了這么多,最關鍵的一點

「確定你的代碼將要看到的類型很重要」

再加上另外一句話:

「作為動態語言,你的程序可能在90%的時間里,都在處理和代碼邏輯無關的事情。即:確認你的代碼是什么形狀」

從傳統的JavaScript 角度來說。

function add(x, y) {
return x + y;
}

你無法很好的保證 add 函數將要看到的類型,哪怕你確實想要這么做。但在一個大型的系統中,維護每一個函數和對象的形狀,極其困難。

你可能在前99次都保證了add 看到的都是Smi 類型,但是在第100次,add 看到了一個String,而在這之前,優化編輯器,即TurboFan,已經大膽的推測了你的函數只會看到Smi,那么這時候

Ops!

優化編輯器將不得不認為自己做了個錯誤的預測,它會立即把之前的優化丟掉。從字節碼開始重新執行。

而如果你的代碼一直陷入優化<->去優化的怪圈,那么程序執行將會變慢,慢到還不如不優化。

大多數的瀏覽器都做了限制,當優化/去優化循環發生的時候會嘗試跳出這種循環。比如,如果 JIT 做了 10 次以上的優化并且又丟棄的操作,那么就不繼續嘗試去優化這段代碼。

圖片

所以,到這里你應該明白了,有兩點準則:

  1. 「確保你的代碼是什么形狀很重要」

但比第一條更重要的是:

  1. 「確保你的代碼固定在某個形狀上」

而編寫TypeScript ,從工程和語言的層面上幫助你解決了這兩個準則,你可以暢快的使用TypeScript,而無需擔心你是否不小心違背了上面兩條準則。

責任編輯:姜華 來源: Tecvan
相關推薦

2017-12-17 16:34:18

JavaScript代碼V8

2021-05-28 05:30:55

HandleV8代碼

2022-06-02 12:02:12

V8C++JavaScript

2022-04-29 08:00:51

V8垃圾回收

2020-10-25 08:22:28

V8 引擎JavaScript回調函數

2022-02-25 08:32:07

nodemon搭Node.jsJavascript

2022-06-21 08:52:47

Node.js服務端JavaScript

2025-09-08 01:55:00

2023-10-10 10:23:50

JavaScriptV8

2020-09-27 07:32:18

V8

2009-08-21 10:09:02

Google ChroV8引擎linux系統

2009-07-20 09:36:04

谷歌瀏覽器安全漏洞

2022-11-04 07:12:24

JavaScript基準測試

2010-07-20 16:35:52

V8JavaScript瀏覽器

2023-06-07 16:00:40

JavaScriptV8語言

2022-09-16 08:32:25

JavaC++語言

2023-06-05 16:38:51

JavaScript編程語言V8

2024-06-27 11:22:34

2014-11-26 09:51:24

GithubGoogleV8

2010-08-31 11:42:03

DB2MDC
點贊
收藏

51CTO技術棧公眾號

国产91精品精华液一区二区三区 | 欧美日韩高清区| 一个色综合久久| 91中文在线| 成人激情黄色小说| 日韩免费观看av| 开心激情五月网| 欧美成人精品午夜一区二区| 午夜视频一区二区| 亚洲国产日韩欧美| 不卡av中文字幕| 麻豆91精品| 欧美成人午夜免费视在线看片| 国产在线不卡av| 成人在线视频观看| 亚洲一区二区影院| 日韩精品最新在线观看| 精品久久久久久亚洲综合网站| 亚洲三级国产| 久久精品国产96久久久香蕉| 丰满岳乱妇一区二区| 久久99久久久精品欧美| 激情成人中文字幕| 日本一道在线观看| 国内在线精品| eeuss影院一区二区三区| 国产日韩在线视频| 亚洲 欧美 中文字幕| 欧美成熟视频| 自拍偷拍亚洲区| 国产一级二级视频| 亚洲精品a区| 欧美色综合久久| 国产69精品久久久久久久| а√天堂8资源在线官网| 久久精品一级爱片| 久久精品国产一区二区三区不卡| 国产熟女一区二区三区四区| 免费观看在线色综合| 午夜免费久久久久| 久久亚洲国产成人精品性色| 欧美激情黄色片| 在线观看视频亚洲| 丰满少妇高潮一区二区| 久久免费视频66| 欧美白人最猛性xxxxx69交| 97超碰成人在线| 小明成人免费视频一区| 黑人巨大精品欧美一区二区一视频| 国产一级不卡视频| a级影片在线观看| 亚洲欧美怡红院| 一本久久a久久精品vr综合| 黄色av免费在线观看| www.亚洲色图| 韩国成人一区| 免费看国产片在线观看| hitomi一区二区三区精品| 国产精品区一区二区三含羞草| 精品国产无码一区二区三区| 国产不卡视频在线播放| 99久久国产免费免费| 亚洲AV午夜精品| 国产成人激情av| 国产高清精品一区二区三区| www.国产视频| 国产69精品久久777的优势| av一区二区三区在线观看| 国产按摩一区二区三区| 成人午夜在线播放| 精品日韩电影| 国产三级在线免费| 国产精品久久久久久亚洲伦| 一区二区三区视频| 99热国产在线| 五月天一区二区三区| 国产特级黄色大片| 日韩av中字| 欧美日韩亚洲不卡| 无套白嫩进入乌克兰美女| av成人资源网| 精品视频在线播放| wwwww黄色| 91精品一区二区三区综合| 欧美成人激情视频免费观看| 日韩av片在线播放| 日韩激情av在线| 成人在线播放av| 欧美77777| 欧美国产欧美综合| 麻豆传媒网站在线观看| 国产ktv在线视频| 色婷婷激情久久| 亚洲欧美天堂在线| 精品久久对白| 北条麻妃在线一区二区| 国产无码精品久久久| 久久一综合视频| 91夜夜揉人人捏人人添红杏| 色视频在线看| 亚洲视频综合在线| 欧洲黄色一级视频| 久久亚洲精品人成综合网| 精品国产欧美一区二区| 男女做爰猛烈刺激| 欧美不卡一区| 国产精品高潮呻吟久久av野狼| 99国产精品99| 国产午夜亚洲精品羞羞网站| av一区二区三区免费观看| 电影久久久久久| 亚洲成人亚洲激情| 91人妻一区二区三区蜜臀| 国产伦理一区| 国产福利不卡| 久操免费在线| 欧美色涩在线第一页| 在线观看国产免费视频| 欧美在线高清| 国产精品在线看| 亚洲av毛片成人精品| 亚洲精品日韩综合观看成人91| 亚洲国产精品毛片av不卡在线| 136福利精品导航| 久久视频免费观看| 国产乱码在线观看| 91视频免费播放| 国产精品www在线观看| 亚洲精品成a人ⅴ香蕉片| 亚洲性猛交xxxxwww| 制服.丝袜.亚洲.中文.综合懂色| 国产精品 欧美精品| 亚洲欧洲精品一区二区三区波多野1战4| 欧美调教sm| 亚洲精品一区在线观看| 看免费黄色录像| 极品尤物av久久免费看| 午夜欧美一区二区三区免费观看| 成人教育av| 日韩av中文字幕在线免费观看| 亚洲一级二级片| 欧美福利网址| 91在线视频免费| eeuss影院在线观看| 欧美日韩美女在线观看| 精品影片一区二区入口| 99久久视频| 国产精品爽爽爽| 九色在线播放| 精品日韩中文字幕| 成人免费网站黄| 国产日韩一区二区三区在线| 成人18视频| 国产三区在线观看| 欧美日韩一级视频| 婷婷色一区二区三区| 秋霞电影网一区二区| 欧美日韩精品一区| 九色porny丨首页入口在线| 精品国产免费视频| 国产亚洲成人av| 成人午夜激情片| 免费特级黄色片| 成人香蕉社区| 97久久久免费福利网址| 黄色美女一级片| 狠狠色香婷婷久久亚洲精品| bl动漫在线观看| 影音先锋中文字幕一区| 国产精品久久九九| 91福利区在线观看| 亚洲精品视频免费在线观看| 成人在线免费看视频| 91麻豆免费观看| 欧美极品欧美精品欧美图片| 九九热线有精品视频99| 国产精品一区二区三区免费视频| av在线二区| 91精品国产一区二区| 国产a免费视频| 粉嫩在线一区二区三区视频| 免费成人在线视频网站| 伊人久久大香线蕉av不卡| 青青草原成人在线视频| 高清毛片在线看| 91精品国产综合久久久蜜臀图片| 国产精品9191| 久久久综合视频| 日本xxxx黄色| 午夜电影亚洲| 久草精品电影| 亚洲日本免费电影| 欧美精品久久久久a| 午夜18视频在线观看| 日本韩国视频一区二区| 亚洲欧美日韩第一页| 国产米奇在线777精品观看| 国产午夜精品视频一区二区三区| 国产精品流白浆在线观看| 青草青草久热精品视频在线网站 | 欧美h片在线观看| 国产一区二区美女| 日韩欧美国产综合在线| 国内精品久久久久久99蜜桃| 亚洲精品女av网站| 欧美最新精品| 欧美久久精品午夜青青大伊人| 日韩在线观看视频网站| 欧美亚洲动漫精品| 久草资源在线视频| 国产精品国产三级国产aⅴ原创 | 韩国三级成人在线| 国产69精品久久久久久| 麻豆传媒视频在线观看免费| 亚洲成人教育av| 精品黑人一区二区三区国语馆| 黑人狂躁日本妞一区二区三区 | 性视频1819p久久| 国产www.大片在线| 亚洲成人激情图| 国产熟女一区二区三区五月婷| 日韩欧美成人精品| 中文字幕影音先锋| 国产欧美精品一区| 无码人妻精品一区二区三区99不卡| 日本在线观看不卡视频| 日韩最新中文字幕| 日韩免费久久| 欧美日韩精品免费看| 98视频精品全部国产| 成人国产精品久久久| 国产精品毛片久久久久久久久久99999999 | 三级在线免费看| 国产亚洲欧洲| 国产在线视频综合| 欧美成人直播| 亚洲黄色一区二区三区| 亚洲传媒在线| 成人高清在线观看| 国产精品成人3p一区二区三区| 51色欧美片视频在线观看| 菠萝蜜视频在线观看www入口| 日韩视频―中文字幕| 国产69精品久久app免费版| 亚洲精品之草原avav久久| 精品女同一区二区三区| 欧美久久一二三四区| 亚洲婷婷久久综合| 欧美无砖专区一中文字| 亚洲婷婷综合网| 欧美日韩精品中文字幕| 日韩特黄一级片| 亚洲美女视频一区| 欧美日韩人妻精品一区二区三区| 国产精品超碰97尤物18| 久久免费手机视频| 国产欧美精品区一区二区三区 | 理论片一区二区在线| 国产精品毛片va一区二区三区| 日本欧美韩国| 成人黄色av网站| 日韩黄色三级| 国产色综合天天综合网| 伦一区二区三区中文字幕v亚洲| 国产欧美精品久久久| 99欧美精品| 国产精品久久久久久超碰| 欧美一区国产| 国产精品九九九| 亚洲www啪成人一区二区| 国产精品久久久久久久久免费看 | 欧美在线网址| 国风产精品一区二区| 影音先锋亚洲一区| 丰满爆乳一区二区三区| 国产精品社区| 麻豆av免费在线| 狠狠v欧美v日韩v亚洲ⅴ| 国产三级精品三级在线| 国产精品一区二区久久不卡| 国产吃瓜黑料一区二区| 97精品超碰一区二区三区| 波多野结衣一本| 久久免费美女视频| 乱h高h女3p含苞待放| 亚洲自拍偷拍九九九| 日韩精品国产一区二区| 午夜日韩在线观看| 亚洲天堂国产精品| 日韩女优电影在线观看| 天天射天天色天天干| 中文字幕av一区二区| 波多野结衣在线播放| 国产精品草莓在线免费观看| 激情亚洲小说| 亚洲free性xxxx护士hd| 免费视频一区三区| 国产av不卡一区二区| 精品999网站| 亚洲 高清 成人 动漫| 国产在线不卡视频| 一区二区三区少妇| 国产精品福利一区| 国产一级做a爰片在线看免费| 午夜av一区二区三区| 中文字幕欧美人妻精品一区蜜臀| 日韩小视频在线观看专区| 污视频网站免费观看| 中文字幕精品国产| 91麻豆国产福利在线观看宅福利| 97激碰免费视频| 精品美女一区| 精品无人区一区二区三区| 自拍偷拍欧美| 亚洲成熟丰满熟妇高潮xxxxx| 激情五月婷婷综合网| 精品中文字幕在线播放| 一区二区三区在线视频观看58| 国产黄色免费观看| 日韩欧美在线不卡| 午夜18视频在线观看| 久久精品成人动漫| 日韩欧美另类一区二区| 高清国产在线一区| 久久电影院7| 黑鬼大战白妞高潮喷白浆| 成人免费视频视频在线观看免费| jizz日本在线播放| 欧美性猛交xxxx乱大交蜜桃 | 亚洲电影免费观看高清| 免费的黄网站在线观看| 青草成人免费视频| 国产图片一区| 精品成在人线av无码免费看| 韩国三级电影一区二区| 欧美丰满老妇熟乱xxxxyyy| 欧美午夜www高清视频| 成 人片 黄 色 大 片| 精品国产一区二区三区久久狼黑人 | 久久精品在线免费观看| 久久免费黄色网址| 精品免费国产二区三区| 国产成人无吗| 国产剧情日韩欧美| 久久免费大视频| 波多结衣在线观看| 国产性色一区二区| 一级黄色av片| 中文字幕亚洲综合| 日本h片久久| 亚洲国内在线| 国内久久婷婷综合| 特黄一区二区三区| 欧美日韩一区二区在线观看视频| 1024免费在线视频| 国产精品久久久久秋霞鲁丝| 欧美**字幕| 国产精品欧美激情在线观看| 国产亚洲欧美在线| 无码人妻aⅴ一区二区三区有奶水| 亚洲精品美女视频| 欧美三区四区| 四虎一区二区| 美女视频网站黄色亚洲| 长河落日免费高清观看| 欧美日韩国产成人在线免费| 成a人片在线观看www视频| 国产精品黄色av| 亚洲国产老妈| 亚洲精品无码一区二区| 亚洲成人www| 黄色片在线免费观看| 国产精品入口福利| 四虎国产精品免费观看| xxxx国产视频| 亚洲成人动漫av| 日本福利片高清在线观看| 欧美洲成人男女午夜视频| 精品国产一区二区三区四区| 我看黄色一级片| 综合久久久久久| 欧美一区二区三区黄片| 热草久综合在线| 日韩三级在线| 亚洲天堂av网站| 色婷婷久久一区二区三区麻豆| 在线免费看黄| 国产日韩欧美一区二区| 老鸭窝毛片一区二区三区| 在线观看免费小视频| 精品国产制服丝袜高跟| 成av人片在线观看www| 欧美一区少妇| 久久精品国产免费| 免费在线观看av网址| 亚洲深夜福利视频| 久久久久毛片免费观看| 大陆极品少妇内射aaaaa| 亚洲视频综合在线| 色综合久久网女同蕾丝边|