溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

[譯]變量在 PHP7 內部的實現(一)

發布時間:2020-08-16 22:02:52 來源:網絡 閱讀:408 作者:wz669 欄目:web開發

http://0x1.im/blog/php/Internal-value-representation-in-PHP-7-part-1.html

→ About → Links → Github → 公眾號[譯]變量在 PHP7 內部的實現(一)

Scholer's Blog

[譯]變量在 PHP7 內部的實現一

Dec 10, 2015

本文第一部分和第二均翻譯自Nikita Popov(nikicPHP 官方開發組成員柏林科技大學的學生) 的博客。為了更符合漢語的閱讀習慣文中并不會逐字逐句的翻譯。

要理解本文你應該對 PHP5 中變量的實現有了一些了解本文重點在于解釋 PHP7 中 zval 的變化。

由于大量的細節描述本文將會分成兩個部分第一部分主要描述 zval(zend value) 的實現在 PHP5 和 PHP7 中有何不同以及引用的實現。第二部分將會分析單獨類型strings、objects的細節。

PHP5 中的 zval

PHP5 中 zval 結構體定義如下

typedef struct _zval_struct {
    zvalue_value value;
    zend_uint refcount__gc;
    zend_uchar type;
    zend_uchar is_ref__gc;} zval;

如上zval 包含一個 value、一個 type 以及兩個 __gc 后綴的字段。value 是個聯合體用于存儲不同類型的值

typedef union _zvalue_value {
    long lval;                 // 用于 bool 類型、整型和資源類型    double dval;               // 用于浮點類型    struct {                   // 用于字符串        char *val;
        int len;
    } str;
    HashTable *ht;             // 用于數組    zend_object_value obj;     // 用于對象    zend_ast *ast;             // 用于常量表達式(PHP5.6 才有)} zvalue_value;

C 語言聯合體的特征是一次只有一個成員是有效的并且分配的內存與需要內存最多的成員匹配也要考慮內存對齊。所有成員都存儲在內存的同一個位置根據需要存儲不同的值。當你需要 lval 的時候它存儲的是有符號×××需要 dval 時會存儲雙精度浮點數。

需要指出的是是聯合體中當前存儲的數據類型會記錄到 type 字段用一個整型來標記

#define IS_NULL     0      /* Doesn't use value */#define IS_LONG     1      /* Uses lval */#define IS_DOUBLE   2      /* Uses dval */#define IS_BOOL     3      /* Uses lval with values 0 and 1 */#define IS_ARRAY    4      /* Uses ht */#define IS_OBJECT   5      /* Uses obj */#define IS_STRING   6      /* Uses str */#define IS_RESOURCE 7      /* Uses lval, which is the resource ID *//* Special types used for late-binding of constants */#define IS_CONSTANT 8
#define IS_CONSTANT_AST 9

PHP5 中的引用計數

在PHP5中zval 的內存是單獨從堆heap中分配的有少數例外情況PHP 需要知道哪些 zval 是正在使用的哪些是需要釋放的。所以這就需要用到引用計數zval 中 refcount__gc 的值用于保存 zval 本身被引用的次數比如 $a = $b = 42 語句中42 被兩個變量引用所以它的引用計數就是 2。如果引用計數變成 0就意味著這個變量已經沒有用了內存也就可以釋放了。

注意這里提及到的引用計數指的不是 PHP 代碼中的引用使用 &而是變量的使用次數。后面兩者需要同時出現時會使用『PHP 引用』和『引用』來區分兩個概念這里先忽略掉 PHP 的部分。

一個和引用計數緊密相關的概念是『寫時復制』對于多個引用來說zaval 只有在沒有變化的情況下才是共享的一旦其中一個引用改變 zval 的值就需要復制”separated”一份 zval然后修改復制后的 zval。

下面是一個關于『寫時復制』和 zval 的銷毀的例子

