IT TIP

isset () 대 strlen ()-빠르고 명확한 문자열 길이 계산

itqueen 2021. 1. 7. 20:15
반응형

isset () 대 strlen ()-빠르고 명확한 문자열 길이 계산


이 코드를 발견했습니다 ...

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

isset ()는 6 ~ 40보다 빠릅니다.

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

isset ()의 유일한 단점은 코드가 명확하지 않다는 것입니다. 우리는 무슨 일이 일어나고 있는지 즉시 알 수 없습니다 (pekka의 답변 참조). isset () 함수를 strlt ($ string, 255)와 같이 감싸면 isset ()의 속도 이점이 사라집니다.

코드의 가독성을 유지하면서 더 빠른 isset () 함수를 어떻게 사용할 수 있습니까?

편집 : 속도 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/questions/6983208/is-micro-optimisation-important-when-coding


좋아 그래서 나는 isset () 메소드가 더 빠르다는 것을 거의 믿을 수 없었기 때문에 테스트를 실행했지만, 그렇다. isset () 메서드는 지속적으로 약 6 배 더 빠릅니다.

나는 다양한 크기의 문자열로 시도하고 다양한 양의 반복을 실행했습니다. 비율은 동일하게 유지되며 전체 실행 길이 (크기가 다양한 문자열의 경우)도 유지됩니다. isset ()과 strlen ()이 모두 O (1)이기 때문입니다. C 배열이고 strlen ()은 문자열에 대해 유지되는 크기 수만 반환합니다.

나는 그것을 PHP 소스에서 찾아 보았고 그 이유를 대략 이해했다고 생각합니다. isset ()은 함수가 아니라 언어 구조이므로 Zend VM에 자체 opcode가 있습니다. 따라서 함수 테이블에서 조회 할 필요가 없으며보다 전문화 된 매개 변수 구문 분석을 수행 할 수 있습니다. 코드는 strlen ()의 경우 zend_builtin_functions.c, isset ()의 경우 zend_compile.c에 있습니다.

이것을 원래의 질문에 다시 연결하기 위해 기술적 인 관점에서 isset () 메서드에 문제가 없다고 생각합니다. 그러나 imo 관용구에 익숙하지 않은 사람들은 읽기가 더 어렵습니다. 또한 isset () 메서드는 시간상 일정하고 strlen () 메서드는 PHP에 빌드되는 함수의 양을 변경할 때 O (n)이됩니다. 즉, PHP를 빌드하고 많은 함수에서 정적으로 컴파일하면 모든 함수 호출 (strlen () 포함)이 느려집니다. 그러나 isset ()은 일정합니다. 그러나이 차이는 실제로 미미할 것입니다. 또한 얼마나 많은 함수 포인터 테이블이 유지되는지 모르기 때문에 사용자 정의 함수도 영향을 미칩니다. 나는 그들이 다른 테이블에 있으므로이 경우와 관련이 없다는 것을 기억하는 것 같지만 마지막으로 이것을 실제로 작업 한 지 오래되었습니다.

나머지는 isset () 메서드의 단점이 없습니다. explode + count와 같은 의도적으로 복잡한 것을 고려하지 않을 때 문자열의 길이를 얻는 다른 방법을 모릅니다.

마지막으로 isset ()를 함수로 래핑하는 위의 제안도 테스트했습니다. 이것은 다른 함수 호출이 필요하고 다른 해시 테이블 조회가 필요하기 때문에 strlen () 메서드보다 느립니다. 검사 할 크기에 대한 추가 매개 변수의 오버 헤드는 무시할 수 있습니다. 참조로 전달되지 않을 때 문자열을 복사하는 것과 같습니다.


이것의 속도 차이는 전혀 중요하지 않습니다. 기껏해야 몇 밀리 초가 될 것입니다.

당신과 코드를 작업하는 다른 사람들이 가장 잘 읽을 수있는 스타일을 사용하십시오. 첫 번째 예제와 달리 의도 (문자열 길이 확인)를 절대적으로 명확하게하기 때문에 개인적으로 두 번째 예제에 강력하게 투표하겠습니다.


코드가 불완전합니다.

여기에서 수정했습니다.

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

vs

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

이제 빈 루프가 아니라 현실적인 루프가 있습니다. 무언가를하는 데 1 밀리 초가 걸린다고 생각 해보자.

