isset() vs strlen() - быстрый/четкий расчет длины строки


Я наткнулся на этот код...

if(isset($string[255])) {
    // too long
}

Isset() на 6-40 быстрее, чем

if(strlen($string) > 255) {
    // too long
}

Единственным недостатком isset() является то, что код неясен - мы не можем сразу сказать, что делается (см. Ответ пекки). Мы можем обернуть isset() в функцию, т.е. strlt($string, 255), но тогда мы теряем преимущества скорости isset().

Как мы можем использовать более быструю функцию isset(), сохраняя при этом читабельность кода?

ИЗМЕНИТЬ: проверить, чтобы показать скорость http://codepad.org/ztYF0bE3

strlen() over 1000000 iterations 7.5193998813629
isset() over 1000000 iterations 0.29940009117126

ПРАВКА2: вот почему isset() быстрее

$string = 'abcdefg';
var_dump($string[2]);
Output: string(1) “c”

$string = 'abcdefg';
if (isset($string[7])){
     echo $string[7].' found!';
  }else{
     echo 'No character found at position 7!';
}

Это быстрее, чем использование strlen(), потому что "...вызов функции обходится дороже, чем использование языковой конструкции". http://www.phpreferencebook.com/tips/use-isset-instead-of-strlen/

ПРАВКА 3: Меня всегда учили интересоваться мирко-оптимизацией. Вероятно, потому, что меня учили в то время, когда ресурсы на компьютерах были крошечными. Я открыт для этой идеи то, что это может быть не важно, в ответах есть несколько веских аргументов против этого. Я начал новый вопрос, исследующий это... https://stackoverflow.com/questions/6983208/is-micro-optimisation-important-when-coding

Author: Community, 2011-08-05

8 answers

Хорошо, поэтому я провел тесты, так как с трудом мог поверить, что метод isset() быстрее, но да, это так, и значительно. Метод isset() последовательно примерно в 6 раз быстрее.

Я пробовал использовать строки разных размеров и выполнял различное количество итераций; соотношения остаются прежними, а также, кстати, общая длина выполнения (для строк разного размера), потому что и isset(), и strlen() равны O(1) (что имеет смысл - isset нужно только выполнить поиск в C массив, и strlen() возвращает только количество размеров, сохраненное для строки).

Я посмотрел его в источнике php и, думаю, примерно понимаю, почему.isset(), потому что это не функция, а языковая конструкция, имеет свой собственный код операции в виртуальной машине Zend. Поэтому его не нужно искать в таблице функций, и он может выполнять более специализированный анализ параметров. Код находится в zend_builtin_functions.c для strlen() и zend_compile.c для isset(), для тех, кто заинтересован.

Связать возвращаясь к первоначальному вопросу, я не вижу никаких проблем с методом isset() с технической точки зрения; но имо труднее читать для людей, которые не привыкли к идиоме. Кроме того, метод isset() будет постоянным во времени, в то время как метод strlen() будет равен O(n) при изменении количества функций, встроенных в PHP. Это означает, что если вы создадите PHP и статически скомпилируете во многих функциях, все вызовы функций (включая strlen()) будут медленнее; но isset() будет постоянным. Однако на практике эта разница будет незначительной; Я также не знаю, сколько таблиц указателей функций поддерживается, поэтому, если пользовательские функции также оказывают влияние. Кажется, я помню, что они находятся в другой таблице и, следовательно, не имеют отношения к данному случаю, но прошло уже некоторое время с тех пор, как я в последний раз действительно работал с этим.

В остальном я не вижу никаких недостатков в методе isset(). Я не знаю других способов получить длину строки, если не рассматривать целенаправленно запутанные, такие как взрыв+подсчет и тому подобное.

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

 50
Author: Roel, 2011-08-05 13:52:44

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

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

 20
Author: Pekka 웃, 2011-08-05 12:22:22

Ваш код неполон.

Вот, я исправил это для вас:

if(isset($string[255])) {
    // something taking 1 millisecond
}

Против

if(strlen($string) > 255) {
    // something taking 1 millisecond
}

Теперь у вас не пустой цикл, а реалистичный. Давайте рассмотрим, что для того, чтобы что-то сделать, требуется 1 миллисекунда.

Современный процессор может многое сделать за 1 миллисекунду - это дано. Но такие вещи, как случайный доступ к жесткому диску или запрос базы данных, занимают несколько миллисекунд - тоже реалистичный сценарий.

Теперь давайте снова рассчитаем время:

realistic routine + strlen() over 1000000 iterations 1007.5193998813629
realistic routine + isset() over 1000000 iterations 1000.29940009117126

См. разница?

 10
Author: Boris Yankov, 2011-08-09 14:38:29

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

Во-вторых, я хочу, чтобы вы знали о том, что XDebug значительно снижает производительность вызовов функций, поэтому, если вы используете XDebug, вы можете получить запутанные числа. Ссылка (Второй раздел вопроса). Таким образом, в производстве (где, надеюсь, у вас не установлен XDebug) разница еще меньше. Это идет ко дну от 6x до 2x.

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

