使用 NSDictionary 元素缓慢创建 NSMutableArray

Posted

技术标签:

【中文标题】使用 NSDictionary 元素缓慢创建 NSMutableArray【英文标题】:Create NSMutableArray with NSDictionary elements slow 【发布时间】:2014-12-03 17:25:13 【问题描述】:

这段代码是一种创建数组以供多个其他类使用的方法。输入是来自 CoreData 提取的数组,类型为 NSDictionaryResultType。

其中 3 个字段是我需要分解为数组的字符串,因此是 componentsSeparatedByString。

生成的数组 _dataProductionArray 运行良好 --- 但是 --- 这段代码需要整整 5 秒来处理大约 32,000 条记录。

任何帮助指出导致这种缓慢性能的明显错误将不胜感激!

NSMutableArray *dataArray = [NSMutableArray array];
int j = 0;
int maxNumMonths = 0;
for (id obj in _dictionaries) 
    if ([_dictionaries[j] [@"month"] length] >0 ) 
        // get production values
        NSArray *aItems   = [_dictionaries[j] [@"prodA"] componentsSeparatedByString:@","];
        NSArray *bItems   = [_dictionaries[j] [@"prodB"] componentsSeparatedByString:@","];
        NSArray *monthItems = [_dictionaries[j] [@"month"] componentsSeparatedByString:@","];

        NSMutableArray *productionAArray = [NSMutableArray array];
        NSMutableArray *productionBArray = [NSMutableArray array];
        int monthLoop = 1;
        for (NSNumber *month in monthItems) 
            if (monthLoop <= MONTHS_OF_PRODUCTION) 
                if ([month intValue] == monthLoop) 
                    [productionAArray addObject:[aItems objectAtIndex:monthLoop-1]];
                    [productionBArray addObject:[bItems objectAtIndex:monthLoop-1]];
                    productionCount ++;
                    if (monthLoop > maxNumMonths)
                        maxNumMonths = monthLoop;
                
            
            monthLoop++;
        

        NSDictionary *arrayItem = @@"name":_dictionaries[j] [@"name"],
                                    @"type":[NSString stringWithFormat:@"%@",_dictionaries[j] [@"type"]],
                                    @"height":[NSString stringWithFormat:@"%@",_dictionaries[j] [@"height"]],
                                    @"aArray":productionAArray,
                                    @"bArray":productionBArray,
                                    ;
        [dataArray addObject:arrayItem];
    
    j++;


_dataProductionArray = [NSArray arrayWithArray:dataArray];

【问题讨论】:

您在这里有几个嵌套循环,因此您可以移出循环的任何内容都将有助于您的表现。您可能想尝试在循环之外创建 productionAArray 和 productionBArray,并在其当前创建点执行 removeObjects 。我认为您的其他问题之一是您在紧密循环中创建了大量对象,这些对象在您完全完成之前不会被释放。与其循环遍历 32000 条记录,不如创建 32 个线程,每个线程只处理 1000 条记录,然后将其分段,这样最多只能运行 5 个线程。 5 秒记录 32,000 条记录听起来很合理。 感谢 Hot Licks - 这正是我所怀疑的。尝试了不同的策略进行优化,但没有什么能显着加快速度。欧文,确实尝试了一些多线程,但要么我做错了,要么就是没有帮助。当相同的代码运行几次迭代时,速度不是问题。 【参考方案1】:

我可以看到您可以在循环中进行一些优化,但我不确定这些优化有多大帮助(特别是如果编译器正在执行这些优化)。根本问题是 32k 是很多迭代。

您是否需要一次获得所有 32k 结果?通过完成这项工作lazily,您可以显着改善用户体验,因为 UI 需要转换后的记录。

这种方法是使 dataProductionArray 成为一个可变字典,由 NSNumber 索引索引。然后,而不是...

// replace this
self.dataProductionArray[128];

// with this
[self dataProductionAtIndex:@128];

那个新的getter方法调用你懒惰写的代码,像这样......

- (id)dataProductionAtIndex:(NSNumber *)index 

    // replace dataProductionArray with dataProductionDictionary
    id result = self.dataProductionDictionary[index];
    if (!result) 
        result = [self getDataAt:index];
        self.dataProductionDictionary[index] = result;
    
    return result;

然后getDataAt: 是对您发布的代码的简单重构,除了不是循环 32k 元素,它只为传入的一个索引完成工作......

- (id)getDataAt:(NSNumber *)index 

    int j = [index intValue];

    // no loop, just skip to iteration j
    NSArray *aItems   = [_dictionaries[j] [@"prodA"] componentsSeparatedByString:@","];
    NSArray *bItems   = [_dictionaries[j] [@"prodB"] componentsSeparatedByString:@","];

    // and so on, then at the end, don't save arrayItem, just return it

    NSDictionary *arrayItem = @@"name":_dictionaries[j] [@"name"],
                                @"type":[NSString stringWithFormat:@"%@",_dictionaries[j] [@"type"]],
                                @"height":[NSString stringWithFormat:@"%@",_dictionaries[j] [@"height"]],
                                @"aArray":productionAArray,
                                @"bArray":productionBArray,
                                ;
    return arrayItem;
    

PS - 可变字典是用于惰性求值的良好数据结构。下一个复杂级别是 NSCache,它的作用类似于可变字典并管理内存 (class ref here)。

【讨论】:

是的,不幸的是,我确实需要一次所有 32k 结果。 NSDictionary 还会是更好的选择吗? 我认为它们的性能相似,但对阵列有一点优势。无论哪种方式,我们都只是处于问题的边缘。如果您需要一次性完成工作,我认为您的精力最好花在 a) 将其从主线程中移除,以便您的 UI 保持响应,以及 b) 让用户保持冷静的进度指示器。 谢谢,danh,我确实把它从主线程上取下来了,并且确实有一个活动指示器。欣赏这些信息。【参考方案2】:

你的 for 循环很愚蠢。随便写

for (NSDictionary* dict in _dictionaries)...

并使用 dict 代替 _dictionaries [j]。每次保存一个方法调用。

stringWithFormat: 每次都会创建一个新字符串。你不能只添加项目本身而不是把它变成一个字符串吗?

与其将所有项目提取到productionAArrayproductionBArray 中,不如创建一个NSIndexSet,将其填充到循环中——或者更好的是使用块——然后一次性创建数组。

【讨论】:

哈哈,你是对的,绝对是愚蠢的......做了这些改变,不影响性能,但绝对是更好的代码。不要认为 NSIndexSet 对我有用,productionAArray 中的值不一定是唯一的,我正在使用 NSArray 来保留顺序。

以上是关于使用 NSDictionary 元素缓慢创建 NSMutableArray的主要内容,如果未能解决你的问题,请参考以下文章

NSDictionary 和核心数据

PHP json_encode() 与空 NSDictionary

嵌套的 JSON 数据将导致使用 NS 字典(在 Swift 中)崩溃

IOS 中NSDictionary 与 Dictionary 有啥区别

NSDictionary - 需要获取关键子元素的值

Cocoa 的 NSDictionary:为啥要复制密钥?