Как я могу получить точки останова / журналы / увеличенную видимость, когда блокирует основной раздел?

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

Я ищу какой-то «режим отладки» или дополнительный код, или крючок, или что-то еще, благодаря чему я могу установить точку останова / лог / что-то, что попадет в цель, и позволить мне проверить, что происходит, если мой основной поток «добровольно» блоки для ввода-вывода (или по какой-либо причине, действительно), кроме как для простоя в конце runloop.

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

Я понимаю, что есть операции, выполняемые UIKit / AppKit, которые имеют только основной поток, что вызовет ввод-вывод и вызовет блокировку основного потока, в какой-то степени он безнадежен (например, доступ к картоналу представляется потенциально блокирующий, основной поток-только), но что-то было бы лучше, чем ничего.

У кого-нибудь есть хорошие идеи? Похоже на то, что было бы полезно. В идеальном случае вы никогда не захотите блокировать основной поток, пока код вашего приложения активен на runloop, и что-то вроде этого было бы очень полезно при приближении к этой цели.

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

Проще было начать с повторения проблемы. Вот что я придумал:

Я хочу быть предупрежден о длительных периодах времени, проведенных в syscalls / mach_msg_trap, которые не являются законным временем простоя. «Законное время простоя» определяется как время, проведенное в mach_msg_trap, ожидающее следующего события из ОС.

Также, что важно, я не заботился о коде пользователя, который занимает много времени. Эта проблема довольно легко диагностировать и понимать с помощью инструмента Time Time Profiler. Я хотел знать конкретно о заблокированном времени. Хотя это правда, что вы также можете диагностировать заблокированное время с помощью Time Profiler, мне было гораздо сложнее использовать для этой цели. Подобным же образом инструмент System Trace Instrument также полезен для подобных исследований, но чрезвычайно мелкозернистый и сложный. Я хотел чего-то более простого – больше нацелился на эту конкретную задачу.

Казалось очевидным, что инструментом выбора здесь будет Dtrace. Я начал с использования наблюдателя CFRunLoop который выстрелил в kCFRunLoopAfterWaiting и kCFRunLoopBeforeWaiting . Вызов моего обработчика kCFRunLoopBeforeWaiting указывает на начало «законного времени простоя», и обработчик kCFRunLoopAfterWaiting станет сигналом для меня, что законное ожидание закончилось. Я бы использовал провайдер Pid Dtrace для ловушки при вызовах этих функций в качестве способа сортировки законного простоя от блокировки простоя.

Этот подход заставил меня начать, но в конце концов оказался ошибочным. Самая большая проблема заключается в том, что многие операции AppKit являются синхронными , поскольку они блокируют обработку событий в пользовательском интерфейсе, но фактически запустили RunLoop ниже в стеке вызовов. Эти спины RunLoop не являются «законными» простоями (для моих целей), потому что пользователь не может взаимодействовать с пользовательским интерфейсом в течение этого времени. Разумеется, они ценны – представьте себе runloop на фоновом потоке, наблюдая за связью ориентированного на RunLoop ввода-вывода, но пользовательский интерфейс по-прежнему блокируется, когда это происходит в основном потоке. Например, я ввел следующий код в IBAction и вызвал его с помощью кнопки:

 NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: @"http://www.google.com/"] cachePolicy: NSURLRequestReloadIgnoringCacheData timeoutInterval: 60.0]; NSURLResponse* response = nil; NSError* err = nil; [NSURLConnection sendSynchronousRequest: req returningResponse: &response error: &err]; 

Этот код не мешает RunLoop вращаться – AppKit вращает его для вас внутри sendSynchronousRequest:... call, но он не позволяет пользователю взаимодействовать с пользовательским интерфейсом, пока он не вернется. На мой взгляд, это не «законный простоя», поэтому мне нужен способ разобраться, какие простоя были такими. (Подход CFRunLoopObserver также был испорчен тем, что он требовал внесения изменений в код, которого нет в моем конечном решении).

Я решил, что я буду моделировать свой пользовательский интерфейс / основной поток как конечный автомат. Он всегда находился в одном из трех состояний: LEGIT_IDLE, RUNNING или BLOCKED, и переходил между этими состояниями как выполняемые программы. Мне нужно было придумать пробники Dtrace, которые позволили бы мне поймать (и, следовательно, измерить) эти переходы. Конечный конечный автомат, который я реализовал, был довольно сложным, чем только эти три состояния, но это представление размером 20 000 футов.

Как описано выше, сортировка законного простоя от плохого простоя не была простой, так как оба случая заканчиваются в mach_msg_trap() и __CFRunLoopRun . Я не мог найти один простой артефакт в стеке вызовов, который я мог бы использовать, чтобы надежно сказать разницу; Похоже, что простой зонд на одну функцию мне не поможет. Я закончил использование отладчика, чтобы посмотреть состояние стека в разных случаях законного простоя против плохого простоя. Я решил, что во время законного простоя я (казалось бы, надежно) вижу стек вызовов следующим образом:

 #0 in mach_msg #1 in __CFRunLoopServiceMachPort #2 in __CFRunLoopRun #3 in CFRunLoopRunSpecific #4 in RunCurrentEventLoopInMode #5 in ReceiveNextEventCommon #6 in BlockUntilNextEventMatchingListInMode #7 in _DPSNextEvent #8 in -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] #9 in -[NSApplication run] #10 in NSApplicationMain #11 in main 