В-четвертых, пожалуйста, обратите внимание, что в настоящее время время разработки намного дороже, чем загрузка сервера. Разработчик тратит даже на полсекунды больше, понимая, что такое код isset это намного дороже, чем экономия на загрузке процессора. Кроме того, загрузка сервера может быть намного лучше сохранена за счет применения оптимизаций, которые действительно имеют значение (например, кэширование).

 3
Author: NikiC, 2017-05-23 12:02:03

Это последний тест:

function benchmark_function($fn,$args=null)
{
    if(!function_exists($fn))
    {
        trigger_error("Call to undefined function $fn()",E_USER_ERROR);
    }

    $t = microtime(true);

    $r = call_user_func_array($fn,$args);

    return array("time"=>(microtime(true)-$t),"returned"=>$r,"fn"=>$fn);
}

function get_len_loop($s)
{
    while($s[$i++]){}
    return $i-1;
}
echo var_dump(benchmark_function("strlen","kejhkhfkewkfhkwjfjrw"))."<br>";
echo var_dump(benchmark_function("get_len_loop","kejhkhfkewkfhkwjfjrw"));

Возвращенные результаты:

ВЫПОЛНИТЬ 1:

Массив(3) { ["время"]=> поплавок(2.1457672119141 Е-6) ["вернулись"]=> тип int(20) ["ФН"]=> строка(6) "функция strlen" } массив(3) { ["время"]=> поплавок(1.1920928955078 Е-5) ["вернулись"]=> тип int(20) ["ФН"]=> строка(12) "get_len_loop" }

ВЫПОЛНИТЬ 2:

Массив(3) { ["время"]=> поплавок(4.0531158447266 Е-6) ["вернулись"]=> тип int(20) ["ФН"]=> строка(6) "функция strlen" } массив(3) { ["время"]=> поплавок(1.5020370483398 Е-5) ["возвращено"]=>int(20) ["fn"]=>строка (12) "get_len_loop"}

ВЫПОЛНИТЬ 3:

Массив(3) { ["время"]=> поплавок(4.0531158447266 Е-6) ["вернулись"]=> тип int(20) ["ФН"]=> строка(6) "функция strlen" } массив(3) { ["время"]=> поплавок(1.2874603271484 Е-5) ["вернулись"]=> тип int(20) ["ФН"]=> строка(12) "get_len_loop" }

ЗАПУСК 4:

Массив(3) { ["время"]=> поплавок(3.0994415283203 Е-6) ["вернулись"]=> тип int(20) ["ФН"]=> строка(6) "функция strlen" } массив(3) { ["время"]=> поплавок(1.3828277587891 Е-5) ["вернулись"]=> int(20) ["fn"]=>строка(12) "get_len_loop"}

ВЫПОЛНИТЬ 5:

Массив(3) { ["время"]=> поплавок(5.0067901611328 Е-6) ["вернулись"]=> тип int(20) ["ФН"]=> строка(6) "функция strlen" } массив(3) { ["время"]=> поплавок(1.4066696166992 Е-5) ["вернулись"]=> тип int(20) ["ФН"]=> строка(12) "get_len_loop" }

 1
Author: Ismael Miguel, 2012-10-14 21:24:41

Недостатком является то, что isset вообще не является явным, в то время как strlen действительно ясно представляет, каковы ваши намерения. Если кто-то прочитает ваш код и должен понять, что вы делаете, это может вызвать у него проблемы и быть не совсем ясным.

Если вы не используете facebook, я сомневаюсь, что strlen будет тем местом, где ваш сервер будет тратить большую часть своих ресурсов, и вам следует продолжать использовать strlen.

Я только что проверил, что strlen намного быстрее, чем isset.

0.01 seconds for 100000 iterations with isset

0.04 seconds for 100000 iterations with strlen

Но это не меняет того, что я только что сказал.

Сценарий, как некоторые люди только что спросили:

$string =    'xdfksdjhfsdljkfhsdjklfhsdlkjfhsdjklfhsdkljfhsdkljfhsdljkfsdhlkfjshfljkhfsdljkfhsdkljfhsdkljfhsdklfhlkjfhkljfsdhfkljsdhfkljsdhfkljhsdfjklhsdjklfhsdkljfhklsdhfkljsdfhdjkshfjlhdskljfhsdkljfhsdjkfhsjkldhfklsdjhfkjlsfhdjkflsdhfjklfsdljfsdlkdlfkjflfkjsdfkl';

for ($i = 0; $i < 100000; $i++) {
   if (strlen($string) == 255) {
   // if (isset($string[255])) {
       // do nothing
   }
}
 0
Author: yokoloko, 2011-08-05 12:14:25

В современных объектно-ориентированных веб-приложениях одну строку, которую вы пишете в небольшом классе, легко можно запустить несколько сотен раз для создания одной веб-страницы.
Возможно, вы захотите профилировать свой веб-сайт с помощью xdebug, и вы можете быть удивлены, сколько раз выполняется каждый метод класса.
Тогда в реальных сценариях вы можете работать не только с маленькими строками, но и с действительно большими документами размером до 3 МБ или больше.
Вы также можете столкнуться с текстом с нелатинскими символами.
Таким образом, в конечном итоге то, что изначально было лишь небольшой потерей производительности, может привести к серверам 100 миллисекунд на рендеринге веб-страницы.

