PHP: самый быстрый способ обработки ключа массива undefined

в очень узком цикле мне нужно получить доступ к десятым числам значений в массиве, содержащем миллионы элементов. Ключ может быть не определен: в этом случае законно возвращать 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, поскольку это быстрее, чем альтернативы - Проверьте php QuickHash

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

Ответ 1

Обновление

Начиная с PHP 7, вы можете сделать это с помощью оператора null coalesce:

return $table[$key] ?? null;

Старый ответ

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

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

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

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

Далее следует использовать ссылку, чтобы избежать второго поиска:

$tmp = &$lookup_table[$key];

return isset($tmp) ? $tmp : null;

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

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

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

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

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

Ответ 2

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

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, за которым следует решение, которое просто отключает отчет об ошибках.

Ответ 3

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

  • Определите значения по умолчанию для клавиши undefined.
  • Проверьте undefined.

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

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

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

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

Ответ 4

Просто внезапная идея, которая должна быть проверена, но попробовали ли вы использовать 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);

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

Ответ 5

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

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

 // first sort the array by it 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);

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

  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. Я не могу обещать это быстрее. Только октет будет быстрее, но время для создания октета может привести к сокращению производительности поиска (я уже это испытывал). Это зависит от того, сколько поисков в данных вы должны делать.

Ответ 6

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

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

но это быстро

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

Ответ 7

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

Ответ 8

Я предпочитаю использовать функцию isset вместо экранирования ошибки. Я сделал функцию, чтобы проверить, существует ли ключ, и если нет, возвращает значение по умолчанию, то в случае вложенных массивов вам просто нужно добавить другие ключи по порядку:

Поиск вложенных массивов:

/**
 * Lookup array value.
 *
 * @param array $array
 * @param array $keys
 * @param $defaultValue
 */
public static function array_key_lookup($array, $keys, $defaultValue)
{
    $value = $array;
    foreach ($keys as $key) {
        if (isset($value[$key])) {
            $value = $value[$key];
        } else {
            $value = $defaultValue;
            break;
        }
    }

    return $value;
}

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

$array = [
    'key1' => 'value1',
    'key2' => 'value2',
    'key3' => [
        'key3a' => 'value3a',
        'key3b' => 'value3b'
    ]
];

array_key_lookup($array, ['key3', 'key3a'], 'default')
'value3a'

array_key_lookup($array, ['key2', 'key2a'], 'default')
'default'

array_key_lookup($array, ['key2'], 'default')
'value2'

array_key_lookup($array, ['key5'], 'default')
'default'

Выход из ошибки:

$value = @$array[$key1][$key2] ?: $defaultValue;