Оптимизация производительности памяти

64-разрядные приложения имеют потенциал для выполнения быстрее, чем 32-разрядные приложения вследствие улучшений современных 64-разрядных процессоров. Однако 64-разрядные среды увеличивают размер указателей и некоторых скалярных данных, приводящих к большему объему потребляемой памяти для Вашего приложения. Больший объем потребляемой памяти приводит к увеличенному давлению на кэши процессора и виртуальную память и может оказать негативное влияние на производительность. При разработке 64-разрядного приложения критически важно профилировать и оптимизировать использование памяти приложения.

Для всестороннего обсуждения оптимизации использования памяти см. Инструкции по Производительности Использования памяти.

Профилируйте свое приложение

Прежде, чем попытаться оптимизировать использование памяти Вашего приложения, необходимо сначала создать стандартные тесты, которые можно запустить и против 32-разрядных и против 64-разрядных версий приложения. Стандартизированные тесты позволяют Вам измерять штраф за компиляцию 64-разрядной версии Вашего приложения когда по сравнению с 32-разрядной версией. Это также обеспечивает способ измерить улучшения, поскольку Вы оптимизируете использование памяти своего приложения. По крайней мере один тест должен использовать минимальное место (например, приложение было просто открыто и показывает пустой документ). Другие тесты должны включать множество различных размеров данных, включая по крайней мере один тест с очень большим набором данных. Сложное приложение может потребовать тестов, покрывающих подмножества его функций. Цель для этих дополнительных тестов состоит в том, чтобы измериться, изменяется ли использование памяти значительно как изменения объема данных или тип. Если определенный вид данных заставляет 64-разрядную версию Вашего приложения использовать существенно больше памяти, чем свой 32-разрядный дубликат, который является великолепным местом, чтобы начать искать улучшения.

Heap и Heapdiff

В то время как и использование штабеля и «кучи» увеличивается на 64-разрядных приложениях, мы рекомендуем фокусировать усилия на сокращении использования «кучи» приложения; использование «кучи» обычно будет намного больше, чем Ваше использование стека. heap инструмент может использоваться для обнаружения, сколько памяти приложение выделило на «куче». heap инструмент также скажет Вам, сколько объектов каждого класса, который Ваше приложение выделило, и общее использование для каждого класса. Фокусируйте свои усилия на классах, использующих большую часть памяти. Как в большей части настройки производительности, часто небольшое количество оптимизации будет приводить к существенным улучшениям.

OS X v10.6 предлагает новый инструмент, heapdiff это может сгенерировать отчет, выделяющий различия между два heap отчеты. Несмотря на то, что можно использовать его для сравнения любых двух отчетов «кучи», наиболее популярного способа использования heapdiff должен сравнить использование памяти того же сценария и на 32-разрядных и на 64-разрядных версиях Вашего приложения.

Генерировать a heapdiff сообщите, выполните следующие шаги.

  1. Идентифицируйте сценарий памяти, который Вы хотите профилировать, как описано выше.

  2. Выполните 32-разрядную версию своего приложения и выполните Ваш тест.

  3. Выполненный heap сгенерировать отчет для 32-разрядной версии Вашего приложения и сохранить его как текстовый файл.

    sudo heap -sumObjectFields <application name> > test32.txt
  4. Повторите шаги 2 и 3 с помощью 64-разрядной версии приложения.

    sudo heap -sumObjectFields <application name> > test64.txt
  5. Выполненный heapdiff генерировать и открыть отчет.

    /Developer/usr/bin/heapdiff.pl test32.txt test64.txt

heapdiff отчет сравнивает число объектов и общей памяти, выделенной для каждого класса, выделяя различия в использовании памяти между двумя отчетами «кучи». Кроме того, отчет также обеспечивает отношение памяти, используемой в двух отчетах, выделяя расширение использования памяти каждого класса после компиляции Вашего приложения для 64-разрядного.

В то время как отчет HTML является самой человекочитаемой формой, heapdiff также предлагает дополнительные форматы данных, более легко обрабатывающиеся компьютером, в случае, если Вы хотите сгенерировать свои собственные отчеты данных. Консультироваться heapdiffсправка для получения дополнительной информации о списке форматов.

Проблемы использования общей памяти

Malloc

Крайне важно понять поведение malloc когда Вы разрабатываете 64-разрядную версию своего приложения. В дополнение к вызову malloc непосредственно, все объективные-C объекты выделяются с помощью malloc.

Маленькие выделения (меньше чем 512 байтов) округлены к следующему самому большому кратному числу 16 байтов. Например, примите свой используемый следующая структура:

struct node
{
    node        *previous;
    node        *next;
    uint32_t    value;
};

Когда эта структура компилируется для 32-разрядной среды, она использует 12 байтов хранения; malloc фактически выделяет 16 байтов. Но в 64-разрядной среде, эта структура приводит 20 байтов в рабочее состояние, и malloc выделяет 32! Приложение, выделяющее много таких узлов, потратило бы впустую существенное количество памяти.

