php array isset array key exists
Why is array_key_exists 1000x slower than isset on referenced arrays?
I have found that array_key_exists is over 1000x slower than isset at check if a key is set in an array reference. Does anyone that has an understanding of how PHP is implemented explain why this is true?
EDIT: I’ve added another case that seems to point to it being overhead required in calling functions with a reference.
Benchmark Example
Output
3 Answers 3
At work I’ve got a VM instance of PHP that includes a PECL extension called VLD. This lets you execute PHP code from the commandline and rather than execute it, it returns the generated opcode instead.
It’s brilliant at answering questions like this.
Just in case you go this route (and if you’re generally curious about how PHP works internally, i think you should) you should definitely install it on a virtual machine (that is, i wouldn’t install it on a machine i’m trying to develop on or deploy to). And this is the command you’ll use to make it sing:
Looking at the opcodes will tell you a more complete story, however, I have a guess. Most of PHP’s built-ins make a copy of an Array/Object and act upon that copy (and not a copy-on-write either, an immediate copy). The most widely known example of this is foreach(). When you pass an array into foreach(), PHP is actually making a copy of that array and iterating on the copy. Whis is why you’ll see a significant performance benefit by passing an array as a reference into foreach like this:
Ok, back to what I was getting at..
Most the built-ins take a copy of an array and act upon that copy. I am going to venture a completely unqualified guess that isset() is highly optimized and that one of those optimizations is perhaps to not do an immediate copy of an Array when its passed-in.
I’ll try to answer any other questions you may have but you could probably read a lot of you google for «zval_struct» (which is the data structure in the PHP internals which stores each variable. It’s a C struct (think.. an associative array) that has keys like «value», «type», «refcount».
Использование функций isset() и array_key_exists()
Навеяно проблемой, с которой я столкнулся из-за неправильного использования функции isset() в одном китайском движке интернет магазина. Там isset() применялась для определения существования элемента массива с некоторым ключом. Самое неприятное в данной ситуации то, что такую ошибку допускают многие. Это не первый случай, когда isset() используется именно в таком контексте, хотя это в корне неверно. Но в этот раз неграмотность разработчиков стоила мне потерянного времени, что и сподвигло меня на эту заметку.
Собственно, об уровне квалификации PHP программистов сказано немало и добавить к этому нечего.
Инициализация и удаление переменной в PHP
В PHP переменная инициализируется в момент присваивания ей значения. Неинициализированной переменная считается в двух случаях:
Функция isset()
Функция проверяет факт инициализации переменной. isset() вернет false, если передать ей в качестве параметра неинициализированную переменную. Тоже самое произойдет и с ключом массива, который имеет NULL значение. Для ключей массива действуют такие же правила приведения к NULL, как и для переменных.
Результат:
Функция array_key_exists()
Для массива использование NULL значения ключа удобно, если наличие ключа требуется сохранить. Можно будет рассчитывать на контекст, в котором данный ключ будет использован. Например, это удобно при заполнении полей формы дефолтными значениями. Например, при использовании конструкции echo, NULL значение будет приведено к пустой строке.
То, к чему я клоню, мне кажется очевидным. Если вы работаете с ключами массива, то используйте именно ту функцию, которая для этого предназначена. Ко всему прочему, использование array_key_exists() позволяет точно понять логику приложения. Никакой двусмысленности не возникает.
Скорость работы isset() и array_key_exists()
О том, что array_key_exists() работает медленнее isset() пишут даже в комментариях к соответствующей странице PHP мануала. Жалуются на то, что более медленная работа array_key_exists() заметна на массивах с количеством пар ключ-значение, превышающих 200.
Ничего не остается, как прогнать банальный тест:
isset() | array_key_exists() |
---|---|
0.687027 | 0.728652 |
Тест, безусловно, очень простой и достаточно субъективный. Было бы неплохо сделать замеры используемой памяти, например. Но глядя на результаты этого теста, у меня нет никакого желания продолжать заниматься мышиной возней. Очевидно, что выигрыша в скорости попросту нет.
Функция isset() и свойства объекта
Совсем клиническим случаем можно считать использование isset() для проверки существования свойства у объекта.
Не так часто, как в случае с массивами, но доводится встречать подобные условия:
Случай вопиющий, так как использование NULL в качестве дефолтного значения свойств – очень распространенная практика. И проверять наличие свойства объекта через isset() просто недопустимо.
Также замечу, что для использования property_exists() не требуется наличие объекта класса. Проверку можно произвести по имени класса и имени свойства.
Такое вот занимательное программирование.
Комментарии (6)
Вечер добрый!
Очень классные статьи, большое спасибо за них!
Вы все верно поняли. На самом деле, практически повсеместно используется isset(). Иногда из-за этого возникают логические ошибки в приложениях, что и стало причиной написания данной заметки.
Не надо людей вводить в заблуждение и навязывать свои ошибочные мысли.
В мануале от создателей PHP явно сказано, что isset используется и для проверки элементов массива.
И это правильно. А вот использовать элемент массива с NULL и считать этот элемент существующим, это ошибка.
Вот из-за таких как ты и приходится потом сутками сидеть и разгребать больные коды и не понимать, почему же блин переменные NULL существуют.
Максим, во-первых, я вам вам не «ты». А во-вторых, мир сложнее, чем вам кажется и нужно беречь свои нервы, а то не хватит сил на разгребание больных кодов :))
И если NULL значение в массиве — это ошибка, то это ошибка в интерпретаторе PHP. Но, думаю, дело все в том, что вы просто не понимаете смысл типа данных NULL.
Олег, вы не правы в ряде моментов. Не стоит «гнать» на PHP разработчиков, т.к. те кто ними являются, пишут хороший код. А то, что вы всех, кто написал строчку кода на PHP называете PHP-программистом, это лично ваши «психо-половые» проблемы. Второй момент, насчет NULL: в языке PHP смысл языковой конструкции null отличается от той же конструкции SQL … и не стоит и пытаться друг на друга «натягивать». Это не проблема языка, т.к. авторы языка вольны толковать как им вздумается «сколько людей, столько и мнений.». Третий момент, если Вы знаете, что PHP-разработчики пишут плохой код, то зачем вы его используете? Пишите сами или используете код других языков. Я могу понять, что писали вы пост под влиянием эмоций, но я так и не понял, что было не так в том коде «китайского движка» и что у вас не выходило? 🙂 Напишите название движка, чтобы люди знали о том, и что тут могут вылезти проблемы при использовании и место возникновения проблемы.
На 100 000 итерации isset работает в 6 раз быстрее.
array_key_exists
(PHP 4 >= 4.0.7, PHP 5, PHP 7, PHP 8)
array_key_exists — Проверяет, присутствует ли в массиве указанный ключ или индекс
Описание
Список параметров
Массив с проверяемыми ключами.
Возвращаемые значения
Возвращает true в случае успешного выполнения или false в случае возникновения ошибки.
array_key_exists() ищет ключи только на первом уровне массива. Внутренние ключи в многомерных массивах найдены не будут.
Примеры
Пример #1 Пример использования array_key_exists()
Пример #2 array_key_exists() и isset()
Примечания
Смотрите также
User Contributed Notes 38 notes
If you want to take the performance advantage of isset() while keeping the NULL element correctly detected, use this:
Benchmark (100000 runs):
array_key_exists() : 205 ms
is_set() : 35ms
isset() || array_key_exists() : 48ms
Note:
The code for this check is very fast, so you shouldn’t warp the code into a single function like below, because the overhead of calling a function dominates the overall performance.
function array_check(. )
<
return (isset(..) || array_key_exists(. ))
>
You’ll notice several notes on this page stating that isset() is significantly faster than array_key_exists(). This may be true except for one small hitch. isset() will return false for arrays keys that have there value set to NULL, which is therefore not entirely accurate.
= array();
$foo [ ‘bar’ ] = NULL ;
Beware that if the array passed to array_key_exists is NULL, the return value will also be NULL.
This is undocumented behaviour, moreover the documentation (and return typehint) suggest that the array_key_exists function only returns boolean value. But that’s not the case.
The way array_key_exists handles null, float, boolean, and ‘integer-representing string’ keys is inconsistent in itself and, in the case of bool and float, with the way these are converted when used as array offset.
array (
» => 1,
0 => 2,
1 => 3,
4 => 4,
’08’ => 5,
8 => 6,
)
null is a key.
false is not a key.
true is not a key.
4.6 is not a key.
«08» is a key.
«8» is a key.
Well, and you get this warning three times (on the bools and the float, but not on the null):
Warning: array_key_exists() [function.array-key-exists]: The first argument should be either a string or an integer in /var/www/php/test.php on line 6
The argument of array_key_exists() vs. isset() came up in the workplace today, so I conducted a little benchmark to see which is faster:
?>
On Windows, the output is similar to
array_key_exists(): 0.504 [82.895%] seconds
isset(): 0.104 [17.105%] seconds
On Mac or Linux, isset() is faster but only by a factor of approximately 1.5.
I’ve got a new take on the multi key function I would like to share.
Very simple case-insensitive array_key_exists:
bool (in_array(strtolower($needle), array_map(‘strtolower’, array_keys($haystack))))
array_key_exists doesn’t work with objects implementing ArrayAccess interface. It also ignores possible __get() method in such objects, despite the fact it accepts object as a second parameter. It works only with ‘real’ properties.
Here is an example with array_key_exists switching between content-types :
I took hours for me to debug, and I finally recognized that,
Or you will get no reply.
Rudi’s multidimensional array_key_exists function was not working for me, so i built one that is.
Enjoy.
Here is a little function for case sensitivity to elaborate on what was said by MarkL from ##php (Freenode) and mmanning at mdanderson dot org from this page:
Also, I’ve been running into issues with escaping for Regex, so I decided to give something like this a shot:
Regarding performance differences between isset() and array_key_exists(), the differences may be there, but the function are not always interchangable.
A little function which take an array as keys
Here’s a function to return a reference to the first array element that has a given key. The code works for multidimensional arrays:
I created this function that uses array key exist to compare a form and a table to see if something has changed.
This can be very helpfull if you need to update a table record from a form but you do not want to display all table fields.
/// it works like array_key_exists that can go deeper
$cidade = array(
‘redonda’ => array(
‘curta’ => ‘o seu filme’
),
‘quadrada’ => array(
‘longa’ => array(
‘azul’ => array(‘logo’,2,’mais’,2,’são’,4),
‘amarela’ => array(‘então’,3,’vezes’,2,’são’,6),
‘verde’ => array(‘senão’,100,’dividido por’,2,’é’,50)
),
‘extravagante’ => array(
‘vermelha’ => ‘chama atenção’,
‘vinho’ => ‘cor de uva’,
‘ocre’ => 1255
),
‘comprida’ => array(
‘amarela’ => ‘brasilia dos mamonas’,
‘branca’ => ‘bandeira da paz’,
‘preta e branca’ => ‘peças do xadrez’
)
),
‘oval’ => array(
‘conde’ => ‘lobo’
),
‘plana’ => array(
‘curta’ => array(
‘azul’ => array(‘e’,2,’mais’,2,’são’,4),
‘amarela’ => array(‘sim’,3,’vezes’,2,’são’,6),
‘verde’ => array(‘verdade’,100,’dividido por’,2,’é’,50)
)
)
);
/// if the tree you search for exists, it will print out ‘true’
Further research on this has turned up that the performance problems are a known, confirmed bug in PHP 5.1.x, and have been fixed in PHP builds after September 2006. You can find the bug report here: http://bugs.php.net/bug.php?id=38812
However, just because it’s a fixed bug doesn’t really change the conclusion. If you’re writing a script and there’s any chance it could be used on a PHP 5.1.x server, you should still avoid this function and use isset() or some other kind of test if you want it to run efficiently.
I saw some examples above for array_keys_exist() or functions to see if multiple keys exist in a given array and return false if any of them don’t.
Here is a simpler way to do this:
Is (isset(..) || array_key_exists(. )) a faster way of detecting null values than just array_key_exists(. ))?
While researching how to detect null values in an array, I came across some user’s comment under the http://www.php.net/manual/en/function.array-key-exists.php manual page.
is faster than doing
The bench marks posted for 100000 runs were
My question:
EDIT: In writing out this question I think I found my answer. I’ve decided to post the question anyway to see if my thinking is correct.
3 Answers 3
Which is faster depends on the array you are checking. If the array contains a value other than null, «», or 0
the above code will be faster, because isset will be checked then the code executed. Array_key_exists will not be run.
So the question which is faster depends a lot on the array you are checking.
A lot of people have said that it doesn’t really matter. They didn’t explain why it doesn’t matter though. I guess they mean that the speed improvements are so minimal that it isn’t worth bothering with. They may also mean that which is faster is dependent on the values assigned in your array (and thus different each time.)
Ultimately though, if you know that most of the keys will be assigned values other than null, «» or 0 and you really need to determine when null values are assigned, then use
PHP: array_key_exists ищет быстрее чем in_array в 500 раз
В 2014 уже писали про обыск массива, но вряд ли кто понял.
C тех пор вышло много версий PHP и не исправили значит обратная связь плохая и об этом мало кто знает. На питоне так же, и в 3* хуже чем в 2.7.
Иногда нужно найти строку в массиве строк — очень частая операция в разных алгоритмах и если массив небольшой и искать немного и не в цикле, то in_array нормально, на общую скорость не влияет, но если big data и искать надо массиве из миллиарда строк и миллиард раз, то это уже критично: лучше час вместо недели.
Простой тест показывает:
in_array ищет за 6-9 сек ideone.com/Yb1mDa 6600ms
а array_key_exists ищет тоже самое, но быстрее в 250(php5.6/py3.*) в 400+ раз (php7.3/py2.7) ideone.com/gwSmFc (цикл увеличен в 100 раз) 12ms (6600/12=550раз +-10% разброс из-за нагрузки и кеша)
Почему же такое происходит? Рассмотрим подробно:
1) Поиск строк на чистом ассемблере/си это сортировка массива строк (быстрая или пузырьковая), затем бинарный поиск.
Число шагов в бинарном поиске log(n) раз и зависит от размера массива, и намного меньше чем простой перебор.
Отсортировать массив строк можно заранее, один раз и закешировать, а потом делать миллиард поисков. Но это не помогает.
По умолчанию сортировка происходит каждый раз снова, хотя писали что улучшили в 7.2 in_array через хеш, но немного.
Размер хеша, алгоритм хеширования зашит в движок пхп и его не поменять, хотя исходники открыты- можно скачать изменить и скомпилировать если сервер свой.
Дальше можно не читать, меняйте in_array на array_combine + array_key_exists и всё.
Число шагов при поиске по хешу зависит от количества коллизий и кол-ва строк с одинаковым хешем. Их нужно перебирать или также сортировать и бинарный поиск.
Для уменьшения коллизий можно выделить больше памяти, если возможно, что сейчас не такая проблема, как 50 лет назад когда 1 кб памяти на магн.катушках стоил как самолет. А именно тогда были придуманы все основные алгоритмы: sort/zip/gif/jpg/итд — им не надо много памяти, но они плохие, сейчас есть намного лучше, но им надо много памяти 1-16 Мб. Да, есть серверы с 256 Мб и на каждого отдельный поток и 16 Мб уже много, но на девайсе среднего юзера 1 Гб как минимум и 16 Мб это капля в море.
Еще больший эффект можно получить если заменить вызов функции array_key_exists на конструкцию isset($mphp array isset array key exists), она не чистит очередь команд и кеш, не использует стек и быстрее где-то на 20%.
Так же можно еще ускорить если создать массив 2х первых букв- 4*16кб и искать сначала по смещению (индекс=код 1го символа + 2го*256) указатель на массив хешей для остальной части строки, затем ищем уже среди маленького массива «хвостов» строк и коллизий на порядок меньше.
Это требует еще больше памяти и алгоритм сложнее, но поиск быстрее в 30+ раз. Но в пхп это не реализовано, можно написать свою библиотеку so/dll и вызывать, или попросить разработчиков добавить в 7.5.
Можно искать через mySQL, но надо группировать запросы и это будет все равно медленней.