Brian Kernighan explica neste vídeo a atração inicial do Bell Labs por pequenas linguagens/programas baseados em limitações de memória
Uma máquina grande teria 64 k-bytes - K, não M ou G - e isso significava que qualquer programa individual não poderia ser muito grande, e então havia uma tendência natural de escrever programas pequenos, e então o mecanismo de pipe, basicamente o redirecionamento de entrada e saída, tornou possível vincular um programa a outro.
Mas não entendo como isso pode limitar o uso de memória, considerando o fato de que os dados precisam ser armazenados na RAM para transmitir entre os programas.
Da Wikipédia :
Na maioria dos sistemas do tipo Unix, todos os processos de um pipeline são iniciados ao mesmo tempo [ênfase minha], com seus fluxos adequadamente conectados e gerenciados pelo agendador junto com todos os outros processos em execução na máquina. Um aspecto importante disso, separando os pipes Unix de outras implementações de pipe, é o conceito de buffer: por exemplo, um programa de envio pode produzir 5.000 bytes por segundo, e um programa de recebimento pode aceitar apenas 100 bytes por segundo, mas não dados são perdidos. Em vez disso, a saída do programa de envio é mantida no buffer. Quando o programa receptor estiver pronto para ler os dados, o próximo programa no pipeline fará a leitura do buffer. No Linux, o tamanho do buffer é de 65.536 bytes (64 KB). Um filtro de terceiros de código aberto chamado bfr está disponível para fornecer buffers maiores, se necessário.
Isso me confunde ainda mais, pois isso anula completamente o propósito de pequenos programas (embora eles sejam modulares até uma certa escala).
A única coisa que posso pensar como uma solução para minha primeira pergunta (as limitações de memória sendo problemáticas dependendo do tamanho dos dados) seria que grandes conjuntos de dados simplesmente não eram computados na época e os pipelines de problemas reais deveriam resolver era o quantidade de memória exigida pelos próprios programas. Mas dado o texto em negrito na citação da Wikipedia, até isso me confunde: como um programa não é implementado de cada vez.
Tudo isso faria muito sentido se os arquivos temporários fossem usados, mas é meu entendimento que os pipes não gravam no disco (a menos que a troca seja usada).
Exemplo:
sed 'simplesubstitution' file | sort | uniq > file2
Está claro para mim que sed
está lendo o arquivo e cuspindo-o linha por linha. Mas sort
, como BK afirma no vídeo vinculado, é um ponto final, então todos os dados precisam ser lidos na memória (ou é?), então são passados para uniq
, o que (na minha opinião) seria um -programa linha por vez. Mas entre o primeiro e o segundo pipe, todos os dados têm que estar na memória, não?
Os dados não precisam ser armazenados na RAM. Pipes bloqueiam seus escritores se os leitores não estiverem lá ou não puderem acompanhar; no Linux (e na maioria das outras implementações, imagino) há algum buffer, mas isso não é necessário. Como mencionado por mtraceur e JdeBP (veja a resposta deste último), versões anteriores do Unix buffered pipes to disk, e foi assim que eles ajudaram a limitar o uso de memória: um pipeline de processamento poderia ser dividido em pequenos programas, cada um dos quais processaria alguns dados, dentro dos limites dos buffers de disco. Programas pequenos ocupam menos memória, e o uso de pipes significava que o processamento poderia ser serializado: o primeiro programa seria executado, preencheria seu buffer de saída, seria suspenso, então o segundo programa seria escalonado, processar o buffer, etc. de magnitude maior do que os primeiros sistemas Unix, e pode executar muitos tubos em paralelo; mas para grandes quantidades de dados, você ainda veria um efeito semelhante (e variantes desse tipo de técnica são usadas para processamento de “big data”).
No seu exemplo,
sed
lê os dadosfile
conforme necessário e depois os grava enquantosort
estiver pronto para lê-los; sesort
não estiver pronto, os blocos de gravação. Os dados de fato vivem na memória eventualmente, mas isso é específico para osort
, esort
está preparado para lidar com quaisquer problemas (ele usará arquivos temporários se a quantidade de dados a ser classificada for muito grande).Você pode ver o comportamento de bloqueio executando
Isso produz uma quantidade razoável de dados e os canaliza para um processo que não está pronto para ler nada nos primeiros dois minutos. Você verá uma série de
write
operações passarem, mas muito rapidamenteseq
irá parar e esperar os dois minutos se passarem, bloqueados pelo kernel (awrite
chamada do sistema espera).Este é o seu erro fundamental. As primeiras versões do Unix não continham dados de pipe na RAM. Eles os armazenaram em disco. Pipes tinham i-nodes; em um dispositivo de disco que foi indicado como dispositivo de tubo . O administrador do sistema executou um programa chamado
/etc/config
para especificar (entre outras coisas) qual volume em qual disco era o dispositivo de pipe, qual volume era o dispositivo raiz e qual era o dispositivo de despejo .A quantidade de dados pendentes foi limitada pelo fato de que apenas os blocos diretos do i-node no disco foram usados para armazenamento. Esse mecanismo tornou o código mais simples, porque para a leitura de um pipe foi empregado o mesmo algoritmo usado para a leitura de um arquivo comum, com alguns ajustes causados pelo fato de que os pipes não são pesquisáveis e o buffer é circular.
Esse mecanismo foi substituído por outros em meados da década de 1980. A SCO XENIX ganhou o "Sistema de Pipe de Alto Desempenho", que substituiu os i-nodes por buffers in-core. O 4BSD transformou tubos sem nome em pares de soquetes. A AT&T reimplementou os tubos usando o mecanismo STREAMS.
E é claro que o
sort
programa executou um tipo interno limitado de pedaços de entrada de 32KiB (ou qualquer quantidade menor de memória que pudesse alocar se 32KiB não estivesse disponível), gravando os resultados classificados emstmX??
arquivos intermediários nos/usr/tmp/
quais, em seguida, mesclava externamente para fornecer o resultado final. resultado.Leitura adicional
config
(1M)". Manual do Programador Unix: 3. Facilidades de Administração do Sistema . Holt, Rinehart e Winston. ISBN 0030093139. pp. 23–28.Você está parcialmente correto, mas apenas por acidente .
No seu exemplo, todos os dados devem realmente ter sido lidos "entre" os pipes, mas não precisam estar residentes na memória (incluindo a memória virtual). As implementações usuais de
sort
podem classificar conjuntos de dados que não cabem na RAM fazendo classificações parciais em arquivos temporários e mesclando. No entanto, é um fato que você não pode produzir uma sequência ordenada antes de ler cada elemento. Isso é bastante óbvio. Então, sim,sort
só pode começar a enviar para o segundo pipe depois de ler (e fazer o que for, possivelmente classificar parcialmente os arquivos temporários) tudo desde o primeiro. Mas não precisa necessariamente manter tudo na RAM.No entanto, isso não tem nada a ver com o funcionamento dos tubos. Pipes podem ser nomeados (tradicionalmente todos eles eram nomeados), o que significa nada mais e nada menos do que eles têm um local no sistema de arquivos, como arquivos. E isso é exatamente o que os pipes eram uma vez, arquivos (com gravações reunidas tanto quanto a disponibilidade de memória física permitiria, como uma otimização).
Hoje em dia, pipes são um buffer de kernel pequeno e de tamanho finito para o qual os dados são copiados, pelo menos é o que acontece conceitualmente . Se o kernel puder ajudá-lo, as cópias são eliminadas usando truques de VM (por exemplo, o pipe de um arquivo geralmente apenas disponibiliza a mesma página para o outro processo ler, então é finalmente apenas uma operação de leitura, não duas cópias e não memória adicional do que já é usada pelo cache de buffer de qualquer maneira é necessária. Em algumas situações você pode obter 100% de cópia zero também. Ou algo muito próximo.
Se os pipes são pequenos e de tamanho finito, como isso pode funcionar para qualquer quantidade de dados desconhecida (possivelmente grande)? É simples: quando nada mais cabe, a gravação bloqueia até que haja espaço novamente.
A filosofia de muitos programas simples foi muito útil em uma época em que a memória era muito escassa. Porque, bem, você pode trabalhar em pequenos passos, um de cada vez. Hoje em dia, as vantagens são, além de alguma flexibilidade extra, ouso dizer, já não tão grandes.
No entanto, pipes são implementados de forma muito eficiente (tinha que ser!), então também não há desvantagem, e é uma coisa estabelecida que está funcionando bem e que as pessoas estão acostumadas, então não há necessidade de mudar o paradigma.