zephir-lang / zephir

Zephir is a compiled high-level language aimed to ease the creation of C-extensions for PHP
https://zephir-lang.com
MIT License
3.29k stars 466 forks source link

Slow performance during create multidimension arrays #1884

Open omatrycy opened 5 years ago

omatrycy commented 5 years ago

Hello Code generated by zephir for setting multi-dimension array is very slow.

Example zep file:

namespace Utils;
class Example
{
    public function generate(int size) -> array
    {
        var output = [];
        var i = 0;
        var j = 0;

        for i in range (0, size) {
            let output[i] = [];

            for j  in range(0, size) {
                let output[i][j] = 1;
            }
        }

        return output;
    }
}

Example php code used this module:

<?php 

$a = new Utils\Example();
$res = $a->generate(1000);

Execution time: real 0m4,693s user 0m4,658s sys 0m0,020s

Generated c code for this function

PHP_METHOD(Utils_Example, generate) {

    zend_bool _0, _4$$3;
    zval *size_param = NULL, output, i, j, _3$$3, _7$$4;
    zend_long size, _1, _2, _5$$3, _6$$3;
    zval *this_ptr = getThis();

    ZVAL_UNDEF(&output);
    ZVAL_UNDEF(&i);
    ZVAL_UNDEF(&j);
    ZVAL_UNDEF(&_3$$3);
    ZVAL_UNDEF(&_7$$4);

    ZEPHIR_MM_GROW();
    zephir_fetch_params(1, 1, 0, &size_param);

    size = zephir_get_intval(size_param);

    ZEPHIR_INIT_VAR(&output);
    array_init(&output);
    ZEPHIR_INIT_VAR(&i);
    ZVAL_LONG(&i, 0);
    ZEPHIR_INIT_VAR(&j);
    ZVAL_LONG(&j, 0);
    _2 = size;
    _1 = 0;
    _0 = 0;
    if (_1 <= _2) {
        while (1) {
            if (_0) {
                _1++;
                if (!(_1 <= _2)) {
                    break;
                }
            } else {
                _0 = 1;
            }
            ZEPHIR_INIT_NVAR(&i);
            ZVAL_LONG(&i, _1);
            ZEPHIR_INIT_NVAR(&_3$$3);
            array_init(&_3$$3);
            zephir_array_update_zval(&output, &i, &_3$$3, PH_COPY | PH_SEPARATE);
            _6$$3 = size;
            _5$$3 = 0;
            _4$$3 = 0;
            if (_5$$3 <= _6$$3) {
                while (1) {
                    if (_4$$3) {
                        _5$$3++;
                        if (!(_5$$3 <= _6$$3)) {
                            break;
                        }
                    } else {
                        _4$$3 = 1;
                    }
                    ZEPHIR_INIT_NVAR(&j);
                    ZVAL_LONG(&j, _5$$3);
                    ZEPHIR_INIT_NVAR(&_7$$4);
                    ZVAL_LONG(&_7$$4, 1);
                    zephir_array_update_multi(&output, &_7$$4 TSRMLS_CC, SL("zz"), 2, &i, &j);
                }
            }
        }
    }
    RETURN_CCTOR(&output);

}

Similar code created in php

<?php

class Example {
    public function generate(int $size): array 
    {
        $output = [];
        for ($i = 0; $i <= $size; $i++) {
             $output[$i] = [];
            for ($j = 0; $j <= $size; $j++) {
                $output[$i][$j] = 1;
            }
        }
        return $output;
    }
}

$a = new Example();
$res = $a->generate(1000);

Execution time: real 0m0,178s user 0m0,156s sys 0m0,021s

For calculating time I used linux time command. PHP version : PHP 7.2.19-0ubuntu0.19.04.1 (cli) (built: Jun 4 2019 14:44:42) ( NTS ) Zephir version : 0.12.0 (with default configuration for new projects)

dreamsxin commented 5 years ago

I can try to use hash directly. Second, zephir_array_update_multi is retrieved only once according to the context, not every update.

dreamsxin commented 5 years ago

My test result: PHP

$ time php ../../test2.php 

real    0m0.115s
user    0m0.095s
sys 0m0.020s

zephir

$ time php ../../test.php 

real    0m0.164s
user    0m0.140s
sys 0m0.024s

After optimization

time php ../../test.php 

real    0m0.089s
user    0m0.049s
sys 0m0.040s

@sergeyklay Please take look if you can add reference type, and convert as follows:

    public function generate(int size) -> array
    {
        var output = [];
        var i = 0;
        var j = 0;

        for i in range (0, size) {
            var arr;
            let output[i] = [];
            let arr = &output[i]; // <-- reference

            for j  in range(0, size) {
                let arr[j] = 1;
            }
        }

        return output;
    }

to c code like this

...
zephir_array_update_zval(&output, &i, &empty_arr, PH_COPY | PH_SEPARATE);
zephir_array_fetch(&empty_arr, &output, &i, PH_NOISY|PH_READONLY, ...); // key point, use PH_READONLY, if reference type
...
zephir_array_update_zval(&empty_arr, &j, &value, PH_COPY); // key point, don't use PH_SEPARATE, if reference type
...
ruudboon commented 5 years ago

Great improvement!