[первоначально опубликовано 21.08.2017]

Нелегко точно понять, что означают различные показатели MachOs (ха-ха)… Чтобы быть уверенным, что вещи означают именно то, что я думал, я написал эту тестовую программу. Я тестировал на своем Mac, но я уверен, что результаты будут такими же для iOS.

Метрики, которые мы изучаем:

Из getusage

  • Максимальный резидентный набор, незначительные сбои, серьезные сбои, системное время, пользовательское время

От task_info

  • Копирование при ошибках записи, виртуальные байты, резидентные байты

Значения учебника

Максимальное количество резидентов

  • Наибольшее количество физических байтов, когда-либо использованных вашим процессом (обратите внимание, что вы можете запрашивать разные объекты, но в данном случае я изучаю RUSAGE_SELF
  • Обратите внимание, что некоторые из ваших байтов могут быть заменены, чтобы освободить место для новых распределений, и, как следствие, можно выделить и даже изменить новые байты без дополнительных изменений здесь.
  • Почему меня это волнует: отличный показатель для оценки нагрузки на память, которую ваше приложение добавляет в систему.

Незначительные ошибки

  • ошибки страниц, для устранения которых не требовался ввод-вывод. Сюда входят ошибки нулевого спроса и ошибки копирования при записи, как мы увидим ниже.
  • Ошибка требования нуля возникает при первом изменении только что выделенной страницы, страницы, содержащей все нули и совместно используемой с другими страницами, которые были все нулями, поскольку это обычная ситуация. Когда вы изменяете страницу, она принудительно выделяет физическую страницу для этого использования. Это ошибка нулевой потребности.
  • Почему меня это волнует: для обработки каждой ошибки требуется ЦП (но не ввод-вывод), они говорят вам, что вы получаете из кеша, а также требуют отсутствия ошибок; после сбоя вы, вероятно, также получите промахи кеша процессора.

Серьезные ошибки

  • ошибка страницы, для устранения которой требовался ввод-вывод. Это включает в себя загрузку кода в первый раз, загрузку постоянных данных в первый раз или восстановление страницы, которая была выгружена, чтобы попытаться повторно использовать физическую память.
  • Почему меня это волнует: все штрафы за незначительные ошибки ПЛЮС ввод/вывод (должно быть ввод/вывод, иначе он был бы незначительным). Серьезные сбои могут привести к остановке вашего приложения, пока вы ждете разрешения ввода/вывода.

Системное время

  • Измеряется в процессорных секундах. Это время, в течение которого поток выполнял любой код непользовательского режима от имени измеряемого процесса.
  • Это может быть больше или меньше, чем время настенных часов, если есть много времени ожидания, оно, вероятно, будет меньше. Если есть небольшое ожидание и несколько запущенных потоков, вероятно, будет больше.
  • Почему меня это волнует: многие из самых дорогостоящих действий, которые делает процесс, выполняются ядром от имени приложения, эти действия также сжигают ЦП, и это позволяет отслеживать их.

Время пользователя

  • То же, что и выше, но для кода пользовательского режима, то есть кода, который вы написали. Сюда входят библиотеки времени выполнения, которые работают в пользовательском режиме… То есть почти все библиотеки.
  • Почему меня это волнует: это ЦП, которым вы управляете напрямую, это опережающий индикатор многих вещей, которые пошли не так, включая сбои, и основной фактор использования батареи.

Ошибки копирования при записи

Ошибка копирования при записи возникает, когда изменяется страница данных, которая была инициализирована некоторыми постоянными данными. Есть две причины, по которым это может произойти:

  • Вы изменяете инициализированную константу, например int global = 5; вы устанавливаете global=6.
  • При запуске инициализированный указатель типа char *string = «String»; требует перебазирования, чтобы он указывал на текст «String». Это выглядит точно так же, как global = 6; к системе ВМ. Эти исправления применяются перед main.
  • Почему меня это волнует. Ошибки COW, возникающие при запуске, особенно болезненны, потому что замедляют процесс запуска. Инкрементный COW, который возникает, когда ваше приложение работает на платной основе, не хуже и не лучше, чем другие мелкие ошибки.

Виртуальные байты

  • общее количество всех сделанных резервирований виртуального пространства, независимо от того, являются ли код/данные резидентными. Виртуальный байт может выйти из строя, если адресное пространство будет освобождено. Замена/неисправность не влияет на это число. Обратите внимание, что это очень большое число, поскольку оно включает все общие библиотеки, на которые может ссылаться ваша программа, независимо от того, используются они или нет. Обычно это намного больше, чем Resident Set.
  • Почему меня это волнует: если ваша система находится под давлением, а резидентный набор продолжает урезаться, что приводит к множеству сбоев, но не увеличивается максимальный размер резидентного набора, вы можете увидеть, кто виноват, наблюдая за процессы, чьи виртуальные байты растут.

Резидентные байты

  • Фактическое количество байтов, выделенных вашему процессу (или измеряемому объекту), которые хранятся в физической памяти. Очевидно, меньше или равно Max Resident Set по определению. Операционная система, скорее всего, попытается «урезать» ваш процесс, чтобы восстановить физическую память, если вы не используете ее. Это приводит к тому, что резидентные байты меньше, чем макс. Тот факт, что это происходит, делает резидентные байты сложной для интерпретации метрикой.
  • Почему меня это волнует: посмотрите максимальный резидентный набор и добавьте это: можно вернуть байты с освобождением, если вы наблюдаете за резидентным набором в течение длительного периода времени, максимальный резидентный набор станет бесполезным после одного неудачного момента.

Вывод программы и интерпретация

Pre-main approximation
maxrss 437, min 711, maj 3, cow 90, virt 618436, res 440, usr = 0.001161, sys = 0.001242

Есть 90 ошибок коровы перед основной, виртуальная память печатается в страницах, так что это 618000 страниц (!) виртуального распределения. Огромный… массивный. Резидентный набор составляет 437 страниц. Серьезных ошибок очень мало. Похоже, что много обращений к дисковому кешу для битов, которые нам нужны. Не спрашивайте меня, как maxrss ‹ текущий резидентный набор.

Если мы запустим это снова с #if, установленным в 1, что будет намного больше ссылок на большие данные, мы получим этот вывод.

Premain approximation
maxrss 437, min 720, maj 4, cow 98, virt 618436, res 440, usr = 0.000953, sys = 0.001234

Обратите внимание, что ошибки копирования при записи увеличились до 98, чтобы применить все исправления, необходимые для инициализации массива. Мелкие неисправности также увеличились. Было немного больше инициализированных данных, так что была и дополнительная серьезная ошибка. Не думайте слишком много об этом, так как я запускал программу несколько раз, поэтому она должна быть в основном в дисковом кеше, поэтому серьезных ошибок будет мало. Таким образом, копирование при записи считается незначительным сбоем. Извините, бухгалтерия не совсем идеальна, трудно добиться идеальной стабильности.

Отсюда я буду использовать результаты с #if, установленным на 0. Другой вариант был просто для иллюстрации ошибок перед main.

Regular alloc phase
maxrss 450, min 724, maj 3, cow 90, virt 623557, res 450, usr = 0.001193, sys = 0.001267
...
maxrss 450, min 724, maj 3, cow 90, virt 669637, res 450, usr = 0.001212, sys = 0.001308

На этом этапе мы делаем 10 аллокаций по 20 мегабайт каждая. Обратите внимание, что maxrss не увеличивается. Это потому, что все эти страницы — нули. У всех одна и та же память. Другие счетчики отказов также остаются постоянными. Однако, как видно, использование виртуальной памяти увеличивается.

Demand zero phase
maxrss 5571, min 5845, maj 3, cow 90, virt 674757, res 5571, usr = 0.006330, sys = 0.007717
...
maxrss 51651, min 51925, maj 3, cow 90, virt 720837, res 51651, usr = 0.052957, sys = 0.079686

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

Data writing phase
maxrss 56771, min 52180, maj 4868, cow 5209, virt 720837, res 56771, usr = 0.057620, sys = 0.100440

На этапе записи данных мы затираем 20 мегабайт инициализированных данных. Это вызывает ошибки копирования при записи. Обратите внимание, что в этом случае также были серьезные ошибки для всех этих данных, потому что 20M заполненных нулями инициализированных данных (хотя я явно инициализировал только несколько байтов) не было в дисковом кеше. Итак, основные неисправности.

BSS writing phase
maxrss 61891, min 57300, maj 4868, cow 5209, virt 720837, res 61891, usr = 0.061720, sys = 0.106610

По историческим причинам данные, которые по умолчанию инициализируются нулем, такие как int global; называется БСС. Причиной таких названий, как правило, могут быть такие вещи, как «Это название сегмента было на PDP10, и мы просто продолжали использовать его годы спустя». Но я не знаю происхождения этого. Я посмотрел однажды…

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

Big alloc phase 
maxrss 1120682, min 2105331, maj 4868, cow 5209, virt 2768852, res 1115007, usr = 2.105623, sys = 3.189343

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

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

Приложение: Исходный код

#include <sys/resource.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#import <mach/mach.h>
#import <mach/task_info.h>

void printmetrics();
char bigdata[20<<20] = "This is a test";
char bigbss[20<<20];  // zero init
extern char * lotsaPointers[];
struct sysmetrics {
  struct rusage rusage;
  struct task_basic_info basic;
  struct task_events_info event;
};
static void getsysmetrics(struct sysmetrics *metrics)
{
  getrusage(RUSAGE_SELF, &metrics->rusage);
  mach_port_t task = mach_task_self();
  mach_msg_type_number_t tcnt;
  tcnt = TASK_BASIC_INFO_COUNT;
  task_info(task, TASK_BASIC_INFO, (task_info_t)&metrics->basic, &tcnt);
  tcnt = TASK_EVENTS_INFO_COUNT;
  task_info(task, TASK_EVENTS_INFO, (task_info_t)&metrics->event, &tcnt);
}
int main() 
{
  int i;
  printf("Pre-main approximation\n");
  printmetrics();
  printf("Regular alloc phase\n");
  for (i = 0; i < 10; i++) {
    void *m = malloc(20 << 20);
    printmetrics();
  }
  printf("Demand zero phase\n");
  for (i = 0; i < 10; i++) {
    void *m = malloc(20 << 20);
    memset(m, 0, 20<<20);
    printmetrics();
  }
  printf("Data writing phase\n");
  memset(bigdata, 0, sizeof(bigdata));
  printmetrics();
  printf("BSS writing phase\n");
  memset(bigbss, 0, sizeof(bigbss));
  printmetrics();
  printf("Big alloc phase\n");
  for (i = 0; i < 400; i++) {
    void *m = malloc(20 << 20);
    memset(m, 0, 20<<20);
  }
  printmetrics();
  // prevent linker from dead eliminating the data
  memcpy(lotsaPointers[0], lotsaPointers[1], 1);
  return 0;
}
void printmetrics()
{
  struct sysmetrics m;;
  getsysmetrics(&m);
  printf(
    "maxrss %ld, min %ld, maj %ld, "
    "cow %d, virt %ld, res %ld, "
    "usr = %f, sys = %f\n",
    m.rusage.ru_maxrss / 4096,
     m.rusage.ru_minflt,
     m.rusage.ru_majflt,
    m.event.cow_faults,
    m.basic.virtual_size / 4096,
    m.basic.resident_size / 4096,
    m.rusage.ru_utime.tv_sec + m.rusage.ru_utime.tv_usec / 1000000.0,
    m.rusage.ru_stime.tv_sec + m.rusage.ru_stime.tv_usec / 1000000.0);
}
char * lotsaPointers[] = 
{
bigdata, bigdata, bigdata, bigdata, bigdata, bigdata, bigdata, bigdata, bigdata, bigdata, 
#if 1
bigdata, bigdata, bigdata, bigdata, bigdata, bigdata, bigdata, bigdata, bigdata, ... many thousand rows of the same
#endif
};