Параллелизм и проектирование приложений

В первые годы вычислений максимальная сумма работы на единицу времени, которую мог выполнить компьютер, была определена тактовой частотой CPU. Но поскольку технология совершенствовалась, и проекты процессора стали более компактными, тепло и другие физические ограничения начали ограничивать максимальные тактовые частоты процессоров. И так, производители микросхемы искали другие способы увеличить общую производительность их микросхем. Решение, по которому они рассчитались, увеличивало число ядер процессора на каждой микросхеме. Путем увеличения числа ядер однокристальная схема могла выполнить больше инструкций в секунду, не увеличивая скорость ЦП или изменив размер кристалла или тепловые характеристики. Единственная проблема состояла в том, как использовать в своих интересах дополнительные ядра.

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

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

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

Перемещение далеко от потоков

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

Вместо того, чтобы полагаться на потоки, OS X и iOS берут асинхронный подход к проектированию для решения проблемы параллелизма. Асинхронные функции много лет присутствовали в операционных системах и часто используются для инициирования задач, которые могли бы занять много времени, такие как чтение данных из диска. Когда вызвано, асинхронная функция выполняет некоторую работу негласно для запуска выполнения задачи, но возвраты перед той задачей могли бы фактически быть завершены. Как правило, эта работа включает получение фонового потока, запуск желаемой задачи на том потоке и затем отправки уведомления вызывающей стороне (обычно через функцию обратного вызова), когда сделана задача. В прошлом, если бы асинхронная функция не существовала для того, что Вы хотите сделать, то необходимо было бы записать собственную асинхронную функцию и создать собственные потоки. Но теперь, OS X и iOS обеспечивают технологии, чтобы позволить Вам выполнять любую задачу асинхронно, не имея необходимость управлять потоками самим.

Одной из технологий для стартовых задач асинхронно является Grand Central Dispatch (GCD). Эта технология берет код управления потоком, который Вы обычно писали бы в своих собственных приложениях и перемещаете тот код вниз на системный уровень. Все, что необходимо сделать, определяют задачи, Вы хотите выполнить и добавить их к соответствующей очереди отгрузки. GCD заботится о создании необходимых потоков и о планировании Ваших задач работать на тех потоках. Поскольку управление потоком является теперь частью системы, GCD обеспечивает целостный подход к управлению задачами и выполнению, обеспечивая лучшую эффективность, чем традиционные потоки.

Очереди работы являются объектами Objective C, действующими очень как очереди отгрузки. Вы определяете задачи, Вы хотите выполнить и затем добавить их к очереди работы, обрабатывающей планирование и выполнение тех задач. Как GCD, очереди работы обрабатывают все управление потоком для Вас, гарантируя, что задачи выполняются настолько быстро и эффективно насколько возможно в системе.

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

Очереди отгрузки

Очереди отгрузки являются механизмом на базе С для выполнения пользовательских задач. Очередь отгрузки выполняет задачи или последовательно или одновременно но всегда в порядке метода «первым пришел - первым вышел». (Другими словами, очередь отгрузки всегда исключает из очереди и запускает задачи в том же порядке, в котором они были добавлены к очереди.) Последовательная очередь отгрузки выполняет только одну задачу за один раз, ожидая, пока та задача не завершена прежде, чем исключить из очереди и запустить новую. В отличие от этого, параллельная очередь отгрузки запускает столько задач, сколько она может, не ожидая уже запущенных задач закончиться.

Очереди отгрузки обладают другими преимуществами:

  • Они обеспечивают прямой и простой интерфейс программирования.

  • Они предлагают автоматическое и целостное управление пулом потоков.

  • Они обеспечивают скорость настроенного блока.

  • Они - намного больше эффективной памяти (потому что штабели потока не задерживаются в памяти приложения).

  • Они не захватывают к ядру при загрузке.

  • Асинхронная диспетчеризация задач очереди отгрузки не может завести в тупик очередь.

  • Они масштабируются корректно в соответствии с конкуренцией.

  • Последовательные очереди отгрузки предлагают более эффективную альтернативу блокировкам и другим примитивам синхронизации.

Задачи, которые Вы представляете очереди отгрузки, должны инкапсулироваться или в функции или в блочном объекте. Блочные объекты являются функцией языка C, представленной в OS X v10.6 и iOS 4.0, которые подобны указателям функции концептуально, но обладают некоторыми дополнительными преимуществами. Вместо того, чтобы определить блоки в их собственном лексическом контексте, Вы обычно определяете блоки в другой функции или методе так, чтобы они могли получить доступ к другим переменным от той функции или метода. Блоки могут также быть перемещены из их исходного объема и скопированы на «кучу», которая является тем, что происходит, когда Вы представляете им очереди отгрузки. Все они семантика позволяют реализовать очень динамические задачи с относительно небольшим кодом.

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

Источники отгрузки

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

  • Таймеры

  • Сигнальные обработчики

  • Связанные с дескриптором события

  • Связанные с процессом события

  • События порта Маха

  • Пользовательские события, которые Вы инициировали

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

Очереди работы

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

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

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

Для получения дополнительной информации о том, как использовать очереди работы, и как определить пользовательские объекты операции, посмотрите Очереди Работы.

Асинхронные методы проектирования

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

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

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

Определите ожидаемое поведение своего приложения

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

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

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

Факторизуйте исполнимые единицы работы

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

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

Идентифицируйте очереди, в которых Вы нуждаетесь

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

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

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

Подсказки для того, чтобы повысить эффективность

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

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

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

  • Избегайте использования блокировок. Поддержка, предоставленная очередями отгрузки и очередями работы, делает блокировки ненужными в большинстве ситуаций. Вместо того, чтобы использовать блокировки для защиты некоторого совместно используемого ресурса назначьте последовательную очередь (или используйте зависимости от объекта операции) выполнить задачи в правильном порядке.

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

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

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

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

Для введения в производительность и доступные инструменты производительности, и для ссылок к более усовершенствованным связанным с производительностью темам, см. Обзор производительности.

Параллелизм и другие технологии

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

OpenCL и параллелизм

В OS X Открыть Computing Language (OpenCL) является основанной на стандартах технологией для выполнения вычислений общего назначения на графическом процессоре компьютера. OpenCL является хорошей технологией, чтобы использовать, если у Вас есть четко определенный набор вычислений, что Вы хотите примениться к большим наборам данных. Например, Вы могли бы использовать OpenCL, чтобы выполнить вычисления фильтра на пикселях изображения или использовать его для выполнения сложных математических вычислений на нескольких значениях сразу. Другими словами, OpenCL приспособлен больше к проблемным наборам, на данных которых можно управлять параллельно.

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

Для получения дополнительной информации о OpenCL и как Вы используете его, см. Руководство по программированию OpenCL для Mac.

Когда использовать потоки

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

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

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