PHP: самый быстрый способ обработки неопределенного ключа массива

в очень узком цикле мне нужно получить доступ к десятым числам значений в массиве, содержащем миллионы элементов. Ключ может быть не определен: в этом случае законно возвращать NULL без сообщения об ошибке:

Существует ключ Array: возвращает значение элемента. Клавиша Array не существует: return null.

Я знаю несколько решений:

if (isset($lookup_table[$key])) { return $lookup_table[$key]; } else { return; } 

или

 @return $lookup_table[$key]; 

или

 error_reporting(0); $return = $lookup_table[$key]; error_reporting(E_ALL); return $return; 

Все решения далеко не оптимальны:

  • Первый требует 2 поиска в B-TREE: один для проверки существования, другой для получения значения. Это эффективно удваивает время выполнения.
  • Второй использует оператор подавления ошибок и, таким образом, создает огромные накладные расходы на этой линии.
  • Третий вызывает обработчик ошибок (который будет проверять параметр error_reporting, а затем отображать ничего) и тем самым создает накладные расходы.

Мой вопрос в том, что если я пропущу способ избежать обработки ошибок и все же работать с одним поиском btree?

Чтобы ответить на некоторые вопросы:

Массив кэширует результаты комплексного расчета – комплексно выполняется в реальном времени. Из миллиардов возможных значений только миллионы были действительными. Массив выглядит как 1234567 => 23457, 1234999 => 74361, …. Это сохраняется в php-файле в несколько мегабайт, а include_once-d – в начале выполнения. Начальное время загрузки не имеет значения. Если ключ не найден, это просто означает, что этот конкретный результат не вернет действительный результат. Проблема состоит в том, чтобы сделать это 50k + в секунду.

Вывод

Поскольку нет способа найти значение с одним поиском и без обработки ошибок, я не могу принять один ответ. Вместо этого я выделил все большие вклады.

Наиболее ценные входы, где: – используйте array_key_exists, так как это быстрее, чем альтернативы. – Проверьте QuickHash php

Было много путаницы в том, как PHP обрабатывает массивы. Если вы проверите исходный код, вы увидите, что все массивы являются сбалансированными деревьями. Создание собственных методов поиска является обычным явлением на C и C ++, но не работает на более высоких скриптовых языках, таких как php.

Обновить

С PHP 7 вы можете выполнить это с помощью оператора null coalesce :

 return $table[$key] ?? null; 

Старый ответ

Прежде всего, массивы не реализованы как B-дерево, это хеш-таблица; массив ведер (индексируется через хэш-функцию), каждый со связанным списком фактических значений (в случае хеш-коллизий). Это означает, что время поиска зависит от того, насколько хорошо хэш-функция «распределяет» значения по ковшим, т. Е. Число хэш-коллизий является важным фактором.

Технически это утверждение является наиболее правильным:

 return array_key_exists($key, $table) ? $table[$key] : null; 

Это вводит вызов функции и, следовательно, намного медленнее оптимизированного isset() . Сколько? ~ 2e3 раза медленнее.

В следующем примере используется ссылка, чтобы избежать второго поиска:

 $tmp = &$lookup_table[$key]; return isset($tmp) ? $tmp : null; 

К сожалению, это изменяет исходный $lookup_table массив, если элемент не существует, потому что ссылки всегда выполняются PHP.

Это оставляет следующий метод, который очень похож на ваш собственный:

 return isset($lookup_table[$key]) ? $lookup_table[$key] : null; 

Помимо отсутствия побочного эффекта ссылок, он также быстрее во время выполнения, даже при выполнении поиска дважды.

Вы можете разделить свои массивы на более мелкие кусочки как один из способов уменьшить длительность поиска.

