iOS/Objective-C - NSMutableDictionary 撑爆了内存? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xingheng
V2EX    Objective-C

iOS/Objective-C - NSMutableDictionary 撑爆了内存?

  •  2
     
  •   xingheng 2019-11-07 13:45:54 +08:00 6204 次点击
    这是一个创建于 2165 天前的主题,其中的信息可能已经有所发展或是发生改变。

    在一个面试被问到一个问题:在一个核心入口函数处统计每一个调用方的调用次数,入口函数知道每一个调用方的函数名(NSSting *),问题是要怎么统计。

    我回答构建一个 NSMutableDictionary<NSString *, NSNumber *>的 static 对象应该就可以解决问题了,用 dispatch_once 保证初始化的线程安全。但是被追问这样的话 NSMutableDictionary 的内存会撑爆,调用量会非常大,怎么优化?

    我没答上来,事后也没想明白。内存增大应该是 NSString 的原因,NSNumber 毕竟只是值的变化,能占多少内存。NSMutableDictionary 确实会对 key 进行 copy,我在想什么量级的 NSString 会撑爆内存。

    有想法?

    21 条回复    2019-11-08 10:59:02 +08:00
    w99wen
        1
    w99wen  
       2019-11-07 15:36:16 +08:00
    你这个有两个问题,
    第一:多线程你这个方法不能保证多线程安全,加锁影响效率。
    第二:内存占用吃不消。

    你的错误想法:
    内存释放的理解有根本错误,再看看书吧。

    推荐:
    切到串型队列,存储一部分数据后写盘,或者持续写盘。

    现在还有问这个的。有意思。
    w99wen
        2
    w99wen  
       2019-11-07 15:39:19 +08:00
    如果问你的人就只能反问成这样,他水平也一般啊。
    ai277014717
        3
    ai277014717  
       2019-11-07 16:38:41 +08:00
    感觉 NSNumber 拆装箱应该也考虑进去
    kera0a
        4
    kera0a  
       2019-11-07 16:46:40 +08:00 via iPhone
    是我没看明白吗?
    一个调用方调用 1 次和 100w 次,占用的内存不是一样的嘛。
    一个程序能用几个调用方啊,这种场景怎么着也和内存无关吧
    wutiantong
        5
    wutiantong  
       2019-11-07 16:47:09 +08:00
    不懂为啥会撑爆内存,怀疑面试官的水平。
    xingheng
        6
    xingheng  
    OP
       2019-11-07 17:03:04 +08:00
    @w99wen 确实想过串行的方法,把初始化和 dict 的操作都挪到串行队列里面去。主要问题还是内存,以我的理解,NSMutableDictionary 在 setValue:forKey:的时候确实会对 NSString \*key 进行 copy,但是这个 copy 不会产生新的内存分配(假定是 inmutable string ),只是把原有的 imutable string's retain count 加 1。上面说 NSMutableDictionary 的撑爆内存其实是指 NSMutableDictionary 持有的 NSString 把内存撑爆了。

    这个理解有错误吗?请指正。

    写 io 的话也想过,一旦 NSMutableDictionary 的数量级到了某个设定量就写文件,这样的话就还需要再次汇总结果了,可能不符合对方的预期。

    这个问题来自蚂蚁金服的面试官。
    xingheng
        7
    xingheng  
    OP
       2019-11-07 17:08:03 +08:00
    @kera0a 对方明确说明了调用方数量确实会有很大,我猜测还是 NSString 作为 key 的内存占用量会很大。

    也不排除因为他们的 app 在正常运行的时候其他需求上已经占用大量内存了,只是针对或者设计了这样一个问题来优化这个统计结果。
    xingheng
        8
    xingheng  
    OP
       2019-11-07 17:10:58 +08:00
    @ai277014717 NSNumber 拆装箱过程中可能会产生局部变量,内存会在每次退出函数的时候就被释放了,我觉得不至于影响 NSMutableDictionary 所持有的内存。
    kera0a
        9
    kera0a  
       2019-11-07 17:35:07 +08:00 via iPhone
    @xingheng 我觉得出题者没考虑实际情况啊
    一个方法能有 100 个调用位置就算它业务复杂了,算下来这个字典顶多 100 个 String + 100 个 number
    决定 这个 Dict 大小的只有 key 有多少个,很显然 Key 不可能很多
    w99wen
        10
    w99wen  
       2019-11-07 17:44:39 +08:00
    api 的数量会很大的,内存占用就不能小看了。
    比如手淘 /支付宝之类的超大 app,底层做数据统计,整个 app 的 api 绝对是很恐怖的存在。
    加上有的 api 是很长的,这个内存要求没问题的。

    举个例子,你见过 6000 个会话的用户吗?
    我在统计后台看到过。
    w99wen
        11
    w99wen  
       2019-11-07 17:48:05 +08:00
    比如说,你 hook 的 objc_msgsend,统计整个 app 的 api 调用,包括系统底层的调用。那你这个 api 的数量,肯定不能在存内存了。也不能在当前线程操作。
    ai277014717
        12
    ai277014717  
       2019-11-07 18:46:25 +08:00
    @xingheng 并不是指内存,而是当遇到这种问题性能也应该考虑进去。
    ai277014717
        13
    ai277014717  
       2019-11-07 18:48:53 +08:00
    @xingheng 函数退出时机并不保证会释放内存 参见 autoreleasepool
    luopengfei14
        14
    luopengfei14  
       2019-11-07 20:58:02 +08:00 via iPhone
    大佬好多,曾经的菜鸡 iOSer 觉得 iOS 开发已经配不上这么深入的研究了。不喜勿喷…
    hoyixi
        15
    hoyixi  
       2019-11-07 21:08:38 +08:00
    这玩意的统计,难道不该实时传给服务器 or 缓存到本地到了一定条件同步给服务器吗,难道要常驻在内存里?
    xingheng
        16
    xingheng  
    OP
       2019-11-07 21:46:04 +08:00
    简单写一下目前我能想的代码结构再讨论吧

    ```

    void core_func(NSString *caller)
    {
    static NSMutableDictionary<NSString *, NSNumber *> *dict;
    static dispatch_queue_t serialQueue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    dict = [NSMutableDictionary new];
    serialQueue = dispatch_queue_create("initializer.serial.queue", DISPATCH_QUEUE_SERIAL);
    });

    dispatch_async(serialQueue, ^{
    if (dict[caller]) {
    dict[caller] = @([dict[caller] unsignedIntegerValue] + 1);
    } else {
    dict[caller] = @1;
    }

    if (dict.count > 1000) {
    NSString *url = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
    NSString *filename = [NSString stringWithFormat:@"stastics-data-%.3f", NSDate.date.timeIntervalSince1970];

    url = [url stringByAppendingPathComponent:filename];

    if ([dict writeToFile:url atomically:YES]) {
    [dict removeAllObjects];
    }
    }
    });
    }

    ```
    xingheng
        17
    xingheng  
    OP
       2019-11-07 22:03:33 +08:00
    @ai277014717 以我的理解,只有给对象发送了 autorelease 消息的对象才会在 autoreleasepool 闭合的时候 release,其他对象还是一直存在的。ARC 下,上面的 url, filename 可以算是 autorelease 对象。
    我印象中以前有看过关于 dispatch queue 在执行的时候外围其实已经包了一个 @autoreleasepool{ },这样的话我觉得 autoreleased 对象并不能对内存构成威胁。

    请指正。
    xingheng
        18
    xingheng  
    OP
       2019-11-07 22:07:43 +08:00
    @hoyixi 那种存服务器的统计以前我还真写过,就是先写内存然后批量发到服务器,发送失败就临时写文件。但是我觉得面试官在这里应该不是问的一个设计上的问题,还是语言级的内存管理问题。
    samlee123
        19
    samlee123  
       2019-11-08 09:33:42 +08:00
    确实会造成内存暴增,他问的就是 hook msgsend 然后做统计吧 ,调用方法需要开辟方法栈,评论里居然还有人质疑面试官水平。。。。。。
    xingheng
        20
    xingheng  
    OP
       2019-11-08 10:3:11 +08:00
    @samlee123 抱歉,是我没有描述清楚。确定不是 hook msgsend,面试官明确说了被调用方知道是谁调用了自己,参见#16 的示例代码。
    ai277014717
        21
    ai277014717  
       2019-11-08 10:59:02 +08:00
    @xingheng 除了个别 init copy new 等方法外一般默认都是 autorelease 对象 例如 numberWithInt:
    dispatch_queue 中确实出现了 autoreleasePool
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2404 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 01:16 PVG 09:16 LAX 18:16 JFK 21:16
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86