Показаны сообщения с ярлыком xCode. Показать все сообщения
Показаны сообщения с ярлыком xCode. Показать все сообщения

среда, 12 ноября 2014 г.

Коротко. Сегодня опять рефакторил свой код под xCode

Сегодня опять рефакторил свой код под xCode, под MacOS X.

В части потребления памяти.

В некотором смысле в продолжение поста - Коротко. Об оптимизации потребления памяти под xCode. Затравочка.

(Почему вообще на Mac, а не наWindows? Ну потому, что там используются специфические маковские структуры, которые лучше всего обрабатыватся нативными маковскими библиотеками. Можно и на Windows перепортировать. Но это - потом. Залезая "в кишки".)

Определённых успехов удалось добиться.

Съэкономил 20% памяти на одних и тех же прецедентах.

С 200 Мб в пике до 160 Мб.

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

В общем - был класс генератора файлов, который одновременно генерировал в два формата.

Ну так было нужно изначально. В том ТЗ под который этот класс писался.

Потом появилось новое ТЗ, не исключающее предыдущего, но в нём не надо было генерировать один из форматов.

Ну собственно изначально взяли и использовали тот класс, о котором я писал выше.

Просто из результатов его деятельности использовали не два формата, а один, а другой просто - игнорировали.

В общем - брали "швейцарский нож" и "выливали воду из чайника" и сводили задачу к предыдущей.

До поры до времени это было некритично.

Потом - стало критично.

Скажем так - к этому прецеденту предъявляются "серверные требования". А не "десктопные".

Ну в итоге этот класс был распилен на три - один абстрактный предок и два конкретных потомка. Один конкретный потомок выливает один формат. Второй - оба формата. Ну и абстрактный класс - обеспечивает инфраструткуру. Можно конечно сделать три конкретных класса -для одного формата, для второго формата и для обоих. Но такой задачи - пока не стоит. Пока в ТЗ нет такого прецедента.

Можно было бы вообще использовать шаблон проектирования Builder или Strategy, но пока - опять же - это не нужно. Не стоит задача большой универсализации.

Был потрачен рабочий день и получено сокращение потребления памяти на 20%,

В общем - я лично доволен. День прошёл с пользой.

Да, ещё один немаловажный момент - это то, что тесты показали, что ошибок в конвертацию данных внесено не было. По крайней мере на текущих массивах данных. Ни одного расхождения.

Детали кода - писать лень. Всё равно - "синтетический пример" - вряд ли кому интересен.

Да и знатоки Objective-C это всё вряд ли читают. А переписывать пример на Delphi - тем более он переписывается "один в один", без потери эффективности. Я кое что писал - Objective-C и Delphi.

P.S. Это к не тому, что не надо вообще "выливать воду из чайника" и использовать "швейцарские ножи". Но к тому, что эти два анти-паттерна - служат первыми кандидатами на анализ проблем (Улучшение существующего кодаЧто такое анти-паттерны?).

вторник, 11 ноября 2014 г.

Коротко. Об оптимизации потребления памяти под xCode. Затравочка

Пусть у нас есть некий алгоритм, который использует синглетон.

И пусть этот синглетон в свою очередь грузит какие-то данные из файла.

Примерно так:

Реализация синглетона:

@interface SomeLoader: NSObject
// - интерфейс класса SomeLoader

- (NSDictionary *) someData;
// - метод получения данных потребованию

+ (instancetype) instance;
// - метод получения экземпляра синглетона
//   instancetype означает "тип текущего определяемого класса"
//   в данном случае - SomeLoader *
// http://osxdev.ru/tag/instancetype/
// http://nshipster.com/instancetype/

@end
// - конец определения интерфейса класса SomeLoader

...

@implementation SomeLoader
// - реализация класса SomeLoader
{
    NSDictionary * m_someData;
    // - наши данные, которые грузятся по требованию
}

- (NSDictionary *) someData 
{
    if (!m_someData) {
     // - данные не созданы
        NSString * vPath = [[NSBundle mainBundle] pathForResource: @"DataName" ofType: @""];
        // - получаем путь к ресурсу в Bundle
        // https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/
        //   Путь попал попал в текущий autoreleasepool
        NSData * vRestoredData = [NSData dataWithContentsOfFile: vPath];
        // - "мапируем" данные на файл
        // https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/#//apple_ref/occ/clm/NSData/dataWithContentsOfFile:
        // https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSData_Class/#//apple_ref/occ/clm/NSData/dataWithContentsOfFile:options:error:
        //   "Мапинг" попал в текущий autoreleasepool
        //   НО на самом деле это далеко не всегда - "мапинг",
        //   а зачастую - очень даже загрузка данных в память
        // Ибо:
        // "Return Value
        // A data object by reading every byte from the file specified by path. Returns nil if the data object could not be created."
        // - в чём часто можно убедится под отладчиком, смотря на текущий прирост памяти ДО и ПОСЛЕ вызова dataWithContentsOfFile.

        if (vRestoredData) {
         NSError * vError = nil;
                // - сюда помещается ошибка
         NSPropertyListFormat vFormat;
                // - сюда помещается формат считанных данных
         m_someData = [ 
                              [NSPropertyListSerialization 
                                propertyListWithData: vRestoredData
                                // - "мапинг", откуда десериализуется объект
                                options: NSPropertyListImmutable
                                // - заявляем о том, 
                                //   что мы НЕ ХОТИМ менять десериализуемый объект
                                //   если объект РЕАЛЬНО не надо менять, 
                                //   то он так оптимальнее создаётся
                                format: &vFormat 
                                // - сюда помещается формат считанных данных
                                error: &vError
                                // - сюда помещается ошибка
                               // - десериализуем данные в m_someData
                              ] retain
                               // - захватываем их в m_someData
                      ];
                //   Данные попали в текущий autoreleasepool и в m_someData
                assert(!vError);
                // - ошибок десериализации - нет
        }
    }
    return m_someData;
    // - возвращаем данные
}

