嵌套(for-)循环的奇怪性能问题

时间:2014-07-03 16:13:51

标签: ios objective-c performance loops

使用Instruments查找代码中的瓶颈我偶然发现了一些奇怪的观察结果:

我正在解析两个csv文件;一个包含约3000个客户,另一个约8000个订单。 之后,我迭代了包含客户和订单的两个数组,以检测彼此之间的关系。这样我不仅可以找到每个客户的相应订单,还可以确定他们的最后订单;根据那个日期他们被分类。

在开始时,两个阵列都没有被排序,所以对于每个客户我都经历了所有剩余的订单,这需要3-4秒。然后我想出了使用客户ID对两个数组进行排序的想法。现在我知道orders-array的第一个订单对应于第一个客户。我的订单中有一个不同的客户ID,我知道,这必须是下一个客户的订单。所以我删除了我已经处理的订单,并为下一个客户执行相同的操作。我已经有了下一个调整的想法(只使用块枚举的索引并跟踪该索引。这样我就不必删除任何条目。也许我的性能提升了一些。但是目前我有另一个问题,我在以下代码后解释:

- (void) determineLastOrders {
    for (Kunde * kunde in _kundenArray) {
        [self determineLastOrder:kunde];
    }
}

- (void) determineLastOrder: (Kunde*)kunde {
    NSMutableArray *bestellungenToRemove = [[NSMutableArray alloc] init];
    /* go through all (remaining) orders (after the loop the matching will be removed) and determine the next ones to remove */
    for (Bestellung * bestellung in _bestellungenMutArr) {
        if ([[bestellung bestKdNr] isEqualToString:kunde.kdnr]) {
            if ( kunde.lastOrder == nil) {
                kunde.lastOrder = _orangeDate;  //"init"
            }
            else if ([kunde.lastOrder compare:[bestellung bestDatum]] == NSOrderedAscending) {
                kunde.lastOrder = [bestellung bestDatum];
            }
            [bestellungenToRemove addObject:bestellung];
        }
        else  { 
            //as all orders are ordered by the customer id we can abort iteration
            //after we went past the current id
            break;
        }
    }
    // after the iteration we can safely remove the instances from the array
    // this is quite efficient as due to the order of the orders we ALWAYS remove from
    // the beginning of the array -> http://ridiculousfish.com/blog/posts/array.html
    [_bestellungenMutArr removeObjectsInArray: bestellungenToRemove];

    if ([kunde.lastOrder compare:_orangeDate] == NSOrderedDescending) {
       [kunde setDotPath: @"green.png"];
    }
    else if (kunde.lastOrder == nil) {
        [kunde setDotPath: @"red.png"];
    }
    else {
        [kunde setDotPath: @"orange.png"];
    }
}

我发现2个功能块大约需要400毫秒。我的下一个想法是,如果我不使用2个函数,我可能会获得一些小的性能提升,从而节省大约3000个函数调用。 所以我踢出了第一个函数,只需将我的for循环放在第二个函数的内容中。大概花了大约10倍的时间?!?为什么会这样?

由于


EDIT1:

具有嵌套循环的较慢代码版本:

- (void) determineLastOrders
{
    NSMutableArray *bestellungenToRemove = [[NSMutableArray alloc] init];

    for (Kunde * kunde in _kundenArray)
    {
        /* go through all (remaining) orders (after the loop the matching will be removed) and determine the next ones to remove */
        for (Bestellung * bestellung in _bestellungenMutArr)
        {
            if ([[bestellung bestKdNr] isEqualToString:kunde.kdnr])
            {
                if ( kunde.lastOrder == nil)
                {
                    kunde.lastOrder = _orangeDate;  //"init"
                }

                else if ([kunde.lastOrder compare:[bestellung bestDatum]] == NSOrderedAscending)
                {
                    kunde.lastOrder = [bestellung bestDatum];

                }
                //As this Bestellung already has had a date comparison (equal by kdnr)
                //we won't need to visit it again by our next customer
                [bestellungenToRemove addObject:bestellung];
            }
            else
            {   //as all orders are ordered by the customer id we can abort iteration
                //after we went past the current id
                break;
            }
        }

        //after the iteration we can safely remove the instances from the array
        //this is quite efficient as due to the order of the orders we ALWAYS remove from
        //the beginning of the array -> http://ridiculousfish.com/blog/posts/array.html
        [_bestellungenMutArr removeObjectsInArray: bestellungenToRemove]; 

        if ([kunde.lastOrder compare:_orangeDate] == NSOrderedDescending)
        {
            [kunde setDotPath: @"green.png"];
        }
        else if (kunde.lastOrder == nil)
        {
            [kunde setDotPath: @"red.png"];
        }
        else
        {
            [kunde setDotPath: @"orange.png"];
        }
    }
}

1 个答案:

答案 0 :(得分:2)

在只有一个函数的版本中,您已经将bestellungenToRemove可变数组的初始化留在了外部循环之外。这意味着你不会像在双功能版本中那样为外循环的每次迭代获得一个新的。

由于该数组每次都变大,removeObjects:调用随着数组的增长而变得越来越长。

所以,将它移回外部循环,你应该在两个版本中都有相同的性能。