Следует ли избегать STL в больших приложениях?


24

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

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

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

Очевидно, что мы могли бы заменить параметры ввода / вывода стандартными типами C ++ и создать объекты STL из тех, которые когда-то были внутри функций, но это может привести к падению производительности.

Можно ли сделать вывод, что, если вы планируете создать приложение, которое может вырасти настолько большим, что один ПК больше не сможет с ним работать, вы вообще не должны использовать STL в качестве технологии?

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

+-----------+-----------+----
| Machine1  | Machine2  | ...
| App_Inst1 | App_Inst2 | ...
|           |           |    
| DLL1.1    | DLL2.1    | ...
| DLL1.2    | DLL2.2    | ...
| DLL1.x    | DLL2.x    | ...
+-----------+-----------+----

App_Inst1 - это экземпляр приложения, установленного на Machine1, а App_Inst2 - это экземпляр того же приложения, установленного на Machine2.
DLL1.x - это DLL, установленная на Machine1, а DLL2.x - это DLL, установленная на Machine2.
DLLx.1 охватывает экспортируемую функцию1.
DLLx.2 покрывает экспортируемую функцию2.

Теперь на Machine1 я бы хотел выполнить function1 и function2. Я знаю, что это перегрузит Machine1, поэтому я хотел бы отправить сообщение в App_Inst2 с просьбой, чтобы этот экземпляр приложения выполнил function2.

Параметры ввода / вывода для function1 и function2 являются объектами STL (стандартная библиотека типов C ++), и регулярно я могу ожидать, что клиент будет обновлять App_Inst1, App_Inst2, DLLx.y (но не все из них, клиент может обновить Machine1, но не Machine2, или только обновить приложения, но не библиотеки DLL или наоборот, ...). Очевидно, что если интерфейс (параметры ввода / вывода) изменится, то клиент вынужден выполнить полную модернизацию.

Однако, как упомянуто в упомянутом URL-адресе StackOverflow, простая повторная компиляция App_Inst1 или одной из библиотек DLL может привести к развалу всей системы, поэтому мой первоначальный заголовок этого поста не рекомендует использовать STL (стандартный шаблон C ++ Библиотека) для больших приложений.

Я надеюсь, что этим я прояснил некоторые вопросы / сомнения.


44
Вы уверены, что у вас проблемы с производительностью из-за размера исполняемого файла ? Можете ли вы добавить некоторые подробности о том, реалистично ли предполагать, что все ваше программное обеспечение скомпилировано с одним и тем же компилятором (например, за один раз на сервере сборки), или вы действительно хотите разделить на независимые команды?
nvoigt

5
В основном вам нужен человек, чья специальная работа - «менеджер сборки» и «менеджер релизов», чтобы гарантировать, что все проекты C ++ компилируются в одной и той же версии компилятора и с идентичными настройками компилятора C ++, скомпилированными из согласованного снимка (версии) исходного кода. код и т. д. Обычно об этом заботятся под знаменем «непрерывной интеграции». Если вы ищете в Интернете, вы найдете много статей и инструментов. Устаревшие практики могут самоутвердиться - одна устаревшая практика может привести к устареванию всех практик.
Руонг

8
Принятый ответ в связанном вопросе гласит, что проблема в вызовах C ++ в целом. Таким образом, «C ++, но не STL» не помогает, вам нужно идти с голым C, чтобы быть в безопасности (но также посмотрите ответы, сериализация, вероятно, является лучшим решением).
Фракс

52
динамическая загрузка при необходимости и последующая выгрузка для решения проблем с производительностью Какие «проблемы с производительностью»? Я не знаю каких-либо проблем, кроме использования слишком большого объема памяти, который можно исправить, выгружая из памяти такие вещи, как библиотеки DLL, и, если это проблема, проще всего просто купить больше оперативной памяти. Профилировали ли вы свое приложение, чтобы определить фактические узкие места производительности? Потому что это звучит как проблема XY - у вас есть неопределенные «проблемы с производительностью», и кто-то уже определился с решением.
Эндрю Хенле