- (void) dealloc 
{
    DESTROY(m_someData);
    // - освобождаем наши данные
    [super dealloc];
}

+ (instancetype) instance
// - метод получения экземпляра синглетона
{
        static SomeLoader * g_Loader = nil;
        // - место хранения экземпляра нашего синглетона
        static dispatch_once_t g_onceToken;
        dispatch_once(&onceToken, 
        // - вызов метода из GCD, который гарантирует, что в блок мы попадём только ОДИН раз
        // Подробности например читаем тут:
        //   http://stackoverflow.com/questions/5720029/create-singleton-using-gcds-dispatch-once-in-objective-c
         ^{
           // - это собственно вызываемый блок
                g_Loader = [self new];
                // - создаём экземпляр синглетона
                //   Где он потом освобождается - это отдельный вопрос.
                //   Пока для простоты - нигде.
         } // - конец вызываемого блока
        ); // - конец вызова dispatch_once
        return  g_Loader;
        // - возвращаем экземпляр нашего синглетона, 
        //   который создан ГАРАНТИРОВАННО только ОДИН РАЗ
}
@end
// - конец определения реализации класса SomeLoader

...


Его использование:

@interface SomeAlgorithm
+ (void) doWith: (NSDictionary *) aData;
@end

...

@interface SomeClass
- (void) SomeMethod;
@end

...

@implementation SomeClass
- (void) SomeMethod
{
 @autoreleasepool {
 // Тут начинается новый пул объектов.
 // https://developer.apple.com/library/ios/documentation/cocoa/conceptual/memorymgmt/Articles/mmAutoreleasePools.html
  ...
  NSDictionary *vData = [
                         [SomeLoader instance] 
                         // - получаем синглетон
                         someData
                         // и получаем его данные
                        ];
  [SomeAlgorithm doWith: vData];
  // - обрабатываем данные
 }
 // - именно тут освобождаются объекты из autoreleasepool
}
@end

Какие тут есть проблемы?

Пока - вроде бы никаких.

Но проблемы вообще говоря есть.

И связаны они со стратегией работы с NSData * vRestoredData.

Рассказать какие?

Намекну:
1. Пусть ресурс "DataName" - достаточно большой (ну например - несколько десятков мегабайт).
2. Пусть SomeAlgorithm - достаточно требователен к потреблению памяти (и несколько десятков мегабайт - для него - критичны).

Ну и сразу дам "правильный ответ".

Метод someData - "должен" бы выглядеть как-то так:

- (NSDictionary *) someData 
{
    if (!m_someData) {
     // - данные не созданы
     @autoreleasepool {
     // - создаём новый autoreleasepool
        NSString * vPath = [[NSBundle mainBundle] pathForResource: @"DataName" ofType: @""];
        // - получаем путь к ресурсу
        //   Путь попал попал в текущий autoreleasepool
        NSData * vRestoredData = [NSData dataWithContentsOfFile: vPath];
        // - "мапируем" данные на файл
        //   "Мапинг" попал в текущий autoreleasepool

        if (vRestoredData) {
        // Мапинг - создался
         NSError * vError = nil;
                // - сюда помещается ошибка
         NSPropertyListFormat vFormat;
                // - сюда помещается формат считанных данных
         m_someData = [
                              [NSPropertyListSerialization 
                                propertyListWithData: vRestoredData 
                                options: NSPropertyListImmutable 
                                format: &vFormat 
                                error: &vError
                               // - десериализуем данные в m_someData
                              ] retain
                               // - захватываем их в m_someData
         ];
         //   Данные попали в текущий autoreleasepool и в m_someData
         assert(!vError);
         // - ошибок десериализации - нет
        }
     } // @autoreleasepool
     // - тут освободятся объекты из пула
     //   В частности - vPath, vRestoredData и m_someData
     //   При этом vPath, vRestoredData - уничтожатся.
     //   А вот m_someData - не уничтожится, потому, что мы его захватили при помощи retain
    } // if (!m_someData)
    return m_someData;
    // - возвращаем данные
}

Ну и детали того почему этот код - "правильный" - я напишу позже.

Хотя и так - "навскидку" - понятно, что мы уменьшили время жизни объектов vPath и vRestoredData.

Ну и пост из "той же серии", но про Delphi - Сокращение времени жизни интерфейса.
Ну и ещё - Об "опасностях" ARC и прочих "автоматов". Пока коротко.

Вообще говоря autoreleasepool относится к "тем самым автоматам". Которые "опасны".

Что это означает?

Это отнюдь не означает, что "ими не надо пользоваться".

Это означает, что зачастую полезно представлять - как они устроены "в байтах".

Надеюсь, что даже то, что я написал - кому-нибудь окажется полезным.

Ну а пока - у меня всё.