Какой подход к проверке типов в PHP более быстрый? gettype() или несколько is_*()

В PHP, который имеет динамическую типизацию, мы можем создавать функции, которые могут принимать несколько типов данных в качестве параметров. Затем мы можем работать с данными в зависимости от типа переменной. Есть два способа сделать это:

Первый подход:

function doSomething1($param) {
    $type = gettype($param);
    if ($type === 'string') {
        // do something
    }
    else if ($type === 'integer') {
        // do something
    }
    else if ($type === 'array') {
        // do something
    }
}

Подход второй:

function doSomething2($param) {
    if (is_string($param)) {
        // do something
    }
    else if (is_int($param)) {
        // do something
    }
    else if (is_array($param)) {
        // do something
    }
}
  1. Насколько я знаю, эти два подхода функционально эквивалентны с точки зрения тестирования, но поскольку в PHP так много подводных камней, я должен спросить, есть ли что-то, что я мог бы упустить, если бы предпочел один подход другому?

  2. С точки зрения производительности, правильно ли говорить, что один подход быстрее, чем два, потому что PHP вызовы функций стоят дорого? Или gettype() гораздо более дорогая операция, чем отдельные is_*() функции?

  3. Есть ли какие-либо идиомы/руководства по стилю кодирования по этому поводу?

Обновление Из моего теста с использованием PHP 7.0.4 миллион итераций doSomething2() занял 159 мс, что чуть меньше половины времени doSomething1() (315 мс). Это было независимо от того, была ли передана строка (первая проверка) или массив (последняя проверка). Это, кажется, предполагает, что gettype() действительно является дорогостоящей операцией и более дорогой, чем вызовы нескольких функций с использованием is_*().

Любой, кто лучше понимает, почему это может быть, ваша помощь приветствуется.


person light    schedule 08.04.2016    source источник
comment
Как и в случае с любым вопросом сравнительного анализа: протестируйте его. Скорее всего, вы обнаружите, что разница настолько минимальна, что ею можно пренебречь.   -  person deceze♦    schedule 08.04.2016
comment
Я сделал. Не знаю, считается ли разница минимальной, но результаты меня немного удивляют. Я думаю, что было бы хорошо быть в курсе, почему. Я знаю, что некоторые могут сказать, что это преждевременная оптимизация, но на самом деле это не так. Речь идет о лучшем понимании языка — оценке плюсов и минусов двух функционально эквивалентных подходов.   -  person light    schedule 08.04.2016
comment
Эээ... ваши результаты прямо противоположны вашему выводу, или вы что-то опечатались. 315 мс для is_* ожидаемо медленнее, чем 159 мс для gettype.   -  person deceze♦    schedule 08.04.2016
comment
В любом случае, даже более медленная версия занимает всего доли секунды для миллиона вызовов; на практике это не имеет абсолютно никакого значения, и вы должны основывать свое решение на удобочитаемости и ясности кода, а не на производительности. – Я соглашусь с тем, что хотеть узнать язык – это нормально; но тогда вам, вероятно, следует покопаться в реализации этих функций на языке C...   -  person deceze♦    schedule 08.04.2016
comment
Откорректируйте этот абзац еще раз, все еще не имея смысла. :П   -  person deceze♦    schedule 08.04.2016
comment
Я согласен, что возиться с этой разницей в производительности, вероятно, не самый эффективный способ настройки производительности, но на самом деле это просто попытка лучше понять язык. PHP, кажется, имеет много способов сделать то же самое. На самом деле я пытаюсь выяснить, есть ли в этом языке какие-либо идиомы, касающиеся такого рода вещей, и, надеюсь, обоснование предпочтения одних функций/подходов другим.   -  person light    schedule 08.04.2016


Ответы (1)


Сравним C-код функций gettype и is_string.

gettype:

PHP_FUNCTION(gettype)
{
    zval *arg;
    zend_string *type;

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_ZVAL(arg)
    ZEND_PARSE_PARAMETERS_END();

    type = zend_zval_get_type(arg);
    if (EXPECTED(type)) {
        RETURN_INTERNED_STR(type);
    } else {
        RETURN_STRING("unknown type");
    }
}

Итак, он создает строку type и заполняет ее результатом вызова функции zend_zval_get_type, а именно:

ZEND_API zend_string *zend_zval_get_type(const zval *arg) /* {{{ */
{
    switch (Z_TYPE_P(arg)) {
        case IS_NULL:
            return ZSTR_KNOWN(ZEND_STR_NULL);
        case IS_FALSE:
        case IS_TRUE:
            return ZSTR_KNOWN(ZEND_STR_BOOLEAN);
        case IS_LONG:
            return ZSTR_KNOWN(ZEND_STR_INTEGER);
        case IS_DOUBLE:
            return ZSTR_KNOWN(ZEND_STR_DOUBLE);
        case IS_STRING:
            return ZSTR_KNOWN(ZEND_STR_STRING);
        case IS_ARRAY:
            return ZSTR_KNOWN(ZEND_STR_ARRAY);
        case IS_OBJECT:
            return ZSTR_KNOWN(ZEND_STR_OBJECT);
        case IS_RESOURCE:
            if (zend_rsrc_list_get_rsrc_type(Z_RES_P(arg))) {
                return ZSTR_KNOWN(ZEND_STR_RESOURCE);
            } else {
                return ZSTR_KNOWN(ZEND_STR_CLOSED_RESOURCE);
            }
        default:
            return NULL;
    }
}

Сравним с is_string, например:

PHP_FUNCTION(is_string)
{
    php_is_type(INTERNAL_FUNCTION_PARAM_PASSTHRU, IS_STRING);
}

Go to php_is_type:

static inline void php_is_type(INTERNAL_FUNCTION_PARAMETERS, int type)
{
    zval *arg;

    ZEND_PARSE_PARAMETERS_START(1, 1)
        Z_PARAM_ZVAL(arg)
    ZEND_PARSE_PARAMETERS_END();

    if (Z_TYPE_P(arg) == type) {
        if (type == IS_RESOURCE) {
            const char *type_name = zend_rsrc_list_get_rsrc_type(Z_RES_P(arg));
            if (!type_name) {
                RETURN_FALSE;
            }
        }
        RETURN_TRUE;
    } else {
        RETURN_FALSE;
    }
}

Итак, основная логика этих методов абсолютно одинакова — PHP использует Z_TYPE_P для определения типа переменной.

Но в случае gettype он также создает дополнительную строку для результата и заполняет ее постоянной строкой вместо того, чтобы просто возвращать логическое значение ИСТИНА или ЛОЖЬ в случае функций is_*. Так что определенно is_* функций быстрее :)

person Ivan Pomortsev    schedule 06.04.2020