Большие выделения еще более критически важны. Если malloc вызывается для выделения блока, больше, чем 512 байтов, он будет вокруг к следующему самому высокому кратному числу 512 и выделять так много памяти. Будьте особенно осторожны с классами или структурами, которые являются выше 256 байтов памяти в 32-разрядной среде. Если процесс преобразования структуры к 64-разрядным результатам в чем-то, что является чуть более чем 512 байтами в размере, Ваше приложение, не будет использовать вдвое больше памяти, но почти в четыре раза больше — большая часть из потраченного впустую.

Используйте надлежащие размеры данных

ConvertCocoa64 сценарий описал в Преобразовании Существующего приложения к 64-разрядным преобразованиям большинство экземпляров int и unsigned int к NSInteger и NSUInteger. NSInteger 64-разрядное целое число на 64-разрядных приложениях, удваивая требуемое хранение, но существенно увеличивая диапазон значений.

Ввести

Диапазон

интервал

- 2,147,483,648 - 2 147 483 647

NSInteger

- 9,223,372,036,854,775,808 к 9,223,372,036,854,775,807

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

Ввести

Диапазон

int8_t

- От 128 до 127

int16_t

- От 32,768 до 32 767

int32_t

- 2,147,483,648 - 2 147 483 647

int64_t

- 9,223,372,036,854,775,808 к 9,223,372,036,854,775,807

uint8_t

От 0 до 255

uint16_t

От 0 до 65 535

uint32_t

От 0 до 4,294,967,295

uint64_t

От 0 до 18,446,744,073,709,551,615

Выберите компактное представление данных

Ищите ситуации, где можно выбрать более сильное представление данных. Например, предположите, что мы сохранили календарную дату с помощью следующей структуры данных:

struct date
{
    int second;
    int minute;
    int hour;
    int day;
    int month;
    int year;
};

Эта структура 24 байта длиной, и, когда преобразовано в использование NSInteger, потребовалось бы 48 байтов, только для даты! Более компактное представление должно было бы просто сохранить число секунд и преобразовать их по мере необходимости.

struct date
{
    uint32_t seconds;
};

Упакуйте структуры данных

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

struct bad
{
    char       a;        // offset 0
    int32_t    b;        // offset 4
    char       c;        // offset 8
    int64_t    d;        // offset 16
};

В то время как структура только использует 14 байтов данных из-за дополнения, это приводит 24 байта в рабочее состояние пространства. Если эта структура была выделена с помощью malloc, потребовалось бы 32 байта; больше пространства пропало впустую, чем используемый для данных.

Лучший проект должен был бы сортировать поля от самого большого до самого маленького.

struct good
{
    int64_t    d;        // offset 0
    int32_t    b;        // offset 8
    char       a;        // offset 12;
    char       c;        // offset 13;
};

Теперь структура не тратит впустую пространства.

Используйте меньше указателей

Избегите злоупотреблять указатели в коде. Давайте смотреть на предыдущий пример снова.

struct node
{
    node        *previous;
    node        *next;
    uint32_t    value;
};

Только одна треть используемой памяти является полезной нагрузкой; остальное используется для соединения. Если мы компилируем ту же самую структуру в 64-разрядное приложение, одни только ссылки составляют 80% используемой общей памяти.

Для сложных типов данных, соответствующих в известном размере данных, можно хотеть заменить указатели индексом вместо этого. Например, если бы мы знали, что никогда не было бы больше чем 65 535 узлов в связанном списке, то мы могли вместо этого использовать следующее определение вместо этого.

struct indexed_node
{
    uint32_t    value;
    uint16_t    next;
    uint16_t    previous;
};
node *nodeList; // pointer to an array of nodes;

Используя связанный список, созданный вокруг индексов, мы используем значительно меньше пространства для ссылок по сравнению с нашими данными. Что еще более важно этот пример только использует единственный указатель. Когда преобразовано от 32-разрядного до 64-разрядного, этот пример только использует четыре дополнительных байта, независимо от размера списка.

Кэш только, когда Вы должны

Кэширование ранее расчетных результатов является распространенным способом улучшить производительность приложения. Однако это стоит исследовать, помогает ли кэширование действительно Вашему приложению. Поскольку предыдущие примеры показали, использование памяти намного выше в 64-разрядных системах. Если Ваше приложение полагается слишком много на кэширование, давление, это ставит систему виртуальной памяти, может фактически привести к худшей производительности.

Типичные примеры способов поведения избежать включают:

  • Кэширование любых данных, которые класс может дешево повторно вычислить на лету.

  • Кэширование данных или объектов, которые можно легко получить из другого объекта.

  • Кэширование системных объектов, которые недороги для воссоздания.

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

Используйте объекты основы мудро

Много классов Какао предлагают гибкий набор функций, но сделать это, они используют больше памяти, чем может обеспечить более простая структура данных. Например, если Вы используете NSDictionary возразите для содержания единственной пары ключ/значение, это значительно более дорого, чем простое выделение переменной для содержания той информации. Создание тысяч таких словарей тратит впустую память.