경로 패턴의 정규식 개선(Teeny PHP 경로 시스템)

16086 단어 phpphp7php5php8
제가 이미 제시한 것처럼 Teeny는 매우 가볍고 작은 경로에 대한 저의 제안입니다Teeny, a route system for PHP

그러나 여기 사이트의 동료는 주로 그 작업을 줄이기 위해 정규식 그룹화를 사용하여 시스템을 개선할 것을 제안했습니다.

그렇게 간단하지 않았습니다. 이 경우 인덱스 작업이 더 쉬울 것 같았기 때문에 콜백 패턴으로 경로를 격리하여 접근 방식을 변경해야 했습니다.

또 다른 상황은 그룹 이름이 경로 시스템의 사용자에 의해 지정되고 자동이 아니기 때문에 중복 이름을 가진 그룹이 충돌하지 않도록 주의해야 했기 때문에 정규식에서 (?J)를 사용했습니다. 사용 예:

^((?J)(/foo/(?P<user>.*?)/(?P<test>\d+))|(/bar/(?P<user>.*?)/(?P<test>\d+))|(/baz/(?P<name>.*?)/(?P<id>\d+)))$


이 예에서 정규식은 다음과 일치할 수 있습니다.
  • /foo/username/10
  • /bar/username/10
  • /baz/username/10

  • 이 테스트를 위해 세 개의 매우 간단한 스크립트를 만들었습니다. 이 경우에 대한 모든 개체 지향 및 고급 방법론이 과장된 것처럼 보였으므로 스크립트는 매우 간단합니다.

    경로 생성.php




    <?php
    
    $patterns = array(
        'alnum' => '[\da-zA-Z]+',
        'alpha' => '[a-zA-Z]+',
        'decimal' => '\d+\.\d+',
        'num' => '\d+',
        'noslash' => '[^\/]+',
        'nospace' => '\S+',
        'uuid' => '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}',
        'version' => '\d+\.\d+(\.\d+(-[\da-zA-Z]+(\.[\da-zA-Z]+)*(\+[\da-zA-Z]+(\.[\da-zA-Z]+)*)?)?)?'
    );
    
    $paramRoutes = array();
    $paramCallbacks = array();
    
    for ($i = 0; $i < 300; ++$i) { 
        $paramRoutes[] = "/{$i}/<user>/<test:num>";
        $paramCallbacks[] = "callback_{$i}";
    }
    
    $paramRoutes[] = '/foo/<name>/<id:num>';
    $paramCallbacks[] = 'callback_success1';
    
    $paramRoutes[] = '/aaa/bbb/ccc';
    $paramCallbacks[] = 'callback_success2';
    
    // To simulate $_SERVER['PATH_INFO']
    $pathinfo = '/foo/bar/1234';
    


    위의 스크립트와 같이 원하는 경로는 끝에서 두 번째, 즉 첫 번째 경로와 비교하여 획득해야 하는 루프에서 테스트를 위한 300개의 스크립트 경로도 생성됩니다.

    ungroup-routes.php



    이 스크립트는 오늘날 Teeny가 이미 하고 있는 것과 유사하게 작동합니다.

    <?php
    
    require 'generate-routes.php';
    
    $start = microtime(true);
    
    foreach ($paramRoutes as $index => $value) {
        $pRegex = preg_replace($getParams, '(?P<$1><$3>)', $value);
        $pRegex = str_replace('<>)', '.*?)', $pRegex);
    
        foreach ($patterns as $pattern => $regex) {
            $pRegex = str_replace('<' . $pattern . '>)', $regex . ')', $pRegex);
        }
    
        if (preg_match('#^(' . $pRegex . ')$#', $pathinfo, $matches)) {
            foreach ($matches as $key => $value) {
                if ($value === '' || is_int($key)) {
                    unset($matches[$key]);
                }
            }
    
            var_dump($matches);
    
            break;
        }
    }
    
    echo round(microtime(true) - $start, 6), "s\n";
    


    그룹 경로.php



    이 스크립트는 최적화 제안입니다.

    <?php
    
    require 'generate-routes.php';
    
    $start = microtime(true);
    
    // Max regex test
    $limit = 15;
    
    for ($i = 0, $j = count($paramRoutes); $i < $j; $i += $limit) { 
        $slice = array_slice($paramRoutes, $i, $limit);
    
        $pRegex = implode(')|(', $slice);
    
        $pRegex = preg_replace($getParams, '(?P<$1><$3>)', $pRegex);
        $pRegex = str_replace('<>)', '.*?)', $pRegex);
    
        foreach ($patterns as $pattern => $regex) {
            $pRegex = str_replace('<' . $pattern . '>)', $regex . ')', $pRegex);
        }
    
        if (preg_match('#^((?J)(' . $pRegex . '))$#', $pathinfo, $matches)) {
            foreach ($matches as $key => $value) {
                if ($value === '' || is_int($key)) {
                    unset($matches[$key]);
                }
            }
    
            var_dump($matches);
            break;
        }
    }
    
    echo round(microtime(true) - $start, 6), "ms\n";
    


    테스트 수행



    많은 테스트가 환경에서 직접 수행되지만 거의 동시에 발생하는 여러 HTTP 요청에 대한 영향을 확인해야 했기 때문에 ApacheBench를 사용하기로 결정했습니다.

    첫 번째 테스트는 그룹 해제를 실행하는 것이었습니다.

    ab -n 1000 -c 10 http://localhost/benchmark-routes/ungroup-routes.php
    


    다음과 같은 결과가 얻어졌습니다.

    Requests per second:    1566.68 [#/sec] (mean)
    Time per request:       6.383 [ms] (mean)
    Time per request:       0.638 [ms] (mean, across all concurrent requests)
    Transfer rate:          472.54 [Kbytes/sec] received
    


    첫 번째 테스트는 그룹 해제를 실행하는 것이었습니다.

    ab -n 1000 -c 10 http://localhost/benchmark-routes/group-routes.php
    


    다음과 같은 결과가 얻어졌습니다.

    Requests per second:    3995.64 [#/sec] (mean)
    Time per request:       2.503 [ms] (mean)
    Time per request:       0.250 [ms] (mean, across all concurrent requests)
    Transfer rate:          1209.19 [Kbytes/sec] received
    


    이와 같은 테스트에서는 초당 평균적으로 얼마나 많은 작업을 수행할 수 있는지 평가됩니다. 즉, 초당 HTTP 요청이 많을수록 좋습니다. 두 번째 다른 테스트와 비교하여 차이가 최소이면 정말 과잉일 수 있습니다.

    테스트에 따르면 약 2400(대략 테스트는 항상 다름) 더 많은 HTTP 요청(1초 동안)을 받았습니다.

    그러나 이 명백한 개선이 경로가 적은 시스템에 해를 끼치지 않도록 해야 했습니다. 많은 요청에 잘 작동하는 것이 때로는 몇 가지 요청에 해로울 수 있기 때문입니다. 그래서 group-routes.php의 경로 수를 30으로 줄이고 테스트를 다시 실행했습니다.

    ungroup-routes.php:

    Requests per second:    5361.82 [#/sec] (mean)
    Time per request:       1.865 [ms] (mean)
    Time per request:       0.187 [ms] (mean, across all concurrent requests)
    Transfer rate:          1617.39 [Kbytes/sec] received
    


    그룹 경로.php:

    Requests per second:    5361.82 [#/sec] (mean)
    Time per request:       1.865 [ms] (mean)
    Time per request:       0.187 [ms] (mean, across all concurrent requests)
    Transfer rate:          1617.39 [Kbytes/sec] received
    


    그리고 놀랍게도 새로운 구조는 거의 동일한 성능을 가진 경로가 거의 없는 시스템에 영향을 미치지 않았습니다.

    새 버전은 이제 https://github.com/inphinit/teeny/releases/tag/0.2.8 또는 작곡가를 사용하여 다운로드할 수 있습니다.

    composer create-project inphinit/teeny <project name>
    


    감사



    이 개선에 도움이 된 것이 그의 제안이었기 때문에 감사합니다.

    좋은 웹페이지 즐겨찾기