<?php$a = 42;   // $a         -> zval_1(type=IS_LONG, value=42, refcount=1)$b = $a;   // $a, $b     -> zval_1(type=IS_LONG, value=42, refcount=2)$c = $b;   // $a, $b, $c -> zval_1(type=IS_LONG, value=42, refcount=3)// 下面幾行是關于 zval 分離的$a += 1;   // $b, $c -> zval_1(type=IS_LONG, value=42, refcount=2)           // $a     -> zval_2(type=IS_LONG, value=43, refcount=1)unset($b); // $c -> zval_1(type=IS_LONG, value=42, refcount=1)           // $a -> zval_2(type=IS_LONG, value=43, refcount=1)unset($c); // zval_1 is destroyed, because refcount=0           // $a -> zval_2(type=IS_LONG, value=43, refcount=1)

引用計數有個致命的問題無法檢查并釋放循環引用使用的內存。為了解決這問題PHP 使用了循環回收的方法。當一個 zval 的計數減一時就有可能屬于循環的一部分這時將 zval 寫入到『根緩沖區』中。當緩沖區滿時潛在的循環會被打上標記并進行回收。

因為要支持循環回收實際使用的 zval 的結構實際上如下

typedef struct _zval_gc_info {
    zval z;
    union {
        gc_root_buffer       *buffered;
        struct _zval_gc_info *next;
    } u;} zval_gc_info;

zval_gc_info 結構體中嵌入了一個正常的 zval 結構同時也增加了兩個指針參數但是共屬于同一個聯合體u所以實際使用中只有一個指針是有用的。buffered 指針用于存儲 zval 在根緩沖區的引用地址所以如果在循環回收執行之前 zval 已經被銷毀了這個字段就可能被移除了。next 在回收銷毀值的時候使用這里不會深入。

修改動機

下面說說關于內存使用上的情況這里說的都是指在 64 位的系統上。首先由于 str 和 obj 占用的大小一樣 zvalue_value 這個聯合體占用 16 個字節bytes的內存。整個 zval 結構體占用的內存是 24 個字節考慮到內存對齊zval_gc_info 的大小是 32 個字節。綜上在堆相對于棧分配給 zval 的內存需要額外的 16 個字節所以每個 zval 在不同的地方一共需要用到 48 個字節要理解上面的計算方式需要注意每個指針在 64 位的系統上也需要占用 8 個字節。

在這點上不管從什么方面去考慮都可以認為 zval 的這種設計效率是很低的。比如 zval 在存儲整型的時候本身只需要 8 個字節即使考慮到需要存一些附加信息以及內存對齊額外 8 個字節應該也是足夠的。

在存儲整型時本來確實需要 16 個字節但是實際上還有 16 個字節用于引用計數、16 個字節用于循環回收。所以說 zval 的內存分配和釋放都是消耗很大的操作我們有必要對其進行優化。

從這個角度思考一個整型數據真的需要存儲引用計數、循環回收的信息并且單獨在堆上分配內存嗎答案是當然不這種處理方式一點都不好。

這里總結一下 PHP5 中 zval 實現方式存在的主要問題

  • zval 總是單獨從堆中分配內存

  • zval 總是存儲引用計數和循環回收的信息即使是整型這種可能并不需要此類信息的數據

  • 在使用對象或者資源時直接引用會導致兩次計數原因會在下一部分講

  • 某些間接訪問需要一個更好的處理方式。比如現在訪問存儲在變量中的對象間接使用了四個指針指針鏈的長度為四。這個問題也放到下一部分討論

  • 直接計數也就意味著數值只能在 zval 之間共享。如果想在 zval 和 hashtable key 之間共享一個字符串就不行除非 hashtable key 也是 zval。

PHP7 中的 zval

在 PHP7 中 zval 有了新的實現方式。最基礎的變化就是 zval 需要的內存不再是單獨從堆上分配不再自己存儲引用計數。復雜數據類型比如字符串、數組和對象的引用計數由其自身來存儲。這種實現方式有以下好處

  • 簡單數據類型不需要單獨分配內存也不需要計數

  • 不會再有兩次計數的情況。在對象中只有對象自身存儲的計數是有效的

  • 由于現在計數由數值自身存儲所以也就可以和非 zval 結構的數據共享比如 zval 和 hashtable key 之間

  • 間接訪問需要的指針數減少了。

我們看看現在 zval 結構體的定義現在在 zend_types.h 文件中

