[源碼分析系列] 不要在循環體中使用 array_push()

原文連接

標題是不要在循環體中使用 array_push(),其實這只是本篇文章的結論之一 下面咱們一塊兒研究一下 php 語言中數組的追加元素php

向數組追加元素

咱們知道 php 在數組棧尾追加元素的方式有兩種git

  • $a = []; array_push($a,'test');
  • $a[] = 'test';

那麼這兩種方式有什麼區別呢?github

咱們先來比較一下性能數組

ArrayPush

一個 ArrayPushbash

  • pushEachOne() 循環體中使用 array_push() 來爲 $a 追加元素
  • pushEachTwo() 循環體中使用 $a[] = $var 來爲 $a 追加元素
/** * Class ArrayPush */
class ArrayPush {

    /** * @param int $times * @return array */
    public static function pushEachOne(int $times): array {
        $a = [];
        $b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        for ($i = 0; $i < $times; $i++) {
            array_push($a, $b[$i % 10]);
        }
        return $a;
    }

    /** * @param int $times * @return array */
    public static function pushEachTwo(int $times): array {
        $a = [];
        $b = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
        for ($i = 0; $i < $times; $i++) {
            $a[] = $b[$i % 10];
        }
        return $a;
    }

}
複製代碼

編寫代碼測試

循環追加 100 萬個元素php7

ini_set('memory_limit', '4000M');
$timeOne = microtime(true);
$a       = ArrayPush::pushEachOne(1000000);
echo 'count pushEachOne result | ' . count($a) . PHP_EOL;
$timeTwo = microtime(true);
$b       = ArrayPush::pushEachTwo(1000000);
echo 'count pushEachTwo result | ' . count($b) . PHP_EOL;
$timeThree = microtime(true);
echo PHP_EOL;
echo 'pushEachOne | ' . ($timeTwo - $timeOne) . PHP_EOL;
echo 'pushEachTwo | ' . ($timeThree - $timeTwo) . PHP_EOL;
echo PHP_EOL;
複製代碼

結果

結果不言而喻,$a[] = 比使用 array_push() 快了接近三倍函數

count pushEachOne result | 1000000
count pushEachTwo result | 1000000

pushEachOne | 1.757071018219
pushEachTwo | 0.67165303230286
複製代碼

分析

array_push()爲何慢?這麼慢,咱們還有使用它的場景嗎?oop

官方手冊

array_push — 將一個或多個單元壓入數組的末尾(入棧)源碼分析

array_push ( array &$array , mixed $value1 [, mixed $... ] ) : intpost

array_push()array 當成一個棧,並將傳入的變量壓入 array 的末尾。array 的長度將根據入棧變量的數目增長。和以下效果相同:

<?php$array[] = $var;?>
複製代碼

並對每一個傳入的值重複以上動做。

Note: 若是用 array_push() 來給數組增長一個單元,還不如用 $array[] = ,由於這樣沒有調用函數的額外負擔。

Note: 若是第一個參數不是數組,array_push() 將發出一條警告。這和 $var[] 的行爲不一樣,後者會新建一個數組。

官方源碼

看一下源碼中的 array_push()

/* {{{ proto int array_push(array stack, mixed var [, mixed ...]) Pushes elements onto the end of the array */
PHP_FUNCTION(array_push)
{
	zval   *args,		/* Function arguments array */
		   *stack,		/* Input array */
		    new_var;	/* Variable to be pushed */
	int i,				/* Loop counter */
		argc;			/* Number of function arguments */


    //這一段是函數的參數解析
	ZEND_PARSE_PARAMETERS_START(2, -1)
		Z_PARAM_ARRAY_EX(stack, 0, 1)
		Z_PARAM_VARIADIC('+', args, argc)
	ZEND_PARSE_PARAMETERS_END();

	/* For each subsequent argument, make it a reference, increase refcount, and add it to the end of the array */
	for (i = 0; i < argc; i++) {
        //拷貝一個
		ZVAL_COPY(&new_var, &args[i]);

        //插入新數值,自動
		if (zend_hash_next_index_insert(Z_ARRVAL_P(stack), &new_var) == NULL) {
			if (Z_REFCOUNTED(new_var)) Z_DELREF(new_var);
			php_error_docref(NULL, E_WARNING, "Cannot add element to the array as the next element is already occupied");
			RETURN_FALSE;
		}
	}

	/* Clean up and return the number of values in the stack */
	RETVAL_LONG(zend_hash_num_elements(Z_ARRVAL_P(stack)));
}
/* }}} */
複製代碼

$a[] = 的實現是根據賦值的變量類型調用了一系列 Zend_API 函數 add_next_index_* ,它們在設置一個對應類型的 zval 值之後直接調用了 zend_hash_next_index_insert

ZEND_API int add_next_index_long(zval *arg, zend_long n) /* {{{ */ {
	zval tmp;

	ZVAL_LONG(&tmp, n);
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
}
/* }}} */

ZEND_API int add_next_index_null(zval *arg) /* {{{ */ {
	zval tmp;

	ZVAL_NULL(&tmp);
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
}
/* }}} */

ZEND_API int add_next_index_bool(zval *arg, int b) /* {{{ */ {
	zval tmp;

	ZVAL_BOOL(&tmp, b);
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
}
/* }}} */

ZEND_API int add_next_index_resource(zval *arg, zend_resource *r) /* {{{ */ {
	zval tmp;

	ZVAL_RES(&tmp, r);
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
}
/* }}} */

ZEND_API int add_next_index_double(zval *arg, double d) /* {{{ */ {
	zval tmp;

	ZVAL_DOUBLE(&tmp, d);
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
}
/* }}} */

ZEND_API int add_next_index_str(zval *arg, zend_string *str) /* {{{ */ {
	zval tmp;

	ZVAL_STR(&tmp, str);
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
}
/* }}} */

ZEND_API int add_next_index_string(zval *arg, const char *str) /* {{{ */ {
	zval tmp;

	ZVAL_STRING(&tmp, str);
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
}
/* }}} */

ZEND_API int add_next_index_stringl(zval *arg, const char *str, size_t length) /* {{{ */ {
	zval tmp;

	ZVAL_STRINGL(&tmp, str, length);
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), &tmp) ? SUCCESS : FAILURE;
}
/* }}} */

ZEND_API int add_next_index_zval(zval *arg, zval *value) /* {{{ */ {
	return zend_hash_next_index_insert(Z_ARRVAL_P(arg), value) ? SUCCESS : FAILURE;
}
/* }}} */
複製代碼

總結

通過上面的分析,彷彿 array_push() 沒有任何存在的意義,真的是這樣嗎?

  • 通常狀況下,array_push() 性能太差,因此咱們應當使用 $array[] = 來替換掉它
  • 若是一次追加多個單元,使用 array_push()

參考

相關文章
相關標籤/搜索