Як трубопроводи обмежують використання пам'яті?


36

Браян Керніган пояснює у цьому відео раннє залучення Bell Labs до малих мов / програм, що базується на обмеженні пам'яті

Велика машина мала б 64 кбайт - K, а не M чи G - і це означало, що будь-яка окрема програма не може бути дуже великою, і тому існувала природна тенденція писати невеликі програми, а потім механізм труби, в основному перенаправлення вводу, що дозволило з'єднати одну програму з іншою.

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

З Вікіпедії :

У більшості Unix-подібних систем усі процеси трубопроводу запускаються одночасно [моє наголос], з їх потоками належним чином підключених та керованих планувальником разом з усіма іншими процесами, що працюють на машині. Важливим аспектом цього, що відрізняє труби Unix від інших реалізацій труб, є концепція буферизації: наприклад, програма відправлення може виробляти 5000 байт в секунду, а приймаюча програма може приймати лише 100 байт в секунду, але ні дані втрачаються. Натомість вихід програми передачі зберігається в буфері. Коли приймаюча програма готова для читання даних, наступна програма в конвеєрі зчитується з буфера. В Linux розмір буфера становить 65536 байт (64 КБ). Сторонній фільтр із відкритим кодом під назвою bfr доступний для надання більших буферів, якщо потрібно.

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

