Чи слід уникати 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, попросивши цей екземпляр програми виконати функцію2.

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

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

Я сподіваюся, що цим я прояснив деякі питання / сумніви.


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

5
По суті, вам потрібна людина, якій призначено "менеджер збірок" та "менеджер випусків", щоб усі проекти C ++ збиралися на одній версії компілятора та з однаковими налаштуваннями компілятора C ++, складеними з послідовного знімка (версії) джерела код і т. д. Зазвичай це вирішується під прапором "постійної інтеграції". Якщо ви шукаєте в Інтернеті, ви знайдете безліч статей та інструментів. Застарілі практики можуть самозміцнюватися - одна застаріла практика може призвести до того, що всі практики застаріли.
rwong

8
У прийнятій відповіді у зв’язаному запитанні зазначено, що проблема полягає у загальних викликах C ++. Тож "C ++, але не STL" не допомагає, вам потрібно їхати з голим C, щоб бути на безпеці (але також бачити відповіді, серіалізація, ймовірно, краще рішення).
Frax

52
динамічне завантаження при необхідності та розвантаження після цього, щоб мати можливість вирішувати проблеми продуктивності Що таке "продуктивність"? Я не знаю жодних проблем, крім використання занадто великої кількості пам'яті, яку можна виправити, вивантаживши такі речі, як DLL, з пам'яті - і якщо це проблема, найпростіше виправити просто придбати більше оперативної пам’яті. Ви профілювали свою заявку, щоб визначити фактичні вузькі місця? Тому що це звучить як проблема XY - у вас не визначені "проблеми з продуктивністю", і хтось уже вирішив рішення.
Ендрю Генле

4
@MaxBarraclough "STL" чудово сприймається як альтернативна назва шаблонованих контейнерів та функцій, які були включені в стандартну бібліотеку C ++. Насправді основні настанови C ++, написані Bjarne Stroustrup та Herb Sutter, неодноразово посилаються на "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 в стилі С, написані таким чином: явний безкоштовний API для кожного API, що передає виділений масив / структуру. Додаткова проблема з STL полягає в тому, що абонент може очікувати, що зможе змінити передану складну структуру даних (додавання / видалення елементів), і це теж не можна дозволити. Але це важко виконати.
давидбак

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

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

20

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

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

Кеш коштує дорожче, але це впливає лише на недавно активний код, а не на код, який не використовується у пам'яті.

Зазвичай програми переростають свої комп’ютери через розмір даних або час процесора, а не розмір коду. Якщо розмір вашого коду стає настільки великим, що це спричиняє великі проблеми, то, ймовірно, ви хочете розглянути, чому це відбувається в першу чергу.

Але: функції, які ми використовуємо, передають параметр вводу та виводу як об'єкти STL, і, як згадується в цій StackOverflow URL, це дуже погана ідея.

Це повинно бути нормально, доки DLL-файли та виконуваний файл створюються одним і тим же компілятором і динамічно пов'язані з тією ж бібліотекою виконання C ++. Звідси випливає, що якщо додаток та пов’язані з ним DLL будуються та розгортаються як єдиний блок, то це не повинно бути проблемою.

Де це може стати проблемою, коли бібліотеки побудовані різними людьми або можуть бути оновлені окремо.

Чи гарно зробити висновок, що, якщо ви плануєте створити додаток, який може вирости настільки великим, що один єдиний ПК більше не може з ним працювати, ви не повинні використовувати STL як технологію взагалі?

Не зовсім.

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


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

1
@Jules: Якщо мертвий код змішується з прямим кодом (з розміром сторінки = 4k деталізація), він буде зіставлений + завантажений. Кеш працює на більш тонку (64B) деталізацію, тому все ще правда, що невикористані функції не сильно шкодять. Кожна сторінка потребує запису TLB та (на відміну від оперативної пам’яті), що є обмеженим ресурсом виконання. (Зображення, що підтримуються файлами, зазвичай не використовують величезні сторінки, принаймні не для 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 ТБ оперативної пам’яті. У ньому достатньо кешу для запуску більшості програм, не торкаючись жодної основної оперативної пам’яті. Навіть без GPU це настільки потужно, як 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.