Странная проблема с производительностью из-за вложенных циклов (for-)

#ios #objective-c #performance #loops

#iOS #objective-c #Производительность #циклы

Вопрос:

Используя инструменты для поиска узких мест в моем коде, я наткнулся на странное наблюдение:

Я анализирую два CSV-файла; один содержит около 3000 клиентов, другой — около 8000 заказов. После этого я перебираю два массива, содержащих customers и orders, чтобы обнаружить взаимосвязи друг с другом. Таким образом, я не только нахожу соответствующие заказы для каждого клиента, но и определяю их последние заказы; в соответствии с этой датой они классифицируются.

Вначале ни один из двух массивов не был отсортирован, поэтому для каждого клиента я просмотрел ВСЕ оставшиеся заказы, что заняло около 3-4 секунд. Затем мне пришла в голову идея отсортировать оба массива, используя идентификаторы клиентов. Теперь я знаю, что первые заказы из массива orders соответствуют первому клиенту. Как только я получил другой идентификатор клиента в своем заказе, я знаю, что это должен быть заказ следующего клиента. поэтому я удаляю заказы, которые я уже обработал, и делаю то же самое для следующего клиента. У меня уже есть моя следующая идея настройки (просто использую индекс перечисления блоков и отслеживаю этот индекс. Таким образом, мне не нужно удалять какие-либо записи. Может быть, я еще немного повышу производительность. Но в настоящее время у меня есть еще одна проблема, которую я объясняю после следующего кода:

 - (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"];
    }
}
  

Я выяснил, что блок из двух функций примерно занимает около 400 мс. Моей следующей мыслью было, что я мог бы получить небольшой прирост производительности, если бы не использовал 2 функции и таким образом сэкономил около 3000 вызовов функций.
Итак, я удалил 1-ю функцию и просто поместил свой цикл for вокруг содержимого 2-й функции. В тот раз это заняло примерно в 10 раз больше времени ?!? Почему это могло быть?

Спасибо


ПРАВКА1:

Более медленная версия кода с вложенным циклом:

 - (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. Можете ли вы опубликовать код до и после?

2. Вы делаете все это в фоновом потоке, верно?

3. Я не реализовывал никаких потоков. Согласно инструментам, все это происходит в основном потоке.

4. Джесси Русак заметил проблему.

Ответ №1:

В версии с одной функцией вы оставили инициализацию bestellungenToRemove изменяемого массива вне внешнего цикла. Это означает, что вы не получаете новый для каждой итерации внешнего цикла, как вы делаете в двухфункциональной версии.

Поскольку этот массив становится больше с каждым разом, removeObjects: вызов занимает все больше и больше времени по мере роста этого массива.

Итак, переместите его обратно во внешний цикл, и у вас должна быть одинаковая производительность в обеих версиях.

Комментарии:

1. Святое дело! Возвращайся ко мне в школу! Вот в чем проблема! И пропуск вызовов функций 3k также сэкономил мне 0,2 секунды. Я думал, что сэкономлю время, не всегда повторно инициализируя свой массив :- / Теперь мне придется пересмотреть большую часть своего кода 🙂

2. Это мне так помогло! Ваш ответ — это еще один ответ на вопрос, который я никогда не задавал, потому что я никогда не знал о подобной проблеме!