Єдине, що я можу розглянути як своє перше питання (обмеження пам'яті проблематично залежать від даних про розмір) - це те, що великі набори даних тоді просто не були обчислені, і справжні трубопроводи мали бути вирішені. об'єм пам'яті, необхідний самим програмам. Але з огляду на жирний текст у цитаті у Вікіпедії, це навіть мене бентежить: оскільки одна програма не реалізується одночасно.

Все це мало б великий сенс, якби використовувались тимчасові файли, але я розумію, що труби не записуються на диск (якщо не використовується swap).

Приклад:

sed 'simplesubstitution' file | sort | uniq > file2

Мені зрозуміло, що sedце читання у файлі та виплюнення його по черзі за рядком. Але sort, як заявляє БК у пов'язаному відео, це повна зупинка, тому всі дані повинні бути прочитані в пам’яті (чи це?), А потім передані в те uniq, що (на мій погляд) було б одним -лінійна програма. Але між першою та другою трубкою всі дані повинні бути в пам'яті, ні?


1
unless swap is usedswap завжди використовується, коли не вистачає оперативної пам’яті
edc65

Відповіді:


44

Дані не потрібно зберігати в оперативній пам'яті. Труби блокують своїх авторів, якщо читачів немає там чи не можуть бути в курсі; в Linux (і більшості інших реалізацій, я думаю, є деякі буферизації, але це не потрібно). Як згадують mtraceur та JdeBP (див . Відповідь останнього), ранні версії буферизованих труб на Unix на диск, і ось вони допомогли обмежити використання пам’яті: обробний конвеєр можна розділити на невеликі програми, кожна з яких оброблятиме деякі дані в межах дискових буферів. Невеликі програми займають менше пам’яті, а використання каналів означало, що обробка може бути серіалізована: перша програма запуститься, заповнить її вихідний буфер, буде призупинено, потім друга програма буде запланована, обробляється буфер тощо. Сучасні системи - це замовлення на величину більший, ніж ранні системи Unix, і може паралельно працювати безліч труб; але для величезної кількості даних ви все одно побачите подібний ефект (а варіанти подібної методики використовуються для обробки "великих даних").

У вашому прикладі

sed 'simplesubstitution' file | sort | uniq > file2

sedчитає дані з fileнеобхідності, а потім записує їх до тих пір, sortпоки готовий їх прочитати; якщо sortвона не готова, блоки запису. Дані дійсно в кінцевому підсумку живуть у пам'яті, але це специфічно sortі sortготово вирішувати будь-які проблеми (він використовуватиме тимчасові файли, оскільки кількість даних для сортування занадто великий).

Ви можете бачити поведінку блокування, запустивши

strace seq 1000000 -1 1 | (sleep 120; sort -n)

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


13
Ця відповідь могла б отримати додаткове пояснення, чому розщеплення програм на багато маленьких заощаджує використання пам'яті: Програма повинна мати можливість вмістити в пам'ять для запуску, але тільки поточну програму, що працює . Будь-яка інша програма була замінена на диск на початку Unix, лише одна програма одночасно помінялася на фактичну оперативну пам'ять. Так процесор запустив би одну програму, яка записувала б у трубу (яка тоді була на диску ), поміняла цю програму і поміняла програму, що читалася з труби. Елегантний спосіб перетворити логічно паралельну конвеєрну лінію в поступовий серійний варіант виконання.
mtraceur

6
@malan: Кілька процесів можна запустити і одночасно можуть перебувати в стані, який можна виконати. Але щонайбільше один процес може бути виконаний на кожному фізичному процесорі в будь-який момент часу, і це завдання планувальника процесів ядра виділяти "фрагменти" часу процесора кожному процесу, який можна виконати. У сучасних системах процес, який можна запустити, але наразі не заплановано, тимчасовий відрізок процесора, як правило, залишається в пам'яті, поки він чекає свого наступного фрагмента, але ядро ​​дозволено знову розміщувати пам'ять будь-якого процесу на диску та повертатися в пам'ять як це вважає зручним. (Подаємо сюди деякі деталі.)
Даніель Приден

5
Процеси з будь-якої сторони труби можуть вести себе ефективно як спільні процедури: одна сторона записує, поки вона не заповнить буфер і блоки запису, і в цей момент процес нічого не може зробити з рештою часового відрізка і переходить у Режим очікування IO. Тоді ОС видає решту часового фрагмента (або іншої майбутньої часової зріз) стороні читання, яка читає, поки в буфері та наступних блоках читання нічого не залишилося, і тоді процес читання нічого не може зробити з рештою її часовий відрізок і повертається назад в ОС. Дані за один раз коштують через трубу.
Даніель Приден

6
@malan Програми запускаються «одночасно» концептуально на всіх системах Unix, просто на сучасних багатопроцесорних системах з достатньою кількістю оперативної пам’яті, щоб утримувати їх, тобто значить, вони буквально всі утримуються в оперативній пам’яті одночасно, тоді як в системі, яка може не втримуйте їх одночасно в оперативній пам’яті, а деякі заміняються на диск. Також зауважте, що "пам'ять" у багатьох контекстах означає віртуальну пам'ять, яка є сумою як простору оперативної пам’яті, так і місцями обміну на диску. Вікіпедія орієнтується на концепцію, а не на деталі впровадження, тим більше, що те, наскільки справді старі Unix робили речі, зараз менш актуальне.
mtraceur

2
@malan Також суперечливість, яку ви бачите, випливає з двох різних значень "пам'яті" (оперативна пам'ять проти оперативної пам'яті + своп). Я говорив тільки про апаратну оперативну пам’ять, і в цьому контексті в код оперативної пам’яті повинен входити лише код, який зараз виконується процесором (саме це було наслідком рішень, про які говорить Керніган), тоді як у контексті всіх програм логічно виконується ОС в даний момент часу (на абстрактному рівні, який надається на відрізку часу), програмі просто потрібно поміститись у всю віртуальну пам'ять, доступну для ОС, яка включає в себе простір на диску на диску.
mtraceur

34

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

Це ваша основна помилка. Ранні версії Unix не містили даних про трубу в оперативній пам'яті. Вони зберігали їх на диску. Труби мали i-вузли; на дисковому пристрої, який позначався трубним пристроєм . Системний адміністратор запустив програму з назвою /etc/configвказати (серед іншого), який об'єм, на якому диску був трубний пристрій, який об'єм був кореневим пристроєм та який дамп-пристрій .

Кількість відкладених даних обмежувалося тим, що для зберігання використовувались лише прямі блоки i-вузла на диску. Цей механізм спростив код, тому що для читання з труби був використаний такий самий алгоритм, як і для читання для звичайного файлу, з деякими налаштуваннями, спричиненими тим, що труби не шукаються, а буфер - круговим.

Цей механізм був замінений іншими в середині-кінці 1980-х. SCO XENIX отримав "Високопродуктивну трубопровідну систему", яка замінила i-вузли внутрішніми буферами. 4BSD зробив безіменні труби в розетки. AT&T повторно впроваджував труби за допомогою механізму STREAMS.

І звичайно, sortпрограма виконувала обмежений внутрішній сорт вхідних частин 32KiB (або будь-який менший об'єм пам'яті, який він міг би виділити, якщо 32KiB не був доступний), записуючи відсортовані результати до проміжних stmX??файлів, у /usr/tmp/яких потім зовнішньо злиття відсортовано для отримання остаточного вихід.

Подальше читання

  • Стів Д. Пат (1996). "Міжпроцесовий зв'язок". UNIX - внутрішній: практичний підхід . Аддісон-Веслі. ISBN 9780201877212.
  • Моріс Дж. Бах (1987). "Виклики системи для файлової системи". Дизайн операційної системи Unix . Prentice-Hall. ISBN 0132017571.
  • Стівен В. Ерхарт (1986). " config(1М)". Посібник програміста Unix: 3. Засоби системного адміністрування . Холт, Рінехарт і Вінстон. ISBN 0030093139. С. 23–28.

1

Ви частково правильні, але лише випадково .

У вашому прикладі всі дані повинні бути прочитані "між" трубами, але вони не повинні залишатися в пам'яті (включаючи віртуальну пам'ять). Зазвичай реалізація sortможе сортувати набори даних, які не впишуться в оперативну пам’ять, роблячи часткові сортування тимчасових файлів та об'єднуючись. Однак, це факт, що ви не можете вивести відсортовану послідовність, перш ніж прочитати кожен елемент. Це досить очевидно. Так, так, sortможна починати виводити на другу трубу лише після того, як прочитав (і зробив все, що можливо, частково сортувавши темпфіли) все з першого. Але не обов'язково все це зберігати в оперативній пам'яті.

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