struct _zval_struct {
	zend_value        value;			/* value */
	union {
		struct {
			ZEND_ENDIAN_LOHI_4(
				zend_uchar    type,			/* active type */
				zend_uchar    type_flags,
				zend_uchar    const_flags,
				zend_uchar    reserved)	    /* call info for EX(This) */
		} v;
		uint32_t type_info;
	} u1;
	union {
		uint32_t     var_flags;
		uint32_t     next;                 /* hash collision chain */
		uint32_t     cache_slot;           /* literal cache slot */
		uint32_t     lineno;               /* line number (for ast nodes) */
		uint32_t     num_args;             /* arguments number for EX(This) */
		uint32_t     fe_pos;               /* foreach position */
		uint32_t     fe_iter_idx;          /* foreach iterator index */
	} u2;};

結構體的第一個元素沒太大變化仍然是一個 value 聯合體。第二個成員是由一個表示類型信息的整型和一個包含四個字符變量的結構體組成的聯合體可以忽略 ZEND_ENDIAN_LOHI_4 宏它只是用來解決跨平臺大小端問題的。這個子結構中比較重要的部分是 type和以前類似和 type_flags這個接下來會解釋。

上面這個地方也有一點小問題value 本來應該占 8 個字節但是由于內存對齊哪怕只增加一個字節實際上也是占用 16 個字節使用一個字節就意味著需要額外的 8 個字節。但是顯然我們并不需要 8 個字節來存儲一個 type 字段所以我們在 u1 的后面增加了了一個名為 u2 的聯合體。默認情況下是用不到的需要使用的時候可以用來存儲 4 個字節的數據。這個聯合體可以滿足不同場景下的需求。

PHP7 中 value 的結構定義如下

typedef union _zend_value {
	zend_long         lval;				/* long value */
	double            dval;				/* double value */
	zend_refcounted  *counted;
	zend_string      *str;
	zend_array       *arr;
	zend_object      *obj;
	zend_resource    *res;
	zend_reference   *ref;
	zend_ast_ref     *ast;
	zval             *zv;
	void             *ptr;
	zend_class_entry *ce;
	zend_function    *func;
	struct {
		uint32_t w1;
		uint32_t w2;
	} ww;} zend_value;

首先需要注意的是現在 value 聯合體需要的內存是 8 個字節而不是 16。它只會直接存儲整型lval或者浮點型dval數據其他情況下都是指針上面提到過指針占用 8 個字節最下面的結構體由兩個 4 字節的無符號整型組成。上面所有的指針類型除了特殊標記的都有一個同樣的頭zend_refcounted用來存儲引用計數

typedef struct _zend_refcounted_h {
	uint32_t         refcount;			/* reference counter 32-bit */
	union {
		struct {
			ZEND_ENDIAN_LOHI_3(
				zend_uchar    type,
				zend_uchar    flags,    /* used for strings & objects */
				uint16_t      gc_info)  /* keeps GC root number (or 0) and color */
		} v;
		uint32_t type_info;
	} u;} zend_refcounted_h;

現在這個結構體肯定會包含一個存儲引用計數的字段。除此之外還有 type、flags 和 gc_info。type 存儲的和 zval 中的 type 相同的內容這樣 GC 在不存儲 zval 的情況下單獨使用引用計數。flags 在不同的數據類型中有不同的用途這個放到下一部分講。

gc_info 和 PHP5 中的 buffered 作用相同不過不再是位于根緩沖區的指針而是一個索引數字。因為以前根緩沖區的大小是固定的10000 個元素所以使用一個 16 位2 字節的數字代替 64 位8 字節的指針足夠了。gc_info 中同樣包含一個『顏色』位用于回收時標記結點。

zval 內存管理

上文提到過 zval 需要的內存不再單獨從堆上分配。但是顯然總要有地方來存儲它所以會存在哪里呢實際上大多時候它還是位于堆中所以前文中提到的地方重點不是而是單獨分配只不過是嵌入到其他的數據結構中的比如 hashtable 和 bucket 現在就會直接有一個 zval 字段而不是指針。所以函數表編譯變量和對象屬性在存儲時會是一個 zval 數組并得到一整塊內存而不是散落在各處的 zval 指針。之前的 zval * 現在都變成了 zval。

