• #define宏定义的min与max函数的速度问题


    引言


    最近几天在写普通平衡树这一题时,我没有使用我平常经常使用的algorithm中的min与max函数(平常使用主要是因为懒得手打这样使用比较标准),而是使用了#define宏定义的min与max函数,我认为这样应该能加快一些速度,所以在我的代码疯狂TLE时我并没有注意到这一点。在我接近debug到崩溃时,我把所有的预处理命令(本来这里想写头文件后来发现define的名字并不叫头文件)都重打了一遍,再次提交时,发现竟然通过了这道题。我观察了这些预处理命令,发现他们唯一的不同就是我把define宏定义函数改成了algorithm库。在我的一脸蒙蔽之中,我测试了各种min,max函数的性能 。

    这是我在这一题中会使用min/max函数的函数:

    int lower(int now,int x) {
        if(!now) return -2147483646;
        if(bt[now].num<x) return max(bt[now].num,lower(bt[now].s[1],x));
        return lower(bt[now].s[0],x);
    }
    
    int upper(int now,int x) {
        if(!now) return 2147483647;
        if(bt[now].num>x) return min(bt[now].num,upper(bt[now].s[0],x));
        return upper(bt[now].s[1],x);
    }
    

    define宏定义的函数为:

    #define max(a,b) ((a) > (b) ? (a) : (b))
    #define min(a,b) ((a) < (b) ? (a) : (b))
    

    测试


    注:测试在洛谷在线IDE(C++无O2优化)上进行
    我写了下面几行代码来测试性能,min和max交替进行。

    int main() {
        int n=1e7;
        int minx=n,maxx=0;
        for(int i=1;i<=n;++i) {
            minx=min(minx,n-i);
            maxx=max(maxx,i);
        }
        return 0;
    }
    
    algorithm库 define宏定义函数 手敲函数(非内连,内部使用三目运算符)
    60-80ms 20-30ms 60ms

    结果显示宏定义函数明显比其他的要快,那为什么我的程序会因为宏定义函数TLE呢?

    考虑到我写的题中的min/max中有函数作为参数,所以我又写了下面一个程序,来测试min/max中有函数时的性能。

    int n=1e7;
    int test(int i,int type) {
        return type==0?n-i:i;
    }
    int main() {
        int minx=n,maxx=0;
        for(int i=1;i<=n;++i) {
            minx=min(minx,test(i,0));
            maxx=max(maxx,test(i,1));
        }
        return 0;
    }
    
    algorithm库 define宏定义函数 手敲函数
    92ms 100ms 88ms

    在我多次测试后,发现define宏定义函数总是最慢的。但是一次慢几ms,对于n≤100000的普通平衡树来说应该也不会让本可以AC的代码TLE。考虑到普通平衡树一题中我在查询前驱/后继时的max/min中使用了递归函数,我再次写了一段代码进行测试。

    int n=25;
    int test(int i,int type,int I) {
        if(!i) return type==0?n-I:I;
        return max(type==0?n-i:i,test(i-1,type,I));
    }
    int main() {
        int minx=n,maxx=0;
        for(int i=1;i<=n;++i) {
            minx=min(minx,test(i,0,i));
            maxx=max(maxx,test(i,1,i));
        }
        return 0;
    }
    

    由于n=1e7时对于define运行时间过长,所以我改成了25(这差距好像有点大)。

    algorithm库 define宏定义函数 手敲函数
    0ms 1020ms 0ms

    这样的情况下差距就十分明显了,我也知道了为什么我的代码会TLE,但是为什么会导致这样呢?我找到了define的工作原理。

    资料


    我翻阅了 C++ Primer,3e ,在其中找到了答案。(C++ Primer,5e 好像已经把宏定义函数这一部分删除了)

    有时候强类型语言对于实现相对简单的函数似乎是个障碍,例如虽下面
    的函数 min()的算法很简单,但是强类型语言要求我们为所有希望比较的
    类型都实现一个实例
    
    int min( int a, int b ) {
        return a < b ? a : b;
    }
    double min( double a, double b ) {
        return a < b ? a : b;
    }
    
    有一种方法可替代这种为每个 min()实例都显式定义一个函数的方法,
    这种方法很有吸引力,但是也很危险,那就是用预处理器的宏扩展设
    施例如
    
     #define min(a,b) ((a) < (b) ? (a) : (b))
    
    虽然该定义对于简单的 min()调用都能正常工作,如
    
    min(10,20);
    min(10.0,20.0);
    
    但是在复杂调用下它的行为是不可预期的,这是因为它的机制并不像函数
    调用那样工作,只是简单地提供参数的替换,结果是它的两个参数值都被
    计算两次,一次是在a和b的测试中,另一次是在宏的返回值被计算期间,
    例如
    
    #include <iostream>
    #define min(a,b) ((a) < (b) ? (a) : (b))
    const int size = 10;
    int ia[size];
    int main() {
        int elem_cnt = 0;
        int *p = &ia[0];
        // 计数数组元素的个数
        while ( min(p++,&ia[size]) != &ia[size] )
            ++elem_cnt;
        cout << "elem_cnt : " << elem_cnt
        << "	expecting: " << size << endl;
        return 0;
    }
    
    这个程序给出了计算整型数组ia的元素个数的一种明显绕弯的的方法。
    min()的宏扩展在这种情况下会失败,因为应用在指针实参p上的后置
    递增操作随每次扩展而被应用了两次,执行该程序的结果是下面不正
    确的计算结果
    elem_cnt:5 expecting:10
    

    其中

    它的两个参数值都被计算两次,一次是在a和b的测试中,另一次是在宏的返回值被计算期间。

    解释了原因。参数值会计算两次,如果递归函数在min与max的define宏定义函数下调用了自己是非常可怕的,它会增加指数级别的时间复杂度。define宏定义因为不会真正调用函数的特性在一定情况下确实能增加速度,然而如果min与max的define宏定义函数的“实参”(其实它并不能叫做实参)中出现了一个复杂的计算的话,它会进行两次计算,这大大拖慢了程序的速度。所以我建议如果在使用define宏定义函数时,如果传值中出现了一个会进行时间较长的计算的函数的话,应该这样使用:

    int t=calc();  //假如calc()是一个需要经过大量计算的函数
    ans=min(t,ans);
    

    这样会大大加快速度(或者除非卡常时否则干脆别用了)

    经过测试,该代码

    #define min(a,b) ((a) < (b) ? (a) : (b))
    #define max(a,b) ((a) > (b) ? (a) : (b))
    int n=25;
    int test(int i,int type,int I) {
        if(!i) return type==0?n-I:I;
        int t=test(i-1,type,I);  //防止重复计算
        return max(type==0?n-i:i,t);
    }
    int main() {
        int minx=n,maxx=0;
        for(int i=1;i<=n;++i) {
            minx=min(minx,test(i,0,i));
            maxx=max(maxx,test(i,1,i));
        }
        return 0;
    }
    

    速度已经下降到了0ms。

  • 相关阅读:
    linux常用命令(4)rm命令
    Apache Commons 工具类
    Apache Commons 工具类介绍及简单使用
    linux常用命令(3)mkdir命令
    linux常用命令(2)pwd命令
    linux常用命令(1)cd命令
    小程序调用方法
    php用json_encode中文问题
    基于thinkphp的RBAC权限控制
    thinkphp获取ip地址及位置信息
  • 原文地址:https://www.cnblogs.com/Sleepp/p/9808270.html
Copyright © 2020-2023  润新知