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

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

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

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

10 ответов


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

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


Если вы следуете трубопровод и Карта- > Уменьшить шаблоны проектирования, этого должно быть достаточно.
Разложите вещи так, чтобы вы мог бы запуск в конвейере многоуровневой обработки ОС.

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

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


введение резьбы автоматически не увеличивает производительность.


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


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

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

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

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


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

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

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

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

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

редактировать Ой, я снова прочитал: преждевременная оптимизация?

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


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

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

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

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

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

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

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


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

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

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

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

ограниченные системы ввода-вывода (наиболее распространенные) часто могут идти либо асинхронно, либо MT.

для IO limited leveraging технологии, такие как государственные потоки могут позволить вам иметь свой торт и съесть его тоже. (Sync design /w асинхронное выполнение)


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

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

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

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

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

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

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

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

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

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

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