Поэтому я попытался создать кучу вложенных / цепных пид-зондов, которые установили бы, когда я приеду, а затем уйду из этого состояния. К сожалению, по какой-либо причине поставщик Pt Dtrace, похоже, не может повсеместно исследовать как запись, так и возврат всех произвольных символов. В частности, я не мог получить пробники на pid000:*:__CFRunLoopServiceMachPort:return или on pid000:*:_DPSNextEvent:return к работе. Детали не важны, но, наблюдая за различными другими событиями и отслеживая определенное состояние, я смог установить (опять же, казалось бы, надежно), когда я был введен и оставил законное свободное состояние.

Затем мне пришлось определить зонды, чтобы сообщить разницу между RUNNING и BLOCKED. Это было немного легче. В конце концов, я решил рассмотреть системные вызовы BSD (используя Sysall-зонд Dtrace) и вызовы mach_msg_trap() (с использованием pid-зонда), не возникающие в периоды законного простоя BLOCKED. (Я смотрел на детектор mach_trap от Dtrace, но он, похоже, не делал то, что я хотел, поэтому я снова вернулся к использованию pid-зонда.)

Вначале я сделал некоторую дополнительную работу с поставщиком расписания Dtrace, чтобы фактически измерить реальное заблокированное время (то есть время, когда мой поток был приостановлен планировщиком), но это значительно усложнило, и я подумал: «Если я в ядре, что меня волнует, если поток действительно спит или нет? Это все равно пользователю: он заблокирован ». Таким образом, окончательный подход просто измеряет все время (syscalls || mach_msg_trap()) && !legit_idle и вызывает блокированное время.

На данный момент улавливание одноядерных вызовов длительной продолжительности (например, вызов sleep(5) ) представляется тривиальным. Тем не менее, чаще всего латентность потока пользовательских интерфейсов возникает из-за множества небольших латентностей, накапливающихся по нескольким вызовам в ядре (считая сотни вызовов read () или select ()), поэтому я подумал, что было бы желательно сбросить некий стек вызовов, если общее количество syscall или mach_msg_trap время за один проход цикла событий превысило определенный порог. Я закончил настройку различных таймеров и протоколирование накопленного времени, проведенного в каждом штате, охваченного различными состояниями на государственной машине, и сброса предупреждений, когда мы случайно перешли из состояния BLOCKED, и перешли порог. Этот метод, очевидно, приведет к данным, которые могут быть подвергнуты неправильной интерпретации, или может быть полной красной селедкой (то есть случайным, относительно быстрым системным сбором, который просто приводит нас к порогу предупреждения), но я чувствую, что это лучше, чем ничего.

В конце концов, сценарий Dtrace заканчивает сохранение конечной машины в переменных D и использует описанные пробники для отслеживания переходов между состояниями и дает мне возможность делать что-то (например, оповещения о печати), когда состояние машины переходит на состояние, основанное на на определенных условиях. Я немного поиграл с придуманным образцовым приложением, которое делает кучу дискового ввода-вывода, сетевого ввода-вывода и вызывает sleep (), и смог поймать все три из этих случаев, не отвлекаясь от данных, относящихся к законным ожиданиям , Это именно то, что я искал.

Это решение, очевидно, довольно хрупкое и абсолютно ужасное почти во всех отношениях. 🙂 Это может быть или не быть полезным мне или кому-либо еще, но это было веселое упражнение, поэтому я подумал, что расскажу об этом, и в результате получится сценарий Dtrace. Возможно, кому-то это будет полезно. Я также должен признаться, что относился к n00b относительно написания сценариев Dtrace, поэтому я уверен, что сделал миллион вещей неправильно. Наслаждайтесь!

Он слишком велик для публикации в строке, поэтому он любезно размещен на @Catfish_Man здесь: MainThreadBlocking.d

На самом деле это работа для инструмента Time Profiler. Я считаю, что вы можете видеть, где время потрачено на код в потоке, поэтому вы бы посмотрели, какой код требуется на время, и получите ответ о том, что потенциально блокирует пользовательский интерфейс.

  • Swift - введите дату из года
  • Качество сжатия JPEG с использованием CGImage
  • Какова временная сложность (большой O) sortedArrayUsingComparator? IOS / OSX
  • Как создать UIImageView с изображением из ссылки?
  • Быстрое: извлечение / понижающее преобразование типов CoreText на основе CFType в CFArray
  • В Metal, каковы правильные blendFactors для классического композитора Porter-Duff?
  • Возможно ли разработать iOS или OS X App в LLVM IR?
  • Могут ли разработчики использовать iCloud в приложениях Windows?
  • Настройка уведомлений о характеристиках приводит к ошибке Invalid Handle
  • Несколько делегатов NSURLConnection в Objective-C
  • Изображение NSTextAttachment не показано в NSTextView (но в UITextView)?
  • Давайте будем гением компьютера.