#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. Это мне так помогло! Ваш ответ — это еще один ответ на вопрос, который я никогда не задавал, потому что я никогда не знал о подобной проблеме!