zoukankan      html  css  js  c++  java
  • 函数优化之尾调用实践(PHP)

    参考维基百科对“尾调用”定义:

         在计算机科学里,尾调用是指一个函数里的最后一个动作是一个函数调用的情形:即这个调用的返回值直接被当前函数返回的情形。这种情形下称该调用位置为尾位置。若这个函数在尾位置调用本身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归,是递归的一种特殊情形。尾调用不一定是递归调用,但是尾递归特别有用,也比较容易实现。

    在程序运行时,计算机会为应用程序分配一定的内存空间;应用程序则会自行分配所获得的内存空间,其中一部分被用于记录程序中正在调用的各个函数的运行情况,这就是函数的调用栈。常规的函数调用总是会在调用栈最上层添加一个新的堆栈帧(stack frame,也翻译为“栈帧”或简称为“帧”),这个过程被称作“入栈”或“压栈”(意即把新的帧压在栈顶)。当函数的调用层数非常多时,调用栈会消耗不少内存,甚至会撑爆内存空间(栈溢出[1],造成程序严重卡顿或意外崩溃。尾调用的调用栈则特别易于优化,从而可减少内存空间的使用,也能提高运行速度。[1]其中,对尾递归情形的优化效果最为明显,尤其是递归算法非常复杂的情形。[1]

    一般来说,尾调用消除是可选的,可以用,也可以不用。然而,在函数编程语言中,语言标准通常会要求编译器或运行平台实现尾调用消除。这让程序员可以用递归取代循环而不丧失性能。

    这里还是以php语言来做示例演示。

    factorial.php

    [root@guangzhou tmp_dir]# cat factorial.php 
    <?php
    
    function factorial($n) {
        if ($n == 0) {
            return 1;
        }
    
        return factorial($n - 1) * $n;
    }
    
    $start = microtime(true);
    
    echo '内存使用量1:' . memory_get_usage() . PHP_EOL;
    
    echo '运行结果:' . factorial(80000) . PHP_EOL;
    
    echo '运行耗时:' . (microtime(true) - $start)  . 's' . PHP_EOL;
    
    echo '内存使用量2::' . memory_get_usage() . PHP_EOL;
    
    echo '内存使用峰值:' . memory_get_peak_usage() . PHP_EOL;
    [root@guangzhou tmp_dir]# php factorial.php 
    内存使用量1:700840
    运行结果:INF
    运行耗时:0.010078907012939s
    内存使用量2::700872
    内存使用峰值:13283784

    factorial2.php

    [root@guangzhou tmp_dir]# cat factorial2.php 
    <?php
    
    function factorial($n, $total=1){
       if($n == 1)
       {
           return $total;
       }else{
           return factorial($n-1, $total*$n);
       }
    }
    
    $start = microtime(true);
    
    echo '内存使用量1:' . memory_get_usage() . PHP_EOL;
    
    echo '运行结果:' . factorial(80000) . PHP_EOL;
    
    echo '运行耗时:' . (microtime(true) - $start)  . 's' . PHP_EOL;
    
    echo '内存使用量2::' . memory_get_usage() . PHP_EOL;
    
    echo '内存使用峰值:' . memory_get_peak_usage() . PHP_EOL;
    [root@guangzhou tmp_dir]# php factorial2.php 
    内存使用量1:701096
    运行结果: INF
    运行耗时:0.01151704788208s
    内存使用量2::701128
    内存使用峰值:14594760

    上面可见factorial2.php峰值内存使用14mb左右。

    后面将$n=80000改成$n=800000,报错内存溢出,后面查询网上资料,发现php的尾调用需要换种方式来编写。

    这里直接运行$n=800000000

    factorial3.php

    [root@guangzhou tmp_dir]# cat factorial3.php 
    <?php
    
    function factorial($n, $accumulator = 1) {
        if ($n == 0) {
            return $accumulator;
        }
    
        return function() use($n, $accumulator) {
            return factorial($n - 1, $accumulator * $n);
        };
    }
    
    function trampoline($callback, $params) {
        $result = call_user_func_array($callback, $params);
    
        while (is_callable($result)) {
            $result = $result();
        }
    
        return $result;
    }
    
    $start = time();
    
    echo '内存使用量1:' . memory_get_usage() . PHP_EOL;
    
    echo '运行结果:' . trampoline('factorial', array(80000000)) . PHP_EOL;
    
    echo '运行耗时:' . (time() - $start)  . 's' . PHP_EOL;
    
    echo '内存使用量2::' . memory_get_usage() . PHP_EOL;
    
    echo '内存使用峰值:' . memory_get_peak_usage() . PHP_EOL;
    [root@guangzhou tmp_dir]# php factorial3.php 
    内存使用量1:703104
    运行结果:INF
    运行耗时:20s
    内存使用量2::703136
    内存使用峰值:807304

    可见内存使用不多,峰值只是增加100kb左右。

  • 相关阅读:
    1,巡检脚本pexpect
    Cisco胖AP配置上网
    阿里云服务器更换密钥后,无论以何种远程连接方式都连接不上
    [PAT乙级题解]——A+B和C
    研究ThreadLocal类
    Java的反射机制
    volatile浅析
    Java 对称数据加密AES
    Java使用非对称数据加密RSA
    练习-登陆接口
  • 原文地址:https://www.cnblogs.com/wscsq789/p/13667411.html
Copyright © 2011-2022 走看看