之前當 zval 在一個新的地方使用時會復制一份 zval * 并增加一次引用計數?,F在就直接復制 zval 的值忽略 u2某些情況下可能會增加其結構指針指向的引用計數如果在進行計數。

那么 PHP 怎么知道 zval 是否正在計數呢不是所有的數據類型都能知道因為有些類型比如字符串或數組并不是總需要進行引用計數。所以 type_info 字段就是用來記錄 zval 是否在進行計數的這個字段的值有以下幾種情況

#define IS_TYPE_CONSTANT            (1<<0)   /* special */#define IS_TYPE_IMMUTABLE           (1<<1)   /* special */#define IS_TYPE_REFCOUNTED          (1<<2)
#define IS_TYPE_COLLECTABLE         (1<<3)
#define IS_TYPE_COPYABLE            (1<<4)
#define IS_TYPE_SYMBOLTABLE         (1<<5)   /* special */

注在 7.0.0 的正式版本中上面這一段宏定義的注釋這幾個宏是供 zval.u1.v.type_flags 使用的。這應該是注釋的錯誤因為這個上述字段是 zend_uchar 類型。

type_info 的三個主要的屬性就是『可計數』refcounted、『可回收』collectable和『可復制』copyable。計數的問題上面已經提過了?!嚎苫厥铡挥糜跇擞?zval 是否參與循環不如字符串通常是可計數的但是你卻沒辦法給字符串制造一個循環引用的情況。

是否可復制用于表示在復制時是否需要在復制時制造原文用的 “duplication” 來表述用中文表達出來可能不是很好理解一份一模一樣的實體?!眃uplication” 屬于深度復制比如在復制數組時不僅僅是簡單增加數組的引用計數而是制造一份全新值一樣的數組。但是某些類型比如對象和資源即使 “duplication” 也只能是增加引用計數這種就屬于不可復制的類型。這也和對象和資源現有的語義匹配現有PHP7 也是這樣不單是 PHP5。

下面的表格上標明了不同的類型會使用哪些標記x 標記的都是有的特性?!汉唵晤愋汀籹imple types指的是整型或布爾類型這些不使用指針指向一個結構體的類型。下表中也有『不可變』immutable的標記它用來標記不可變數組的這個在下一部分再詳述。

interned string保留字符在這之前沒有提過其實就是函數名、變量名等無需計數、不可重復的字符串。

                | refcounted | collectable | copyable | immutable
----------------+------------+-------------+----------+----------
simple types    |            |             |          |
string          |      x     |             |     x    |
interned string |            |             |          |
array           |      x     |      x      |     x    |
immutable array |            |             |          |     x
object          |      x     |      x      |          |
resource        |      x     |             |          |
reference       |      x     |             |          |

要理解這一點我們可以來看幾個例子這樣可以更好的認識 zval 內存管理是怎么工作的。

下面是整數行為模式在上文中 PHP5 的例子的基礎上進行了一些簡化

<?php$a = 42;   // $a = zval_1(type=IS_LONG, value=42)$b = $a;   // $a = zval_1(type=IS_LONG, value=42)           // $b = zval_2(type=IS_LONG, value=42)$a += 1;   // $a = zval_1(type=IS_LONG, value=43)           // $b = zval_2(type=IS_LONG, value=42)unset($a); // $a = zval_1(type=IS_UNDEF)           // $b = zval_2(type=IS_LONG, value=42)

這個過程其實挺簡單的?,F在整數不再是共享的變量直接就會分離成兩個單獨的 zval由于現在 zval 是內嵌的所以也不需要單獨分配內存所以這里的注釋中使用 = 來表示的而不是指針符號 ->unset 時變量會被標記為 IS_UNDEF。下面看一下更復雜的情況

<?php$a = [];   // $a = zval_1(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])$b = $a;   // $a = zval_1(type=IS_ARRAY) -> zend_array_1(refcount=2, value=[])           // $b = zval_2(type=IS_ARRAY) ---^// zval 分離在這里進行$a[] = 1   // $a = zval_1(type=IS_ARRAY) -> zend_array_2(refcount=1, value=[1])           // $b = zval_2(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])unset($a); // $a = zval_1(type=IS_UNDEF),   zend_array_2 被銷毀           // $b = zval_2(type=IS_ARRAY) -> zend_array_1(refcount=1, value=[])

