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

2019-11-07 13:45:54 +08:00
 xingheng

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

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

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

有想法?

5582 次点击
所在节点   Objective-C
21 条回复
w99wen
2019-11-07 15:36:16 +08:00
你这个有两个问题,
第一:多线程你这个方法不能保证多线程安全,加锁影响效率。
第二:内存占用吃不消。

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

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

现在还有问这个的。有意思。
w99wen
2019-11-07 15:39:19 +08:00
如果问你的人就只能反问成这样,他水平也一般啊。
ai277014717
2019-11-07 16:38:41 +08:00
感觉 NSNumber 拆装箱应该也考虑进去
kera0a
2019-11-07 16:46:40 +08:00
是我没看明白吗?
一个调用方调用 1 次和 100w 次,占用的内存不是一样的嘛。
一个程序能用几个调用方啊,这种场景怎么着也和内存无关吧
wutiantong
2019-11-07 16:47:09 +08:00
不懂为啥会撑爆内存,怀疑面试官的水平。
xingheng
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
2019-11-07 17:08:03 +08:00
@kera0a 对方明确说明了调用方数量确实会有很大,我猜测还是 NSString 作为 key 的内存占用量会很大。

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

举个例子,你见过 6000 个会话的用户吗?
我在统计后台看到过。
w99wen
2019-11-07 17:48:05 +08:00
比如说,你 hook 的 objc_msgsend,统计整个 app 的 api 调用,包括系统底层的调用。那你这个 api 的数量,肯定不能在存内存了。也不能在当前线程操作。
ai277014717
2019-11-07 18:46:25 +08:00
@xingheng 并不是指内存,而是当遇到这种问题性能也应该考虑进去。
ai277014717
2019-11-07 18:48:53 +08:00
@xingheng 函数退出时机并不保证会释放内存 参见 autoreleasepool
luopengfei14
2019-11-07 20:58:02 +08:00
大佬好多,曾经的菜鸡 iOSer 觉得 iOS 开发已经配不上这么深入的研究了。不喜勿喷…
hoyixi
2019-11-07 21:08:38 +08:00
这玩意的统计,难道不该实时传给服务器 or 缓存到本地到了一定条件同步给服务器吗,难道要常驻在内存里?
xingheng
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
2019-11-07 22:03:33 +08:00
@ai277014717 以我的理解,只有给对象发送了 autorelease 消息的对象才会在 autoreleasepool 闭合的时候 release,其他对象还是一直存在的。ARC 下,上面的 url, filename 可以算是 autorelease 对象。
我印象中以前有看过关于 dispatch queue 在执行的时候外围其实已经包了一个 @autoreleasepool{ },这样的话我觉得 autoreleased 对象并不能对内存构成威胁。

请指正。
xingheng
2019-11-07 22:07:43 +08:00
@hoyixi 那种存服务器的统计以前我还真写过,就是先写内存然后批量发到服务器,发送失败就临时写文件。但是我觉得面试官在这里应该不是问的一个设计上的问题,还是语言级的内存管理问题。
samlee123
2019-11-08 09:33:42 +08:00
确实会造成内存暴增,他问的就是 hook msgsend 然后做统计吧 ,调用方法需要开辟方法栈,评论里居然还有人质疑面试官水平。。。。。。🐶
xingheng
2019-11-08 10:23:11 +08:00
@samlee123 抱歉,是我没有描述清楚。确定不是 hook msgsend,面试官明确说了被调用方知道是谁调用了自己,参见#16 的示例代码。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/617185

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX