精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

久久老女人爱爱| 女人色偷偷aa久久天堂| 欧美午夜在线观看| 中文字幕中文字幕一区三区| www.黄色国产| 国产亚洲亚洲| 按摩亚洲人久久| 成年人视频网站免费观看| 99免在线观看免费视频高清| 模特精品在线| 亚洲第一区在线| 九九九在线观看视频| 日本孕妇大胆孕交无码| 久久人人97超碰com| 91久久精品美女| wwwwww国产| 91高清一区| 亚洲人成电影在线观看天堂色| 毛片在线视频播放| 男人和女人做事情在线视频网站免费观看| 裸体素人女欧美日韩| 久久天天躁狠狠躁老女人| 99久久免费看精品国产一区| 亚洲国产91视频| 黑人巨大精品欧美一区二区三区| 成人一区二区在线| 中文字幕乱码一区二区| 99精品热视频只有精品10| 久久久精品2019中文字幕神马| 视频区 图片区 小说区| 日韩天堂在线| 福利微拍一区二区| 日韩亚洲一区在线播放| 日本xxxxwww| 精品一区二区日韩| 欧美国产精品va在线观看| 亚洲精品国产一区黑色丝袜| 欧美大奶一区二区| 欧美成人一区二区三区 | 国产精品x8x8一区二区| 欧美日韩电影在线播放| 爆乳熟妇一区二区三区霸乳| 九七久久人人| 成人av网站大全| 亚洲一区二区三区视频播放| 精品成人久久久| 午夜久久一区| 蜜臀久久99精品久久久无需会员 | 中文字幕日韩一区二区三区不卡| va婷婷在线免费观看| 精品一区二区三区视频在线观看 | 亚洲一区二区精品在线| www.五月激情| 国产高清不卡一区二区| 91九色国产社区在线观看| 91精品视频免费在线观看| 亚洲国产精品一区| 久久久久久久香蕉网| 欧美人妻一区二区| 国产精品啊啊啊| 久久久久五月天| 五月天综合在线| 日韩理论电影| 日韩中文字幕av| 超碰人人人人人人人| 日本一区二区在线看| 色噜噜国产精品视频一区二区| 国产毛片毛片毛片毛片毛片毛片| 欧美91在线|欧美| 欧美另类高清zo欧美| www国产精品内射老熟女| 免费在线视频欧美| 亚洲免费观看高清完整版在线观看熊| 明星裸体视频一区二区| 韩国三级av在线免费观看| 国产清纯白嫩初高生在线观看91| 国产超碰91| 国产精品伊人久久| 国产成人在线网站| 久久精品美女| 少妇又色又爽又黄的视频| 国内精品伊人久久久久av影院| 黄网站色欧美视频| 福利视频一区二区三区四区| 理论不卡电影大全神| 一区二区三区四区蜜桃| 亚洲欧洲在线一区| 91麻豆免费在线视频| 精品国产老师黑色丝袜高跟鞋| 日韩a级黄色片| 麻豆电影在线播放| 亚洲精品v日韩精品| www.玖玖玖| 日日夜夜一区| 日韩av网址在线观看| 欧美福利第一页| 国色天香一区二区| 国产精品久久久久久一区二区 | 国产成人精品久久二区二区91| 亚洲天堂日韩av| 蜜臀久久99精品久久久久宅男| 国产精品678| 午夜精品久久久久久久96蜜桃| 国产精品系列在线播放| 欧美日韩综合另类| 黄视频网站在线| 一本大道久久精品懂色aⅴ| 免费看的黄色大片| 91丨精品丨国产| 亚洲欧美另类自拍| 黄色国产在线观看| 一道在线中文一区二区三区| 久久激情视频久久| 无码人妻精品一区二区三区不卡| 日韩av一二三| 国产在线一区二区三区四区| 拍真实国产伦偷精品| 欧美日韩亚洲视频一区| 国产人妖在线观看| 欧美1区二区| 九九久久精品一区| 一区二区视频免费观看| 久久久综合精品| 97碰在线视频| 国内不卡的一区二区三区中文字幕| 日韩一区二区三区高清免费看看| 人妻互换一二三区激情视频| 欧美高清在线| 国产精品美女免费视频| 日韩欧美在线观看一区二区| 亚洲国产日日夜夜| 免费人成视频在线播放| 色婷婷精品视频| 欧美极品美女视频网站在线观看免费| 影音先锋亚洲天堂| 日本成人中文字幕| 欧美少妇一区| 大片免费在线观看| 欧美日韩亚洲综合| 91社区视频在线观看| 欧美在线亚洲| 亚洲free性xxxx护士白浆| 99精品老司机免费视频| 欧美日韩综合在线免费观看| 亚洲一级片在线播放| 久久在线精品| 51国偷自产一区二区三区的来源| 亚洲人视频在线观看| 亚洲成人第一页| 国产午夜在线一区二区三区| 国内久久视频| 精品综合在线| 色老太综合网| 中文字幕欧美视频在线| wwwxxx亚洲| 91麻豆福利精品推荐| 午夜啪啪免费视频| 9999精品| 欧美日韩福利电影| 又色又爽又黄无遮挡的免费视频| 成人99免费视频| 亚洲一区二区三区乱码| 日本不良网站在线观看| 精品香蕉在线观看视频一| 久久青青草原亚洲av无码麻豆 | 精人妻一区二区三区| 欧美二区不卡| 精品国产免费人成电影在线观...| 五月天婷婷在线视频| 精品国产成人av| 老鸭窝一区二区| 欧美黑人做爰爽爽爽| 久久久伊人欧美| 亚洲av毛片成人精品| 亚洲青青青在线视频| 亚洲精品中文字幕无码蜜桃| 成人亚洲一区二区| 91在线免费看片| 毛片电影在线| 在线看国产精品| 99久久久无码国产精品免费| 国产精品丝袜一区| 色一情一乱一伦一区二区三区日本| 中文字幕区一区二区三| 奇米成人av国产一区二区三区| 亚洲精品福利网站| 欧美体内谢she精2性欧美| 国产性猛交xx乱| 丁香啪啪综合成人亚洲小说| 欧美aⅴ在线观看| 国产精品99在线观看| 国产精品揄拍一区二区| 欧美日韩色网| 一区二区三区无码高清视频| 国产成人精品一区二三区四区五区| 中文字幕亚洲视频| 欧洲熟妇的性久久久久久| 玖玖视频精品| 精品丰满人妻无套内射| 欧美一区二区三区高清视频| 国产精品一二区| 成人在线播放视频| 精品久久国产老人久久综合| 国产男人搡女人免费视频| 国产三级精品视频| 欧美婷婷精品激情| 亚洲国产黄色| 四虎精品欧美一区二区免费| 最新亚洲精品| 国产三级精品在线不卡| 不卡的国产精品| 国产精品第七影院| 国产精品xx| 亚洲欧美一区二区三区情侣bbw| 国产午夜精品久久久久| 亚洲午夜精品在线| 国产aaaaaaaaa| 久久久久9999亚洲精品| av在线播放网址| 国产精品一区二区三区四区| 久久午夜夜伦鲁鲁一区二区| 国产精品88久久久久久| 日韩影视精品| 制服丝袜日韩| 狠狠色综合色区| 99这里只有精品视频| 亚洲free性xxxx护士白浆| 欧美成人黄色| 国产精品极品美女在线观看免费| 国产日产一区二区| 中文字幕精品网| 高清毛片在线看| 亚洲欧洲成视频免费观看| 午夜福利一区二区三区| 精品国产区一区| 亚洲av综合色区无码一二三区 | 涩涩视频在线观看| 欧美日韩亚洲精品内裤| 日本在线播放视频| 国产精品美女久久久久高潮| 国精品无码人妻一区二区三区| 精品在线你懂的| 中文字幕亚洲乱码| 美女视频第一区二区三区免费观看网站| 999久久欧美人妻一区二区| 美女一区二区在线观看| 国产免费亚洲高清| 韩国精品一区| 97人洗澡人人免费公开视频碰碰碰| 亚洲搞黄视频| 神马久久桃色视频| 欧美激情二区| 亚洲欧美日韩高清| 第九色区av在线| 日韩在线中文字幕| dy888亚洲精品一区二区三区| 亚洲视频电影图片偷拍一区| 男人天堂综合| 最近中文字幕2019免费| 久操视频在线观看| 久久99视频免费| 九九精品调教| 97超级碰碰人国产在线观看| 精品国产第一福利网站| 国产精品看片资源| 国产精品一级在线观看| 国产精品扒开腿做爽爽爽的视频| 久久电影网站| 欧美伊久线香蕉线新在线| 91福利精品在线观看| 97激碰免费视频| 欧美人体一区二区三区| 国产免费一区二区三区在线观看| 美女福利一区二区三区| 国产精品亚洲аv天堂网| 精品三级久久久| 精品欧美日韩在线| 日韩欧美视频在线播放| 国产女人18毛片| 亚洲精品2区| 日本手机在线视频| 日韩av在线播放中文字幕| 免费观看成人在线视频| 精品一区二区三区av| 亚洲成人av免费在线观看| 欧美国产乱子伦| 久久免费在线观看视频| 日本丰满少妇一区二区三区| 国产精品国产一区二区三区四区 | 男人插女人视频在线观看| 欧美亚洲三区| www.亚洲自拍| 99精品视频一区二区| 国产精品一区二区亚洲| 亚洲h精品动漫在线观看| 黄色一区二区视频| 精品少妇一区二区三区视频免付费 | 91精品国产综合久久香蕉最新版| 全球最大av网站久久| av一本久道久久波多野结衣| 亚洲深夜福利在线观看| 免费观看国产视频在线| 久久av一区| 色婷婷狠狠18禁久久| 成人av动漫在线| 在线观看日韩精品视频| 成人欧美一区二区三区| 久久国产黄色片| 欧美一卡在线观看| 成人h小游戏| 78m国产成人精品视频| 国产欧美视频在线| 日韩精品久久一区二区三区| 欧美高清视频手机在在线| 国产精品视频一区二区三区四区五区| 亚洲在线播放| 丰满少妇一区二区三区专区| 中文字幕免费不卡| 亚洲自拍一区在线观看| 精品88久久久久88久久久| 成人福利网站| 国产精品视频导航| 精品一区免费| 久久久一本二本三本| 成人h动漫精品一区二| 日韩激情综合网| 欧美色倩网站大全免费| www.好吊色| 久久影院在线观看| 日韩专区视频网站| 亚洲欧美成人一区| 日韩和的一区二区| 伊人网在线视频观看| 狠狠躁18三区二区一区| 一本大道伊人av久久综合| 亚洲视频777| 欧美大片免费| 欧美日韩国产精品一区二区| 亚洲永久字幕| 久操视频免费看| 欧美性生活大片视频| 黄色网址在线播放| 国产福利视频一区| 欧美色就是色| 奇米影音第四色| 国产精品久久国产精麻豆99网站| 久久久久久久久久久久久久久久久 | 国产一区二区精品久久91| 91香蕉视频污在线观看| 欧美裸体bbwbbwbbw| a视频在线观看| 97超碰人人看人人| 成人久久电影| 色多多视频在线播放| 国产精品久久久久一区二区三区| 日本网站免费观看| 亚洲激情成人网| 成人一区福利| 天堂va久久久噜噜噜久久va| 免费观看30秒视频久久| 成年人网站在线观看视频| 3d动漫精品啪啪一区二区竹菊 | 久久亚洲精华国产精华液| 成年人一级黄色片| 精品欧美乱码久久久久久1区2区| 老司机精品影院| 国产激情久久久久| 日韩精品首页| 69久久精品无码一区二区| 国产精品久久久久久久久免费相片 | 日韩私人影院| 国产精品视频一| 综合久久婷婷| 爱爱的免费视频| 欧美日韩视频在线一区二区 | 久久精品99久久久久久久久| 欧洲大片精品免费永久看nba| 亚洲自拍的二区三区| 国产精品99久久久久| 国产乡下妇女做爰视频| 国产婷婷97碰碰久久人人蜜臀 | 日韩理论电影大全| 深夜福利网站在线观看| 欧美视频免费在线观看| 麻豆视频在线免费观看| 国内一区在线| 久久国产精品99精品国产 | 美日韩精品免费视频| 欧美大奶一区二区| 日韩av一卡二卡三卡| 国产精品久久久久aaaa樱花| 超碰人人人人人人| 国产精品久久久久av免费| 欧美日韩视频| 免费观看污网站| 欧美三级视频在线| 精品极品在线| 米仓穗香在线观看| 欧美国产丝袜视频| 欧美77777|