Я сделал некоторую маркировку с помощью следующего кода:

 set_time_limit(100); $count = 2500000; $search_index_end = $count * 1.5; $search_index_start = $count * .5; $array = array(); for ($i = 0; $i < $count; $i++) $array[md5($i)] = $i; $start = microtime(true); for ($i = $search_index_start; $i < $search_index_end; $i++) { $key = md5($i); $test = isset($array[$key]) ? $array[$key] : null; } $end = microtime(true); echo ($end - $start) . " seconds<br/>"; $start = microtime(true); for ($i = $search_index_start; $i < $search_index_end; $i++) { $key = md5($i); $test = array_key_exists($key, $array) ? $array[$key] : null; } $end = microtime(true); echo ($end - $start) . " seconds<br/>"; $start = microtime(true); for ($i = $search_index_start; $i < $search_index_end; $i++) { $key = md5($i); $test = @$array[$key]; } $end = microtime(true); echo ($end - $start) . " seconds<br/>"; $error_reporting = error_reporting(); error_reporting(0); $start = microtime(true); for ($i = $search_index_start; $i < $search_index_end; $i++) { $key = md5($i); $test = $array[$key]; } $end = microtime(true); echo ($end - $start) . " seconds<br/>"; error_reporting($error_reporting); $start = microtime(true); for ($i = $search_index_start; $i < $search_index_end; $i++) { $key = md5($i); $tmp = &$array[$key]; $test = isset($tmp) ? $tmp : null; } $end = microtime(true); echo ($end - $start) . " seconds<br/>"; 

и я обнаружил, что самым быстрым испытанием является тот, который использует isset($array[$key]) ? $array[$key] : null isset($array[$key]) ? $array[$key] : null строго следует решению, которое просто отключает отчет об ошибках.

Есть два типичных подхода к этому.

  1. Определите значения по умолчанию для неопределенного ключа.
  2. Проверьте наличие неопределенного ключа.

Вот как выполнить первый и как можно меньше кода.

 $data = array_merge(array($key=>false),$data); return $data[$key]; 

Вот как выполнить второе.

 return isset($data[$key]) ? $data[$key] : false; 

Просто внезапная идея, которая должна быть протестирована, но вы попытались использовать array_intersect_key() чтобы получить существующие значения и заполнить array_merge ()? Это устранит необходимость в цикле для доступа к данным. Что-то вроде того :

 $searched_keys = array ('key1' => null, 'key2' => null); // the list of the keys to find $exiting_values = array_intersect_key($lookup_table, $searched_keys); $all_values = array_merge($searched_keys, $exiting_keys); 

Обратите внимание, что я не пробовал это с точки зрения производительности.

Во-первых, переустановите данные для производительности, сохранив новый массив, где данные отсортированы по ключам, но новый массив содержит обычный числовой индекс.

Эта часть будет занимать много времени, но только один раз.

  // first sort the array by it's keys ksort($data); // second create a new array with numeric index $tmp = new array(); foreach($data as $key=>$value) { $tmp[] = array('key'=>$key,'value'=>$value); } // now save and use this data instead save_to_file($tmp); 

Как только это будет сделано, нужно быстро найти ключ, используя двоичный поиск . Позже вы можете использовать такую ​​функцию.

  function findKey($key, $data, $start, $end) { if($end < $start) { return null; } $mid = (int)(($end - $start) / 2) + $start; if($data[$mid]['key'] > $key) { return findKey($key, $data, $start, $mid - 1); } else if($data[$mid]['key'] < $key) { return findKey($key, $data, $mid + 1, $end); } return $data[$mid]['value']; } 

Чтобы выполнить поиск ключа, вы сделаете это.

  $result = findKey($key, $data, 0, count($data)); if($result === null) { // key not found. } 

Если count($data) выполняется все время, вы можете кэшировать его в файле, в котором вы храните данные массива.

Я подозреваю, что этот метод будет намного быстрее в производительности, чем регулярный линейный поиск, который повторяется против $data . Я не могу обещать, что это быстрее. Только октет будет быстрее, но время для создания октета может привести к сокращению производительности поиска (я уже это испытывал). Это зависит от того, сколько поисков в данных вы должны делать.

Эта работа для меня

 {{ isset($array['key']) ? $array['key']: 'Default' }} 

но это быстро

 {{ $array['key'] or 'Default' }} 

Операторы @ и методы error_reporting будут медленнее, чем использование isset. При использовании обоих этих методов он изменяет параметр отчетности об ошибках для PHP, но обработчик ошибок PHP будет по-прежнему вызываться. Обработчик ошибок будет проверять настройку error_reporting и выйти, не сообщая ничего, однако это все еще требует времени.