這種情況下每個變量變量有一個單獨的 zval但是是指向同一個有引用計數 zend_array 的結構體。修改其中一個數組的值時才會進行復制。這點和 PHP5 的情況類似。

類型Types

我們大概看一下 PHP7 支持哪些類型zval 使用的類型標記

/* regular data types */#define IS_UNDEF					0
#define IS_NULL						1
#define IS_FALSE					2
#define IS_TRUE						3
#define IS_LONG						4
#define IS_DOUBLE					5
#define IS_STRING					6
#define IS_ARRAY					7
#define IS_OBJECT					8
#define IS_RESOURCE					9
#define IS_REFERENCE				10/* constant expressions */#define IS_CONSTANT					11
#define IS_CONSTANT_AST				12/* internal types */#define IS_INDIRECT					15
#define IS_PTR						17

這個列表和 PHP5 使用的類似不過增加了幾項

  • IS_UNDEF 用來標記之前為 NULL 的 zval 指針和 IS_NULL 并不沖突。比如在上面的例子中使用unset 注銷變量

  • IS_BOOL 現在分割成了 IS_FALSE 和 IS_TRUE 兩項?,F在布爾類型的標記是直接記錄到 type 中這么做可以優化類型檢查。不過這個變化對用戶是透明的還是只有一個『布爾』類型的數據PHP 腳本中。

  • PHP 引用不再使用 is_ref 來標記而是使用 IS_REFERENCE 類型。這個也要放到下一部分講

  • IS_INDIRECT  和  IS_PTR 是特殊的內部標記。

實際上上面的列表中應該還存在兩個 fake types這里忽略了。

IS_LONG 類型表示的是一個 zend_long 的值而不是原生的 C 語言的 long 類型。原因是 Windows 的 64 位系統LLP64上的 long 類型只有 32 位的位深度。所以 PHP5 在 Windows 上只能使用 32 位的數字。PHP7 允許你在 64 位的操作系統上使用 64 位的數字即使是在 Windows 上面也可以。

zend_refcounted 的內容會在下一部分講。下面看看 PHP 引用的實現。

引用

PHP7 使用了和 PHP5 中完全不同的方法來處理 PHP & 符號引用的問題這個改動也是 PHP7 開發過程中大量 bug 的根源。我們先從 PHP5 中 PHP 引用的實現方式說起。

通常情況下 寫時復制原則意味著當你修改一個 zval 之前需要對其進行分離來保證始終修改的只是某一個 PHP 變量的值。這就是傳值調用的含義。

但是使用 PHP 引用時這條規則就不適用了。如果一個 PHP 變量是 PHP 引用就意味著你想要在將多個 PHP 變量指向同一個值。PHP5 中的 is_ref 標記就是用來注明一個 PHP 變量是不是 PHP 引用在修改時需不需要進行分離的。比如

<?php$a = [];  // $a     -> zval_1(type=IS_ARRAY, refcount=1, is_ref=0) -> HashTable_1(value=[])$b =& $a; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_1(value=[])$b[] = 1; // $a = $b = zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_1(value=[1])          // 因為 is_ref 的值是 1, 所以 PHP 不會對 zval 進行分離

但是這個設計的一個很大的問題在于它無法在一個 PHP 引用變量和 PHP 非引用變量之間共享同一個值。比如下面這種情況

<?php$a = [];  // $a         -> zval_1(type=IS_ARRAY, refcount=1, is_ref=0) -> HashTable_1(value=[])$b = $a;  // $a, $b     -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[])$c = $b   // $a, $b, $c -> zval_1(type=IS_ARRAY, refcount=3, is_ref=0) -> HashTable_1(value=[])$d =& $c; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[])          // $c, $d -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_2(value=[])          // $d 是 $c 的引用, 但卻不是 $a 的 $b, 所以這里 zval 還是需要進行復制          // 這樣我們就有了兩個 zval, 一個 is_ref 的值是 0, 一個 is_ref 的值是 1.$d[] = 1; // $a, $b -> zval_1(type=IS_ARRAY, refcount=2, is_ref=0) -> HashTable_1(value=[])          // $c, $d -> zval_1(type=IS_ARRAY, refcount=2, is_ref=1) -> HashTable_2(value=[1])          // 因為有兩個分離了的 zval, $d[] = 1 的語句就不會修改 $a 和 $b 的值.

