Хорошая многопоточная разработка преждевременной оптимизации?

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

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

Как вы поддерживаете баланс между оптимизацией и выполнением?

Ответ 1

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

Конечно, если системы начинают иметь 1000+ ядер, тогда этот ответ может стать устаревшим и нуждается в пересмотре. Но опять же, если вы собираетесь "сделать все", то вы окончательно захотите отправить свой товар до этого.

Ответ 2

Если вы следуете шаблонам дизайна Pipeline и Map-Reduce, этого должно быть достаточно.
Разложите вещи так, чтобы вы могли работать в конвейере многопроцессорной обработки уровня.

Затем вы можете запустить фактический конвейер. Никакой дополнительной работы. ОС обрабатывает все. Огромная возможность ускорения.

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

Ответ 3

Представление Threading не повышает производительность автоматически.

Ответ 4

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

Ответ 5

Говорят, что дни кодирования могут сэкономить часы дизайна.

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

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

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

Ответ 6

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

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

Такие вещи мне кажутся важными.

Если он проектируется для многопоточности... тогда это важно преждевременный подход.

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

ИЗМЕНИТЬ, я прочитал его еще раз: преждевременная оптимизация?

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

Ответ 7

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

  • Жесткие контракты const
    • В С++ вы можете пометить метод как const, то есть он не изменит значение переменных экземпляра. Вы также можете пометить входной параметр для метода как const, а это значит, что для этого параметра могут быть вызваны только методы const. Используя эти два метода (и не используя "трюки", чтобы обойти эти принудительные действия компилятора), вы можете очистить операции, которые должны быть многопоточными.
  • Инверсия зависимостей
    • Это общий метод, когда любые внешние объекты, необходимые объекту, передаются ему при построении/времени инициализации или как часть сигнатуры метода для конкретного метода. С помощью этой технологии на 100% ясно, какие объекты могут быть изменены с помощью операции (переменные экземпляра non-const плюс неконстантные параметры для операции). Зная, что вы знаете объем нефункциональных аспектов операцию, и вы можете добавлять мьютексы и т.д. к объектам, которые могут использоваться совместно с параллельными операциями. Затем вы можете создать свой parallelism для правильной и эффективной работы.
  • Благоприятный функционал над процедурной
    • По иронии судьбы, это означает, что преждевременно не оптимизируйте. Сделать объекты ценности неизменяемыми. Например, в С# строки неизменяемы, что означает, что любые операции с ними возвращают новые экземпляры строковых объектов, а не модифицированные экземпляры существующей строки. Единственными объектами, которые не должны быть неизменными, являются неограниченные массивы или объекты, которые содержат неограниченные массивы, если эти массивы, вероятно, будут часто меняться. Я бы сказал, что неизменные объекты легче понять. Многим программистам были преподаны процедурные методы, поэтому это немного чуждо нам, но когда вы начинаете думать в неизменных терминах, ужасные аспекты программирования в прецедентах, такие как порядок зависимости деятельности и побочные эффекты, уходят. Эти аспекты еще более ужасны в многопоточном программировании, поэтому использование функционального стиля в дизайне классов помогает во многих отношениях. Поскольку машины растут быстрее, более высокая стоимость неизменяемых объектов становится проще и легче оправдывать. Сегодня это баланс.

Ответ 8

Существуют потоки, чтобы упростить работу с несколькими агентами.

  • Если агенты являются пользователями, например, если у вас есть поток за пользователя, они упрощают запись программы. Это не проблема производительности, это проблема легкости написания.

  • Если агенты являются устройствами ввода/вывода, они упрощают запись программы, которая делает операции ввода-вывода параллельно. Это может быть или не быть сделано для производительности.

  • Если агенты являются ядрами ЦП, они упрощают запись программ, которые параллельны нескольким ядрам. То есть, когда потоки соотносятся с производительностью.

Другими словами, если вы думаете, что threads == parallelism == performance, это касается только одного из видов использования потоков.

Ответ 9

Существует три основных варианта дизайна: синхронизация, Async или Sync + multi threaded. Выберите один или несколько, если ваш безумный гениальный.

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

Если синхронизация не отвечает требованиям клиента:

Системы с ограниченным доступом требуют выбора многопоточного/процесса

IO ограниченные системы (чаще всего) часто могут быть либо Async, либо MT.

Для IO ограниченных технологий использования, таких как потоки состояния, вы можете получить свой торт и съесть его. (Выполнение синхронизации /w async)

Ответ 10

Каков ваш способ сохранить баланс между оптимизацией и получением что делать?

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

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

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

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

Тем не менее, все это можно было бы избежать, если бы программное обеспечение просто разработало свои абстракции на более грубом уровне, например IImage, и избегало подвергать отдельные пиксельные объекты остальной части системы. Изображение фактически представляет собой набор пикселей (часто миллионы пикселей), и он может обеспечивать операции, обрабатывающие много пикселей за один раз. Теперь затраты на обработку и память, связанные с обрабатывающими пикселями, уменьшены для изображения с миллионным пикселем до 1/1,000,000-го, и в этот момент он становится тривиализованным. Это также приводит к тому, что операции с изображениями содержат много дышащей комнаты, чтобы параллельно делать пиксели процесса и векторизовать теперь на центральном уровне, не переписывая эпические суммы кода, когда клиентский код не обрабатывает отдельно один пиксель за раз, а вместо этого запрашивает целую операции изображения.

Хотя это может показаться неистовым с обработкой изображений, который по своей сути является очень критичным для производительности полем, в других доменах есть много возможностей. С вашим примером классического наследования вам не нужно делать Dog inherit Mammal. Вы можете сделать Dogs inherit Mammals.

Итак, вернемся к делу, я начинаю с ориентированного на данные мышления не для того, чтобы получить наиболее эффективные для кэширования, смущающие параллельные, потокобезопасные, SIMD-дружественные представления данных и передовые структуры данных и алгоритмы на первая попытка. В противном случае я мог бы потратить целую неделю, просто настраивая вещи с помощью VTune в руке, наблюдая, что тесты идут быстрее и быстрее (я люблю делать это, но это определенно неэффективно, чтобы делать всюду и вперед). Я только задумался над этим, чтобы определить соответствующий уровень детализации, который я должен использовать для разработки вещей: "Должен ли я заставлять систему зависеть от Dog или Dogs?", Такого рода вещи. И это даже не требует много размышлений. Для ООП это нравится: "обрабатывает ли система сто тысяч собак каждый отдельный кадр? Да/нет?" Если "да", не создавайте центральный интерфейс Dog и не создавайте центральный интерфейс IMammal. Design Dogs для наследования IMammals, так же, как мы избегаем интерфейса IPixel в сценарии обработки аналогового изображения выше, если мы собираемся обрабатывать миллионы пикселей за раз.

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

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

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