webman框架源码修改及性能优化(二)

kaoson

webman-framework/src/App.php几处性能优化(建议官方修改)

不是鸡蛋里挑骨头,webman本身就是出于对性能的追求,所以应要该精于求精,改进任何不太合理的地方。

  1. guessControllerAction(...)这个方法,在foreach循环中用了array_merge,array_merge这个方法性能并不是很好,另一个问题是进行了两次foreach循环,实际只需要一次,看对比图。
    截图

修改后的代码

protected static function parseControllerAction(string $path)
 {
        // $path = str_replace('-', '', $path); //这个方法里注释掉这一行,为了路径更规范,比如some-other,如果不注释掉这行,就变成someother了
        //....
}

protected static function guessControllerAction($pathExplode, $action, $suffix, $classPrefix)
    {
        array_unshift($pathExplode, 'app');
        $map = [];
        $map2 = [];
        foreach ($pathExplode as $index => $section) {
            $tmp = $pathExplode;
            array_splice($tmp, $index + 1, 0, 'controller');
            $map2[] = trim("$classPrefix\\" . implode('\\', $tmp), '\\') . '\\Index';
            $controller = array_pop($tmp);
            if ($controller === 'controller') {
                continue;
            }
            $controller = static::kebab2camel($controller);
            $map[] = trim("$classPrefix\\" . implode('\\', $tmp), '\\') . '\\' . $controller;
        }

        $map = array_merge($map, $map2);

        foreach ($map as $controllerClass) {
            $controllerClass .= $suffix;
            if ($controllerAction = static::getControllerAction($controllerClass, $action)) {
                return $controllerAction;
            }
        }
        return false;
    }

    public static function kebab2camel($name)
    {
        $name = preg_replace_callback('/-([a-z]+)/', function($m) {
            return ucfirst($m[1]);
        }, $name);
        return ucfirst($name);
    }
  1. 【强烈建议官方修改】getController(...)这个方法,两次进行scandir遍历目录和文件,webman可能是考虑了各种奇葩目录和文件命名方式,但规范就是规范,这个限定,规范后代码写起来更简洁,性能更好。
    截图

修改后的getController(...):

protected static function getController(string $controllerClass)
    {
        if (class_exists($controllerClass)) {
            return (new ReflectionClass($controllerClass))->name;
        }

        $basePath = '';
        $file = '';
        if (0 === strpos($controllerClass, 'plugin\\')) {
            $basePath = BASE_PATH . '/plugin';
            $file = $basePath . str_replace('\\', '/', substr($controllerClass, 6)) . '.php';
        } else {
            $basePath = static::$appPath;
            $file = $basePath . str_replace('\\', '/', substr($controllerClass, 3)) . '.php';
        }

        if (is_file($file)) {
            require_once $file;
            if (class_exists($controllerClass, false)) {
                return (new ReflectionClass($controllerClass))->name;
            }
        }

        return false;
    }
1465 3 2
3个评论

walkor

感谢你的建议,不过这里压测发现官方代码还是比你优化后的快1.3倍。
以下是测试程序 (测试时需要将guessControllerAction改成public)

<?php
use Webman\App;

require_once __DIR__ . '/../webman-framework/src/App.php';
require_once  __DIR__ . '/../webman-framework/src/Util.php';

$pathExplode = explode('/', trim('/a/b/c', '/'));
$action = 'index';
$classPrefix = '';
$suffix = '';
$t = microtime(true);
$i = $j = 100000;
for($k=0; $k<$i; $k++) {
    App::guessControllerAction($pathExplode, $action, $suffix, $classPrefix);
}

echo $j/(microtime(true) - $t) , "\n";

官方代码每秒可运行169000左右。
你的代码每秒可运行76000左右。