這種行為方式也導致在 PHP 中使用引用比普通的值要慢。比如下面這個例子

<?php$array = range(0, 1000000);$ref =& $array;var_dump(count($array)); // <-- 這里會進行分離

因為 count() 只接受傳值調用但是 $array 是一個 PHP 引用所以 count() 在執行之前實際上會有一個對數組進行完整的復制的過程。如果 $array 不是引用這種情況就不會發生了。

現在我們來看看 PHP7 中 PHP 引用的實現。因為 zval 不再單獨分配內存也就沒辦法再使用和 PHP5 中相同的實現了。所以增加了一個 IS_REFERENCE 類型并且專門使用 zend_reference 來存儲引用值

struct _zend_reference {
    zend_refcounted   gc;
    zval              val;};

本質上 zend_reference 只是增加了引用計數的 zval。所有引用變量都會存儲一個 zval 指針并且被標記為IS_REFERENCE。val 和其他的 zval 的行為一樣尤其是它也可以在共享其所存儲的復雜變量的指針比如數組可以在引用變量和值變量之間共享。

我們還是看例子這次是 PHP7 中的語義。為了簡潔明了這里不再單獨寫出 zval只展示它們指向的結構體

<?php$a = [];  // $a                                     -> zend_array_1(refcount=1, value=[])$b =& $a; // $a, $b -> zend_reference_1(refcount=2) -> zend_array_1(refcount=1, value=[])$b[] = 1; // $a, $b -> zend_reference_1(refcount=2) -> zend_array_1(refcount=1, value=[1])

上面的例子中進行引用傳遞時會創建一個 zend_reference注意它的引用計數是 2因為有兩個變量在使用這個 PHP 引用。但是值本身的引用計數是 1因為 zend_reference 只是有一個指針指向它。下面看看引用和非引用混合的情況

<?php$a = [];  // $a         -> zend_array_1(refcount=1, value=[])$b = $a;  // $a, $b,    -> zend_array_1(refcount=2, value=[])$c = $b   // $a, $b, $c -> zend_array_1(refcount=3, value=[])$d =& $c; // $a, $b                                 -> zend_array_1(refcount=3, value=[])          // $c, $d -> zend_reference_1(refcount=2) ---^          // 注意所有變量共享同一個 zend_array, 即使有的是 PHP 引用有的不是$d[] = 1; // $a, $b                                 -> zend_array_1(refcount=2, value=[])          // $c, $d -> zend_reference_1(refcount=2) -> zend_array_2(refcount=1, value=[1])          // 只有在這時進行賦值的時候才會對 zend_array 進行賦值

這里和 PHP5 最大的不同就是所有的變量都可以共享同一個數組即使有的是 PHP 引用有的不是。只有當其中某一部分被修改的時候才會對數組進行分離。這也意味著使用 count() 時即使給其傳遞一個很大的引用數組也是安全的不會再進行復制。不過引用仍然會比普通的數值慢因為存在需要為 zend_reference 結構體分配內存間接并且引擎本身處理這一塊兒也不快的的原因。

結語

總結一下 PHP7 中最重要的改變就是 zval 不再單獨從堆上分配內存并且不自己存儲引用計數。需要使用 zval 指針的復雜類型比如字符串、數組和對象會自己存儲引用計數。這樣就可以有更少的內存分配操作、更少的間接指針使用以及更少的內存分配。

文章的第二部分我們會討論復雜類型的問題。

More:

  • Jan 07,2017  再見2016我在騰訊這一年

  • Jan 02,2017  如何學習 PHP 源碼 - 從編譯開始

Website powered by Jekyll, hosted on Github and theme of marcgg 
All of the blog's articles are under Creative commons license unless stated otherwise. Everything else is .


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女