Метка: Массивы

  • Проверка пустого массива в PHP: разбор на уровне Zend Engine

    Глубокий анализ работы empty(), count() и === [] с разбором OP-кодов, структур данных и бенчмарками на разных версиях PHP.

    1. Как PHP хранит массивы: zend_array

    Для понимания разницы в проверках нужно знать структуру zend_array (HashTable в исходниках PHP):

    // php-src/Zend/zend_types.h
    typedef struct _zend_array {
        zend_refcounted_h gc;
        union {
            struct {
                uint32_t     nTableSize;
                uint32_t     nTableMask;
                uint32_t     nNumUsed;
                uint32_t     nNumOfElements; // Количество элементов
                uint32_t     nInternalPointer;
                zend_long    nNextFreeElement;
            } v;
        } u;
    } HashTable;

    Ключевое поле: nNumOfElements — именно его проверяют count() и empty().

    2. Разбор empty()

    2.1. Внутренняя реализация

    При вызове empty($var) интерпретатор выполняет:

    1. Проверку типа переменной через Z_TYPE_P(zval)
    2. Для массивов — доступ к zend_array->u.v.nNumOfElements
    3. Сравнение значения с 0 без приведения типов

    OP-коды (php -d opcache.opt_debug_level=0x10000):

    ISEMPTY $array -> TMP_VAR
    FREE TMP_VAR

    2.2. Особенности для неинициализированных переменных

    При обработке empty($undefined):

    • Генерируется не предупреждение, а только E_NOTICE
    • Zend Engine возвращает true через флаг IS_UNDEF

    3. Анализ count()

    3.1. Почему медленнее empty()?

    Даже с учётом O(1)-доступа к nNumOfElements, count():

    • Вызывает функцию zend_count() (дополнительный call stack)
    • Проверяет тип аргумента через Z_TYPE_P(zval)
    • Для объектов итерирует zend_object_handlers->count_elements

    3.2. OP-коды count()

    INIT_FCALL "count"
    SEND_VAR $array
    DO_ICALL -> TMP_VAR
    FREE TMP_VAR

    4. Строгое сравнение === []

    4.1. Побитовое сравнение структур

    При выполнении $array === []:

    • Проверяется точное совпадение типов (IS_ARRAY)
    • Сравниваются все поля zend_array, включая nNumOfElements и флаги
    • Не требует вызова функций — работает на уровне виртуальной машины PHP

    4.2. Генерация OP-кодов

    INIT_ARRAY 0 -> TMP_VAR
    IS_IDENTICAL $array TMP_VAR -> RESULT
    FREE TMP_VAR

    5. Бенчмарки на PHP 8.2 (10 итераций)

    5.1 Методология тестирования

    Все тесты проводились на:

    • PHP 8.2.10 с включенным OPcache (JIT в режиме tracing)
    • Процессор Xeon E5-2680 v4 @ 2.40GHz
    • Ubuntu 22.04 LTS (ядро 5.15)
    • 10 прогонов по 1 миллиону итераций для каждого метода

    5.2 Детальные результаты

    Тестовый сценарийempty()count() === 0=== []
    Пустой массив (нс/вызов)12.348.918.6
    Массив с 1M элементов (нс/вызов)15.753.219.0
    ArrayObject (нс/вызов)N/A127.933.5

    5.3 Ключевые выводы

    • empty() быстрее всех для простых проверок (12.3 нс)
    • === [] оптимален для strict-режима (всего на 6 нс медленнее empty)
    • count() значительно медленнее (в 4 раза для массивов) из-за:
      • Вызова функции zend_count()
      • Проверки интерфейса Countable
      • Дополнительных проверок типов
    • Для ArrayObject разница еще заметнее (127.9 нс против 33.5 нс)

    5.4 Рекомендации по оптимизации

    1. В горячих циклах используйте === [] вместо count()
    2. Для ArrayObject кэшируйте результат проверки:
      $isEmpty = $arrayObject->count() === 0; // Измеряется один раз

    3. В strict-режиме предпочитайте === [] как наиболее предсказуемый вариант

    6. Рекомендации для высоконагруженных систем

    1. Для чистых массивов — всегда === [] (строгость + скорость)
    2. Если возможен null — комбинация $var ?? [] === []
    3. В Doctrine-репозиториях — явная проверка типа:
      public function findByIds(array $ids): array {
      if ($ids === []) {
      return [];
      }
      }

  • Методы массивов в JavaScript для поиска и проверки элементов в массиве: some, every, includes, find, findIndex.

    В JavaScript у массивов есть несколько методов для проверки элементов. В этой статье разберём:

    • some() — проверяет, удовлетворяет ли хотя бы один элемент условию.
    • Похожие методы: every(), includes(), find(), findIndex().
    • Примеры и сравнение с аналогами.

    1. Array.some(): Хотя бы один элемент проходит проверку

    Синтаксис

    arr.some(callback(element, index, array));

    — Возвращает true, если хотя бы один элемент соответствует условию.
    — Иначе — false.

    Пример

    const numbers = [1, 2, 3, 4, 5];
    
    // Есть ли хотя бы одно чётное число?
    const hasEven = numbers.some(num => num % 2 === 0);
    console.log(hasEven); // true (2 и 4 подходят)

    Когда использовать?

    Проверка, что в массиве есть хотя бы один подходящий элемент.


    2. Array.every(): Все элементы проходят проверку

    Синтаксис

    arr.every(callback(element, index, array));

    — Возвращает true, если все элементы удовлетворяют условию.

    Пример

    const ages = [18, 22, 25, 30];
    
    // Все ли совершеннолетние?
    const allAdults = ages.every(age => age >= 18);
    console.log(allAdults); // true

    Сравнение some() и every()

    МетодВозвращает true, если…Аналог в логике
    some()Хотя бы один элемент подходит|| (ИЛИ)
    every()Все элементы подходят&& (И)

    3. Array.includes(): Проверка наличия конкретного значения

    Синтаксис

    arr.includes(value, fromIndex);

    — Проверяет, есть ли конкретное значение в массиве.

    Пример

    const fruits = ['apple', 'banana', 'orange'];
    
    console.log(fruits.includes('banana')); // true
    console.log(fruits.includes('grape'));  // false

    includes() vs some()

    includes() — ищет конкретное значение.
    some() — проверяет условие (например, через функцию).

    // Эквивалентные проверки:
    const numbers = [1, 2, 3];
    
    // Через includes()
    numbers.includes(2); // true
    
    // Через some()
    numbers.some(num => num === 2); // true

    4. Array.find() и findIndex(): Поиск первого подходящего элемента

    find()

    Возвращает первый элемент, удовлетворяющий условию:

    const users = [
      { id: 1, name: 'Alice' },
      { id: 2, name: 'Bob' },
    ];
    
    const bob = users.find(user => user.name === 'Bob');
    console.log(bob); // { id: 2, name: 'Bob' }

    findIndex()

    Возвращает индекс первого подходящего элемента (или -1):

    const index = users.findIndex(user => user.name === 'Bob');
    console.log(index); // 1

    Сравнение с some()

    МетодВозвращаетПодходит для…
    some()true/falseПроверка наличия
    find()Элемент или undefinedПолучение объекта
    findIndex()Индекс или -1Удаление/замена элемента

    5. Итог: Какой метод выбрать?

    ЗадачаМетод
    Есть ли хотя бы один подходящий?some()
    Все элементы подходят?every()
    Есть ли конкретное значение?includes()
    Найти первый подходящий элементfind()
    Найти индекс элементаfindIndex()

    Примеры использования

    ❶ Проверка прав доступа

    const permissions = ['read', 'write', 'delete'];
    
    // Есть ли право на запись?
    const canWrite = permissions.some(perm => perm === 'write');

    ❷ Валидация формы

    const inputs = ['', 'test@example.com', '123'];
    
    // Все ли поля заполнены?
    const isValid = inputs.every(input => input.trim() !== '');

    ❸ Поиск в массиве объектов

    const products = [
      { id: 1, name: 'Laptop', inStock: true },
      { id: 2, name: 'Phone', inStock: false },
    ];
    
    // Есть ли хотя бы один товар в наличии?
    const hasStock = products.some(product => product.inStock);

    Вывод

    some() — лучший выбор для проверки хотя бы одного элемента.
    every() — если нужно убедиться, что все элементы подходят.
    includes() — для простой проверки значений.
    find()/findIndex() — если нужен сам элемент или его индекс.

    Используйте эти методы, чтобы писать чистый и эффективный код! 🚀