以上是本地macbook跑的结果,压测可能不严谨,不过webman是常驻内存的框架,第一次加载控制器的时候速度慢0.00000x秒不是很重要,后面请求就直接走内存了,不会再去磁盘找文件了,就没有这层消耗了。

  • kaoson 2023-05-30

    不好意思,没测,仅凭代码判断,回头我测一下。不过我还是认为我这段更合理、更简洁一些,对目录文件命名规范应该有所限定。

  • walkor 2023-05-30

    目录规范
    假设有个/app/api/controller/UserController.php里面有个getSomeData()方法
    目前支持通过这样的url访问
    1、/api/user/getSomeData
    2、/api/user/getsomedata
    3、/api/user/get-some-data

    看起来你的代码只支持1这种方式访问,url里大小写混用,这样很多开发者就会觉得很别扭

  • kaoson 2023-05-30

    我的代码支持这样/api/some-other/get-some-data,some-other严格与目录对应,官方的貌似只能用someother目录,我一向遵循的规范是url一定小写,目录名一定小写,特别讨厌url用驼峰命名方法,也不喜欢url用下划线。

kaoson

@walkor,我跟你的测试差距很大
截图
用我的方式将近快一倍。

  • walkor 2023-05-30

    我试了下,还是官方的快。
    官方使用scandir(),php内部有缓存机制,不会每次去读磁盘。
    你的慢getController里使用了在is_file(),因为文件不存在每次都要查磁盘。如果把is_file()注释掉,就和官方的一样快。
    还是那个结论,第一次加载控制器这里没必要优化,快了0.00000x秒没有意义,后续这个控制器的请求不会有这个查找文件的消耗。

  • kaoson 2023-05-30

    我以为是时间,弄错了,哈哈。关键问题是没有判断static::$appPath,官方代码实际没往下执行了,我的还在判断is_file,加上static::$appPath判断就快了,如果scandir有缓存机制,我的可以改成scandir,但我看了官方代码,scandir使用了两次,实际没有必要,出于严谨,我希望some-other这样的目录名不会被替换成someother,这样会导致我的控制器找不到,更重要的是不便于查找和排错。

kaoson

@walkor,如果定义了static::$appPath,官方的慢不是一星半点,比我的慢30多倍
定义static::$appPath后,
我的3次测试时间分别是:3.3秒,3.0秒,3.3秒
官方的3次测试时间分别是:96.9秒,100.8秒,100.3秒(我的本本风扇都开始响起了)
官方的慢就在于代码有两处sandir,实际执行的还不止两次,因为第一次scandir是在循环中,如果explodes在3个以上,那实际执行scandir的次数就是4次以上,而且,如果目录文件很多的话,官方的会更慢,因为我本地测试还没那么多目录文件,如果正式项目,可能会有几百以上的目录文件。

  • walkor 2023-05-31

    确实,定义static::$appPath后官方的会慢非常多。
    发个pr吧,我合并下,如果哪里有兼容问题我后面再修。

  • walkor 2023-05-31

    linux下有个问题
    app\controller\UserLogin::testUser
    通过url /userlogin/testuser 访问404,应该是大小写问题

  • kaoson 2023-06-01

    url地址有规范限定,app\controller\UserLogin::testUser,应该对应/user-login/test-user

  • kaoson 2023-06-01

    对应/user-login/testuser也可以,因为php方法名不区分大小写,但为了规范,最好是对应/user-login/test-user

  • kaoson 2023-06-01

    url与controller对应就是烤串命名对驼峰命名

  • walkor 2023-06-01

    这样规定死不行。/userlogin/testuser能访问 app\controller\UserLogin::testUser 是很普遍的需求。
    我大概知道为什么官方要scandir目录了,因为需要解决小写url能匹配到正确的目录和文件问题。
    你看下能不能解决,不能的话可能要回滚到之前官方版本

  • kaoson 2023-06-01

    应该可以解决,我改下

  • kaoson 2023-06-01

    已经修改并发了pr,在原来基础上增加了scandir,但性能跟之前相比没太明显差别,完全可以接受。

  • kaoson 2023-06-01

    官方文档最好告诉用户,url最好使用烤串命名法,对应目录名也用烤串命名,文件名用大驼峰命名,action用小驼峰命名

  • kaoson 2023-06-01

    等等,发现问题

  • kaoson 2023-06-01

    重新pr了,在原来基础上增加了scandir,不过对性能无明显影响。

kaoson

520
积分
0
获赞数
0
粉丝数
2023-05-30 加入
×
🔝