4
@MaxBarraclough "STL" прекрасно воспринимается как альтернативное имя для шаблонных контейнеров и функций, которые были включены в стандартную библиотеку C ++. Фактически, в Основных рекомендациях C ++, написанных Бьярном Страуструпом и Хербом Саттером, при упоминании об этом постоянно упоминается «STL». Вы не можете получить гораздо более авторитетный источник, чем этот.
Шон Бертон,

Ответы:


110

Это классическая проблема XY.

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

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

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


54
«Вместо этого вы надеетесь, что разбиение вашего кода на библиотеки DLL волшебным образом решит проблему (чего, к сожалению, не будет)» - +1 за это. Ваша операционная система почти наверняка реализует требуют подкачки , который достигает точно такой же результат, загрузки и разгрузки функциональности библиотек DLL, только автоматически , а не требует ручного вмешательства. Даже если вы лучше прогнозируете, сколько времени кусок кода должен находиться после использования, чем система виртуальной памяти ОС (что на самом деле маловероятно), ОС все равно будет кэшировать файл DLL и в любом случае сведет на нет ваши усилия .
Жюль

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

2
@Izkata - все еще не совсем ясно, но я думаю, что они описывают то, что они хотят динамически выбирать (в зависимости от конфигурации времени выполнения) версию каждой функции, которая является локальной или удаленной. Но любая часть файла EXE, которая никогда не используется на данном компьютере, просто никогда не будет загружена в память, поэтому использование DLL для этой цели не является необходимым. Просто включите обе версии всех функций в стандартную сборку и создайте таблицу указателей функций (или вызываемых объектов C ++, или любого другого метода, который вы предпочитаете), чтобы вызвать соответствующую версию каждой функции.
Жюль

38

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

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


1
Да, и особенно часть о передаче выделенных объектов через DLL / границы. Вообще говоря, единственный способ полностью избежать проблемы множественных распределителей состоит в том, чтобы гарантировать, что DLL / so (или библиотека!), Которая распределила структуру, также освобождает ее. Вот почему вы видите множество API-интерфейсов в стиле C, написанных таким образом: явный бесплатный API-интерфейс для каждого API, передающий обратно выделенный массив / структуру. Дополнительная проблема с STL заключается в том, что вызывающая сторона может ожидать, что она сможет изменить переданную сложную структуру данных (добавить / удалить элементы), и это тоже не может быть разрешено. Но это трудно осуществить.
Давидбак