На сьогоднішній день труби - це невеликий буфер ядра кінцевого розміру, до якого копіюються дані, принаймні, так відбувається концептуально . Якщо ядро ​​може в цьому допомогти, копії видаляються за допомогою відтворення VM-трюків (наприклад, передача файлів із файлу зазвичай просто робить ту саму сторінку для читання іншим процесом, тож, нарешті, це лише операція читання, а не дві копії і ні потрібна додаткова пам'ять, ніж уже використана кеш-пам'ять буфера. У деяких ситуаціях ви також можете отримати 100% нульову копію. Або щось дуже близьке.

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

Філософія багатьох простих програм була найкориснішою колись, коли пам’ять була дуже дефіцитною. Тому що, ну, ви могли робити роботу невеликими кроками, по одному. Сьогодні переваги, окрім деякої додаткової гнучкості, я, смію, вже не дуже велика.
Однак труби реалізовані дуже ефективно (вони повинні були бути!), Тому недоліків теж немає, і це усталена річ, яка добре працює і до якої люди звикли, тому не потрібно змінювати парадигму.


Коли ви говорите «труби були названі» (схоже, JdeBP сказав, що був один «трубний пристрій»), чи означає це, що було обмежено кількість труб, які могли бути використані в даний момент часу (тобто, обмеження на скільки разів ви могли використовувати |в команді)?
малан

2
Я ніколи не бачив такого обмеження, і не думаю, що в теорії коли-небудь був такий. На практиці все, що має ім'я файлу, потребує inode, а кількість inode, звичайно, кінцева. Як і кількість фізичних сторінок у системі, якщо нічого іншого. Сучасні системи гарантують 4 к атомних записів, тому кожна труба повинна мати принаймні одну повну сторінку 4k, що встановлює жорсткий ліміт кількості труб, які ви можете мати. Але врахуйте наявність декількох гігабайт оперативної пам’яті… практично, це обмеження, з яким ви ніколи не зіткнетесь. Спробуйте набрати кілька мільйонів труб на терміналі ... :)
Деймон,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.