zoukankan      html  css  js  c++  java
  • 代码中的魔鬼细节

    版权声明:本文为博主原创文章。未经博主同意不得转载。 https://blog.csdn.net/dizuo/article/details/28910517

    软件开发最关心的三个指标:性能、内存、程序稳定性三方面。本文总结一下近期项目扫尾工作中的一些遭遇:


    使用正确的哈希函数

    道路的路况绘制,道路的颜色由三个ID唯一确定,他们存储在一个哈希表中。


    上图是两种哈希函数的性能对照。

    badHashFunction的结果为蓝色,goodHashFunction的结果为红色曲线。

    使用坏的哈希函数,运行DJB_hash的结果冲突可能性十分大。因此哈希的平均查找次数很大,在性能很好的机器上拖动时也有明显的卡顿现象。

    优化哈希函数,将三个ID的全部位数拼接成一个数字串。然后传入DJB_hash结果十分好,性能得到质的提升。

    static unsigned int DJB_hash(int* buffer, int len) 
    {
    	unsigned int hash = 5381;
    	int i = 0;
    
    	while (i < len) {
    		hash += (hash << 5) + buffer[i++];
    	}
    
    	return (hash & 0x7FFFFFFF);
    }
    
    static unsigned int badHashFunction(const void* key)
    {
    	rtic_t* ptr = (rtic_t*)key;
    
    	int buffer[3];
    	buffer[0] = ptr->mapId - RTIC_MIN_MAPID;
    	buffer[1] = ptr->kind;
    	buffer[2] = ptr->middle;
    	
    	return DJB_hash(buffer, 3);
    }
    static unsigned int goodHashFunction(const void* key)
    {
    	rtic_t* ptr = (rtic_t*)key;
    
        const int SIZE = 20;
    	int buffer[SIZE] = {0};
    
        int len = 0;
    
        int v = ptr->mapId;
        while (v && len < SIZE)
        {
            buffer[len++] = v % 10;
            v /= 10;
        }
        
        v = ptr->kind;
        while (v && len < SIZE)
        {
            buffer[len++] = v%10;
            v /= 10;
        }
    
        v = ptr->middle;
        while (v && len < SIZE)
        {
            buffer[len++] = v%10;
            v /= 10;
        }
    	
    	return DJB_hash(buffer, len);
    }
    


    慎重使用vector

    Vector的优点就是动态增长,使用起来很方便,仅仅管不断地push_back。不用关心内存添加的细节。Vector略微有经验的都知道,push_back之前应该先reserve。

    这么做效率更高。

    近期查我们项目的样式配置模块。突然发现内存占用十分厉害,前后情况例如以下:

    样式载入前内存情况:


    样式载入后内存情况:


    单纯样式模块占用600K

    一路追查下去,结果哭笑不得。我们自己实现了一套C风格的Vector。里面存储void*指针从而实现泛型。Vector内部reserve函数实现很之坑爹,代码片段例如以下,每次reserve结果vector中至少有256个指针。

    void TXVector::reserve(TXUINT32 capacity)
    {
    	if (capacity <= _capacity)
    	{
    		return;
    	}
        
    	_capacity = capacity * 2;
    	if (_capacity < 256)
    	{
    		_capacity = 256;
    	}
    


    上图为样式配置模块的数据结构,是个二维数组。当时由于赶项目进度,regionStyleList的每一个成员内部有一个vector。然而vector仅仅保存1-3个指针成员。RegionStyleList的数量很大,所以导致内存浪费十分严重。

    优化时直接KISS(keep it simple and stupid)化,採用最简单的二维数组优化后,结果很明显,内存降到82K


    注意空指针訪问问题

    背景:地图切换数据后,城市的路况映射表达到500k左右。于是我们对数组进行了延迟创建处理。就是由于这个优化引入了一个十分隐晦的BUG,灰度上线后IOS、Android两个平台都收到不同数量的crash日志。

    以下是android的日志:

    ********** Crash dump: **********

    Build fingerprint: 'samsung/t03gzs/t03g:4.1.2/JZO54K/N7100ZSDMA6:user/release-keys'

    pid: 10190, tid: 10190  >>> com.XX.map <<<

    signal 11 (SIGSEGV), fault addr 00000000

    Stack frame #00  pc 0000e270  /system/lib/libc.so (memcpy)

    日志中黄色部分标识空指针訪问越界,Crash代码行指向memcpy这一行:


    这个空指针crash正常情况下不会发生,例如以下图我们升级由于要兼容旧数据。所以程序中有两种路径:


    每条竖直路径表示我们预期的正常途径:全量更新时创建数组,增量更新时刷新数组。

    红色箭头路径表示非法路径:当A格式全量更新创建A格式数组。然后下次增量更新时跳至了B的路径去刷新B的数组。此时B的数组为空。从而空指针CRASH。

    界面层的某种操作会触发红色路径。所以空指针crash带有随机性色彩。

    通过不断讨论我们终于归纳出了BUG必现的触发途径。当然了空指针BUG解决起来很easy。


    注意对程序边界条件处理

    引擎重构以后,两个平台多了一种crash日志,android内容:

    Build fingerprint: 'samsung/m0zs/m0:4.1.2/JZO54K/I9300ZSEMC1:user/release-keys'

    pid: 26598, tid: 26598  >>> com.XXX.map <<<

    signal 11 (SIGSEGV), fault addr 04000000

    Stack frame #00  pc 0000e264  /system/lib/libc.so (memcpy)

    Stack frame #01  pc 0001a780  /data/data/com.XXX.map/lib/libengine.so: Routine getScanEdges in jni/src/gc/SEA/SubPolygon.cpp:69

    相应的代码行:


    POD类型对象拷贝调用的是memcpy。看到这个结果我们怀疑是某种极端的面数据导致了引擎的crash,于是乎大家雄心勃勃,一起讨论了一个方案:写一个benchmark程序在内存中处理全国全部城市数据。可是跑了好几天也没办法复现,时间一点点过去。大家的意志力逐渐被消磨殆尽,crash日志还是越涨越多。

    终于一个经验丰富的高工终于了问题的可能原因:SubPolygon没有特殊处理顶点为0的情况

    原因是生成瓦片中。多边形使用软件方法裁剪时可能会生成顶点为0的多边形,然后进行绘制。

    举个样例:int* ptr = new int[0]; ptr返回指针是不确定的。可能为空也可能不为空。malloc(0)返回的指针除了能够传入free函数之外不建议有其它操作,直接訪问内存会出现随机性的结果。

    Linux上malloc(0)行为:http://www.cnblogs.com/xiaowenhu/p/3222709.html

    教训:代码中添加一些合法性推断代码。覆盖各种边界处逻辑。它们绝对不会是Dead Code,由于它们什么时候起作用,你很难想象到或者根本没办法预料到。

     

查看全文
  • 相关阅读:
    迅雷亲历面试经过,笔试+上机+面试(完整)
    Flash Player安全高级攻略
    EBS查看Report程式所挂在的. 报表名. 组. 责任
    ORACLE常用后台表查询
    Ap_Aging_Report SQL月底结账使用
    2012年最新会计科目表
    如何用sql实现AP_payments中应付余额与GL_balance对应科目余额相同
    SQL应收帐款帐龄报表(AR_Aging_Reporting)
    Navigator: Disable Multiform
    GL: 访问权限集
  • 原文地址:https://www.cnblogs.com/ldxsuanfa/p/10812511.html
  • Copyright © 2011-2022 走看看