Поэтому я очень заинтересован в этой проблеме и написал небольшой тест, который проверил бы 4 различных метода, чтобы проверить, действительно ли строка пуста "" или на самом деле содержит что-то вроде "0".

function stringCheckNonEmpty0($string)
{
  return (empty($string));
}

function stringCheckNonEmpty1($string)
{
  return (strlen($string) > 0);
}

function stringCheckNonEmpty1_2($string)
{
  return (mb_strlen($string) > 0);
}

function stringCheckNonEmpty2($string)
{
  return ($string !== "");
}

function stringCheckNonEmpty3($string)
{
  return (isset($string[0]));
}

Я обнаружил, что PHP сложно работать с нелатинскими символами, чтобы я скопировал русский текст с веб-страницы чтобы сравнить результаты между крошечной строкой "0" и большим русским текстом.

$steststring = "0";

$steststring2 = "Hotel Majestic в городе Касабланка располагается всего в нескольких минутах от "
  . "следующих достопримечательностей и объектов: "
  . "Playas Ain Diab y La Corniche и Центральный рынок Касабланки. "
  . "Этот отель находится вблизи следующих достопримечательностей и объектов: "
  . "Площадь Мухаммеда V и Культурный комплекс Сиди-Бельот.";

Чтобы действительно увидеть разницу, я вызывал каждую тестовую функцию несколько миллионов раз.

$iruncount = 10000000;

echo "test: empty(\"0\"): starting ...\n";

$tmtest = 0;
$tmteststart = microtime(true);
$tmtestend = 0;

for($irun = 0; $irun < $iruncount; $irun++)
  stringCheckNonEmpty0($steststring);

$tmtestend = microtime(true);
$tmtest = $tmtestend - $tmteststart;

echo "test: empty(\"0\"): '$tmtest' s\n";

Результаты испытаний

$ php test_string_check.php
test0.1: empty("0"): starting ...
test0.1: empty("0"): '7.0262970924377' s
test0.2: empty(russian): starting ...
test0.2: empty(russian): '7.2237210273743' s
test1.1.1: strlen("0"): starting ...
test1.1.1: strlen("0"): '11.045154094696' s
test1.1.2: strlen(russian): starting ...
test1.1.2: strlen(russian): '11.106546878815' s
test1.2.1: mb_strlen("0"): starting ...
test1.2.1: mb_strlen("0"): '11.320801019669' s
test1.2.2: mb_strlen(russian): starting ...
test1.2.2: mb_strlen(russian): '23.082058906555' s
test2.1: ("0" !== ""): starting ...
test2.1: ("0" !== ""): '7.0292129516602' s
test2.2: (russian !== ""): starting ...
test2.2: (russian !== ""): '7.1041729450226' s
test3.1: isset(): starting ...
test3.1: isset(): '6.9401099681854' s
test3.2: isset(russian): starting ...
test3.2: isset(russian): '6.927631855011' s

$ php test_string_check.php
test0.1: empty("0"): starting ...
test0.1: empty("0"): '7.0895299911499' s
test0.2: empty(russian): starting ...
test0.2: empty(russian): '7.3135821819305' s
test1.1.1: strlen("0"): starting ...
test1.1.1: strlen("0"): '11.265664100647' s
test1.1.2: strlen(russian): starting ...
test1.1.2: strlen(russian): '11.282053947449' s
test1.2.1: mb_strlen("0"): starting ...
test1.2.1: mb_strlen("0"): '11.702164888382' s
test1.2.2: mb_strlen(russian): starting ...
test1.2.2: mb_strlen(russian): '23.758249998093' s
test2.1: ("0" !== ""): starting ...
test2.1: ("0" !== ""): '7.2174110412598' s
test2.2: (russian !== ""): starting ...
test2.2: (russian !== ""): '7.240779876709' s
test3.1: isset("0"): starting ...
test3.1: isset("0"): '7.2104151248932' s
test3.2: isset(russian): starting ...
test3.2: isset(russian): '7.2232971191406' s

Заключение

  • Обычная функция emtpy() работает хорошо, но не работает со строками , такими как "0".
  • Функция mb_strlen(), необходимая для проверки текстов с нелатинскими символами, хуже работает на больших тексты.
  • Проверка $string !== "" выполняется очень хорошо. Даже лучше, чем функция empty().
  • Но наилучшую производительность дает проверка isset($string[0]).

Мне определенно придется поработать над всей моей библиотекой объектов.

 0
Author: Bodo Hugo Barwich, 2017-03-28 08:13:39

Если вы хотите сохранить ясность, вы могли бы сделать что-то вроде:

function checklength(&$str, $len)
{
     return isset($str[$len]);
}
 -1
Author: Manhim, 2011-08-07 20:51:00