1
Если бы мне пришлось разделить приложение таким образом, я бы, вероятно, использовал бы COM, но это обычно увеличивает размер кода, так как каждый компонент приносит свои собственные библиотеки C и C ++ (которые могут совместно использоваться, когда они одинаковы, но могут расходиться при необходимости, например, во время переходов. Я не уверен, что это подходящее решение проблемы ОП, однако
Саймон Рихтер

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

20

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

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

Кэш дороже, но это влияет только на код, который недавно активен, а не код, который находится в памяти неиспользованным.

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

Но: функции, которые мы используем, передают входной и выходной параметры как объекты STL, и, как упоминалось в этом URL-адресе StackOverflow, это очень плохая идея.

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

Проблема может возникнуть, когда библиотеки создаются разными людьми или могут обновляться отдельно.

Можно ли заключить, что в случае, если вы планируете создать приложение, которое может вырасти настолько большим, что один ПК больше не сможет с ним работать, вы вообще не должны использовать STL в качестве технологии?

На самом деле, нет.

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


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

1
@Jules: Если мертвый код смешивается с живым кодом (с размером страницы = гранулярность 4k), он будет отображен + загружен. Кэш-память работает с более высокой степенью детализации (64B), поэтому все еще в основном верно, что неиспользуемые функции не причиняют большого вреда. Тем не менее, каждая страница нуждается в записи TLB и (в отличие от RAM), которая является дефицитным ресурсом времени выполнения. (При отображениях с файловой поддержкой обычно не используются огромные страницы, по крайней мере, в Linux; одна огромная страница имеет размер 2 МБ на платформе x86-64, поэтому вы можете охватить гораздо больше кода или данных, не пропуская при этом TLB с помощью огромных страниц.)
Питер Кордес,

1
Что @PeterCordes отмечает: Итак, обязательно используйте «PGO» как часть процесса сборки для выпуска!
JDługosz

13

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

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


7

Вы упускаете суть этого вопроса.

Есть в основном два типа DLL. Ваш и чужой. «Проблема STL» заключается в том, что вы и они, возможно, не используете один и тот же компилятор. Очевидно, что это не проблема для вашей собственной DLL.


5

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

Однако «приправленный Windows» способ разбить приложение на несколько частей, некоторые из которых можно использовать повторно, это COM-компоненты . Они могут быть маленькими (отдельные элементы управления или кодеки) или большими (IE доступен как элемент управления COM, в mshtml.dll).

динамическая загрузка при необходимости и разгрузка после

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

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

может вырасти настолько большим, что один ПК не сможет с этим справиться

Купите большой компьютер.

Не забывайте, что при правильной оптимизации ноутбук может превзойти кластер Hadoop.

Если вам действительно нужно несколько систем, вы должны очень тщательно продумать границу между ними, поскольку именно здесь и стоит стоимость сериализации. Это где вы должны начать смотреть на фреймворки, такие как MPI.


1
«Это действительно жизнеспособно, только если у вас есть приложение, которое проходит несколько этапов в течение длительного периода времени, чтобы вы знали, когда что-то больше не понадобится» - даже тогда это вряд ли поможет, потому что ОС будет кэшируйте файлы DLL, которые, скорее всего, потребуют больше памяти, чем просто включение функций непосредственно в ваш базовый исполняемый файл. Наложения полезны только в системах без виртуальной памяти или когда ограничивающим фактором является виртуальное адресное пространство (я предполагаю, что это приложение 64-разрядное, а не 32 ...).
Жюль

3
«Купи большой компьютер» +1. Теперь вы можете приобрести системы с несколькими терабайтами оперативной памяти. Вы можете нанять один из Amazon по цене ниже, чем почасовая ставка для одного разработчика. Сколько времени разработчик собирается потратить на оптимизацию кода для уменьшения использования памяти?
Жюль

2
Самая большая проблема, с которой я столкнулся при покупке «большего ПК», была связана с вопросом «как далеко будет масштабироваться ваше приложение?». Мой ответ был: «Сколько вы готовы потратить на тест? Потому что я ожидаю, что он будет настолько масштабным, что аренда правильной машины и установка достаточно большого теста обойдется в тысячи долларов. Ни один из наших клиентов даже близко не подходит». что может сделать однопроцессорный ПК ". Многие старые программисты не имеют ни малейшего представления о том, сколько ПК выросло; Одна только видеокарта в современных ПК является суперкомпьютером по стандартам 20-го века.
MSalters

COM-компоненты? Может быть, в 1990-х, но сейчас?
Питер Мортенсен

@MSalters - верно ... всем, у кого есть какие-либо вопросы относительно масштабирования приложения на одном ПК, следует ознакомиться со спецификациями для типа экземпляра Amazon EC2 x1e.32xlarge - всего 72 физических процессорных ядра в машине, обеспечивающих 128 виртуальных ядер на 2,3 ГГц (с возможностью расширения до 3,1 ГГц), потенциально до пропускной способности памяти 340 ГБ / с (в зависимости от того, какой тип памяти установлен, что не описано в спецификации), и 3,9 ТБ ОЗУ. Он имеет достаточно кеша для запуска большинства приложений, даже не касаясь основной оперативной памяти. Даже без графического процессора он такой же мощный, как кластер суперкомпьютеров с 500 узлами 2000 года.
Жюль

0

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

Первая часть имеет смысл (разделение приложения на разные машины по соображениям производительности).

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

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

Классическое решение выглядит так:

[user] [front-end] [machine1] [common resources]
                   [machine2]
                   [machine3]

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

Это никоим образом не подразумевает дополнительную загрузку / выгрузку DLL, а также не имеет ничего общего с STL.

То есть используйте STL по мере необходимости и сериализуйте ваши данные между элементами (см. Grpc и буферы протокола и вид проблем, которые они решают).

Тем не менее, с учетом предоставленной вами ограниченной информации, это похоже на классическую проблему xy (как сказал @Graham).

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.