최신 CPU는 1 밀리 초 안에 많은 일을 할 수 있습니다. 그러나 임의의 하드 드라이브 액세스 또는 데이터베이스 요청과 같은 작업에는 수 밀리 초가 소요되며 이는 현실적인 시나리오이기도합니다.

이제 타이밍을 다시 계산해 보겠습니다.

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

차이점이 보이십니까?


첫째, 함수 호출이 언어 구조에 오버 헤드를 가져 오는 이유를 설명 하는 Artefacto의 답변 을 가리키고 싶습니다 .

둘째, XDebug가 함수 호출의 성능을 크게 저하 시킨다는 사실을 알려 드리고 싶습니다. 따라서 XDebug를 실행하는 경우 복잡한 숫자를 얻을 수 있습니다. 참조 (질문의 두 번째 섹션). 따라서 프로덕션 (XDebug가 설치되어 있지 않은 경우)에서는 그 차이가 훨씬 적습니다. 6x에서 2x로 내려갑니다.

셋째, 측정 가능한 차이가 있더라도이 차이는이 코드가 수백만 번의 반복이있는 타이트한 루프에서 실행되는 경우에만 나타납니다. 일반적인 웹 응용 프로그램에서는 차이를 측정 할 수 없으며 분산의 노이즈가 발생합니다.

넷째, 오늘날 개발 시간은 서버로드보다 훨씬 더 비쌉니다. 개발자는 isset 코드가 수행하는 작업을 이해하는 데 0.5 초만 더 소비하는 것이 CPU 부하를 줄이는 것보다 훨씬 더 비쌉니다. 또한 실제로 차이를 만드는 최적화 (예 : 캐싱)를 적용하면 서버로드를 훨씬 더 잘 절약 할 수 있습니다.


이것은 최신 테스트입니다.

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 :

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

실행 2 :

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

실행 3 :

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

실행 4 :

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

실행 5 :

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


단점은 strlen이 당신의 의도가 무엇인지에 대해 정말로 명확하지만 isset이 전혀 명시 적이 지 않다는 것입니다. 누군가가 당신의 코드를 읽고 당신이 무엇을하는지 이해해야한다면 그것은 그를 괴롭 히고 명확하지 않을 수 있습니다.

당신이 페이스 북을 실행하지 않는 한 나는 strlen이 당신의 서버가 그의 자원의 대부분을 소비하는 곳이 될 것이라고 의심하고 strlen을 계속 사용해야합니다.

방금 테스트 한 strlen이 설정이 훨씬 빠릅니다.

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
   }
}

현대의 ObjectOriented 웹 응용 프로그램에서는 작은 클래스 내에서 작성하는 한 줄을 수백 번 쉽게 실행하여 단일 웹 페이지를 만들 수 있습니다. XDebug를 사용
하여 웹 사이트를 프로파일 링 할 수 있으며 클래스의 각 메서드가 실행되는 횟수에 놀라게 될 수 있습니다. 그런 다음 실제 시나리오에서는 작은 문자열로 작업 할 수있을뿐만 아니라 최대 3MB 이상의 큰 문서로 작업 할 수도 있습니다. 라틴어가 아닌 문자가 포함 된 텍스트를 볼 수도 있습니다. 따라서 결국 처음에는 약간의 성능 손실이 웹 페이지 렌더링에서 서버의 수백 밀리 초를 초래할 수 있습니다.


그래서 저는이 문제에 매우 관심이 있고 문자열이 정말로 비어 있는지 또는 실제로 "0"과 같은 것을 포함하고 있는지 확인하기 위해 4 가지 다른 메소드를 테스트하는 작은 테스트를 작성했습니다.

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 и Культурный комплекс Сиди-Бельот.";

To see really a difference I called each test function several millions of times.

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

Test Results

$ 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

Conclusion

  • The conventional emtpy() Function performs well but fails on strings like "0".
  • The mb_strlen() Function which is necessary to check on texts with non latin characters performs worse on larger texts.
  • The Check $string !== "" performs very well. Even better than the empty() Function.
  • But the best Performance gives the isset($string[0]) Check.

I will definitely have to work over my whole Object Library.

ReferenceURL : https://stackoverflow.com/questions/6955913/isset-vs-strlen-a-fast-clear-string-length-calculation

반응형