Подтвердить что ты не робот

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

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

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

isset() находится между 6 и 40 быстрее, чем

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

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

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

EDIT: тест, чтобы показать скорость http://codepad.org/ztYF0bE3

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

EDIT2: вот почему 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/

EDIT3: меня всегда учили интересовать mirco-оптимизацией. Наверное, потому, что меня учили в то время, когда ресурсы на компьютерах были крошечными. Я открыт для идеи, что это может быть неважно, есть несколько хороших аргументов против этого в ответах. Я начал новый вопрос, изучая это... https://stackoverflow.com/info/6983208/is-micro-optimisation-important-when-coding

4b9b3361

Ответ 1

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

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

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

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

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

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

Ответ 2

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

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

Ответ 3

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

Здесь я исправил его для вас:

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

См. разницу?

Ответ 4

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

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

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

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

Ответ 5

это последнее испытание:

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"));

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

RUN 1:

array (3) {[ "time" ] = > float (2.1457672119141E-6) [ "return" ] = > int (20) [ "fn" ] = > string (6) "strlen" } array (3) {[ "time" ] = > float (1.1920928955078E-5) [ "return" ] = > int (20) [ "fn" ] = > string (12) "get_len_loop" }

RUN 2:

array (3) {[ "time" ] = > float (4.0531158447266E-6) [ "return" ] = > int (20) [ "fn" ] = > string (6) "strlen" } array (3) {[ "time" ] = > float (1.5020370483398E-5) [ "return" ] = > int (20) [ "fn" ] = > string (12) "get_len_loop" }

RUN 3:

array (3) {[ "time" ] = > float (4.0531158447266E-6) [ "return" ] = > int (20) [ "fn" ] = > string (6) "strlen" } array (3) {[ "time" ] = > float (1.2874603271484E-5) [ "return" ] = > int (20) [ "fn" ] = > string (12) "get_len_loop" }

RUN 4:

array (3) {[ "time" ] = > float (3.0994415283203E-6) [ "return" ] = > int (20) [ "fn" ] = > string (6) "strlen" } array (3) {[ "time" ] = > float (1.3828277587891E-5) [ "return" ] = > int (20) [ "fn" ] = > string (12) "get_len_loop" }

RUN 5:

array (3) {[ "time" ] = > float (5.0067901611328E-6) [ "return" ] = > int (20) [ "fn" ] = > string (6) "strlen" } array (3) {[ "time" ] = > float (1.4066696166992E-5) [ "return" ] = > int (20) [ "fn" ] = > string (12) "get_len_loop" }

Ответ 6

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

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

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

0.01 seconds for 100000 iterations with isset

0.04 seconds for 100000 iterations with strlen

Но не меняет то, что я сказал сейчас.

script как только кто-то спросил:

$string =    'xdfksdjhfsdljkfhsdjklfhsdlkjfhsdjklfhsdkljfhsdkljfhsdljkfsdhlkfjshfljkhfsdljkfhsdkljfhsdkljfhsdklfhlkjfhkljfsdhfkljsdhfkljsdhfkljhsdfjklhsdjklfhsdkljfhklsdhfkljsdfhdjkshfjlhdskljfhsdkljfhsdjkfhsjkldhfklsdjhfkjlsfhdjkflsdhfjklfsdljfsdlkdlfkjflfkjsdfkl';

for ($i = 0; $i < 100000; $i++) {
   if (strlen($string) == 255) {
   // if (isset($string[255])) {
       // do nothing
   }
}

Ответ 7

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

function checklength(&$str, $len)
{
     return isset($str[$len]);
}

Ответ 8

В современных объектно-ориентированных веб-приложениях одна строка, которую вы пишете в небольшом классе легко, может запускаться несколько раз в несколько раз для создания единой веб-страницы.
Возможно, вы захотите профилировать свой веб-сайт с помощью 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]) Check.

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