RAII, a fantasia de Rust/Linux
(kristoff.it)Resumo
Este é um texto escrito após acompanhar a disputa entre desenvolvedores Rust e desenvolvedores Linux tradicionais. Vários desenvolvedores podem ter estilos de código diferentes, mas o projeto Linux já tem um histórico de excluir C++ para evitar seu estilo e estrutura de código (RAII).
A forma como o código mencionada por Asahi Lina funciona é lenta demais ao encerrar esse programa e entra em conflito com o processamento em lote, que é a abordagem mais básica para criar software orientado a desempenho. Por exemplo, usar regiões de memória para processamento em lote permite coordenar várias lifetimes como uma só, então RAII não é necessário.
Aqui estão materiais que sustentam meu argumento. Todos eles explicam por que o processamento em lote é bom:
- Casey Muratori | Smart-Pointers, RAII, ZII? Becoming an N+2 programmer
- CppCon 2014: Mike Acton "Data-Oriented Design and C++"
- Modern Systems Programming: Rust and Zig - Aleksey Kladov
Portanto, acho que o Linux nunca deveria adotar RAII.
O motivo de eu trazer este texto é o seguinte. Vi várias vezes desenvolvedores Rust coreanos muito irritados ao ver esse texto, então fiquei curioso sobre o que as pessoas daqui pensam. O que vocês acham?
11 comentários
Na minha opinião, mas até consigo entender em certo grau o elitismo de certos desenvolvedores. Do ponto de vista da "engenharia" de software, especialmente no caso do Linux, hoje em dia é difícil encontrar um "software" que, no campo do open source, tenha dado as mãos de forma tão ampla até mesmo ao mundo fechado e ajudado no avanço da filosofia open source. Será que, por medo de que programadores não comprovados entrem em massa empunhando Rust, enchendo o projeto de código fora do controle do núcleo de mantenedores existentes, aumentando desenfreadamente a dívida técnica e encurtando o ciclo de vida do Linux, eles acabam mantendo uma postura conservadora que parece ainda mais excludente e até ludita?
É curioso adotar uma atitude nada "aberta" para que o open source continue sendo open source por muito tempo.
Eu também costumo usar e recomendar RAII ou formas semelhantes de gerenciamento de recursos com frequência. Isso porque, mesmo sem saber o que é RAII e usando de forma meio automática, ainda assim sai um código “ao menos seguro”.
No entanto, se não for usado com entendimento adequado, é muito fácil acabar produzindo em massa código ineficiente, como abrir e fechar dezenas de vezes um arquivo que poderia ser aberto apenas uma vez. Se o desenvolvedor mantém atenção constante ao desempenho e essa cultura é um pressuposto dentro da equipe de desenvolvimento, acredito que mesmo com RAII é possível obter um nível de desempenho plenamente suficiente.
freesempre que cada objeto for destruídofreee executar em bulk?Existe alguma funcionalidade no Linux para fazer o 2 rodar mais rápido que o 1? Algo como uma API?
Eu sempre vivi naturalmente com o 1, então não estou entendendo muito bem.
Não quero voltar à experiência de desenvolvimento em que, depois de terminar tudo, eu precisava procurar vazamentos de memória com o valgrind.
Não sei ao certo, mas dizer que não vai usar RAII parece significar que pretende melhorar o desempenho de
closeusando vazamento intencional de memória; não sei se é isso mesmo nem se essa é a direção certa.De qualquer forma, se for um desenvolvedor que já sabe fazer bem o gerenciamento manual de memória, também vai saber usar RAII; e um desenvolvedor que não consegue programar sem RAII provavelmente também não vai conseguir gerenciar memória manualmente, então não vejo motivo para não usar RAII.
Fiquei curioso sobre quanto tempo
freeconsome, então fiz um teste com um código simples — embora seja bem diferente de uma carga real. (Usei build release de Rust, comstd::alloc::allocestd::fs::File.)Alocando 10.000.000 blocos de memória de tamanhos variados, totalizando cerca de 2,5 GB, e medindo apenas o tempo de liberação, levou 1,87 segundo. Isso dá 187 ns por bloco.
Já no caso de arquivos, deixei abertos só uns 10.000 handles e medi apenas o tempo para fechá-los; deu cerca de 9 segundos. Ou seja, 900 us por arquivo.
(Neste PC com Windows, operações com arquivos são particularmente lentas, talvez por causa do antivírus. Em outro notebook com Windows, os números foram 400 ns/200 us, e em outro PC com Linux, 50 ns/600 ns.)
Como alternativa ao RAII, fala-se bastante em processamento em lote ou em confiar no SO no encerramento e vazar recursos de propósito; para memória, isso parece bem viável.
Mas, para recursos como arquivos ou sockets, nunca vi uma API de recuperação em lote, e se você vaza esses recursos, talvez o tempo no código de usuário diminua, mas o kernel acaba gastando praticamente esse mesmo tempo extra para encerrar o processo, então o ganho de desempenho é pequeno.
O RAII para memória também não é algo relativamente tão lento assim, nem é uma técnica que impossibilite o uso de arenas, nem impede vazamentos intencionais quando necessário; então me parece difícil usar isso como motivo para evitar RAII.
E, no caso do RAII de arquivos, que é ainda mais lento, como não há forma de tratar em lote nem de evitar o custo, fico curioso sobre o quanto as alternativas ao RAII realmente seriam melhores.
Um pouco fora do tema, mas tenho a impressão de que as objeções a RAII e
lifetimecostumam ser discutidas apenas no contexto de recursos de memória, representados pormalloc/free.RAII e
lifetimesão úteis não só para alocação de memória, mas de forma ampla para modelar a aquisição e devolução de recursos — e o controle de acesso exclusivo enquanto eles estão adquiridos — incluindo não só recursos do SO como arquivos, sockets e locks, mas também pools de objetos, pools de conexão etc.Esses recursos também compartilham a mesma estrutura de
malloc/free, então compartilham problemas estruturais como vazamento, use after free e double free.E justamente por compartilharem essa mesma estrutura, acho que deveria haver mais destaque para o fato de que RAII e
lifetimeresolvem de uma vez não só problemas de memória, mas também os desses outros recursos.Por exemplo, em Rust, até para file handles o compilador previne
use after closeedouble closeem tempo de compilação:https://play.rust-lang.org/?version=stable&mode=debug&edition=…
As principais linguagens com GC gerenciam memória com GC, mas no fim ainda precisam introduzir, para recursos cujo gerenciamento precisa ser determinístico, como file handles e sockets, estruturas no estilo RAII (como o
try-with-resourcesdo Java, ousingdo C# e owithdo Python) ou estruturas parecidas (como odeferdo Go).No fim, isso faz a linguagem ter vários modos diferentes de gerenciamento de recursos; me parece que algo assim talvez seja inferior a uma abordagem mais unificada.
Se com isso você quer dizer arena, então o Rust também naturalmente tem arena, e também é possível, ao eliminar a arena com
lifetime, proibir o acesso aos elementos da arena depois de fazer a "liberação em lote". Consulte https://crates.io/keywords/arena .Espero que surjam muitas linguagens mesmo depois de Zig e Rust. Mas, até agora, não vi nenhuma linguagem tão adequada quanto Rust. Na verdade, acho útil o conhecimento entre desenvolvedores que surge dessas discussões entre linguagens. Haha..
Eu também sou, à minha maneira, um desenvolvedor que usa Rust como linguagem principal, e não fiquei irritado, mas realmente me parece que estão recorrendo a um exemplo um pouco extremo ("o programa fica lento demais ao encerrar"; e, no vídeo linkado, citam um caso que não tem relação direta com exemplos em projetos Rust, e sim um caso em que o Visual Studio demora demais para fechar porque os destrutores de cada componente individual são chamados no encerramento).
Se, por questão de desempenho, for necessário processar de uma vez a limpeza de vários componentes, em vez de implementar
Droppara cada componente individual, acho que dá para optar por implementarDropem um tipo que detenha o tempo de vida desses componentes, para executar a limpeza de uma só vez. Melhor ainda se houver uma proteção para que esses componentes só possam ser criados por meio da API desse tipo.Claro, me parece que a preocupação do autor do texto é que, se a prática de usar RAII entrar na base de código do Linux, pode acabar se acumulando, em uma base gigantesca e cheia de complexidade, código com preocupações de desempenho muito implícitas, e no longo prazo acontecer algo semelhante ao Visual Studio; acho que isso é, de fato, uma preocupação bem válida. Ainda assim, como você mencionou em outro comentário, também existe a segurança que o RAII oferece, então vejo a escolha mais como um trade-off.
Os dois lados estão dizendo coisas certas.
Fazendo uma analogia: no LoL, existe a percepção de que Azir é um campeão de tier alto, absurdamente bom em split push, controle de área em teamfights e valor da ultimate, mas isso só vale em partidas profissionais de altíssimo nível; no nível das pessoas comuns, ele é fraco demais na lane e também tem stats muito fracos, então acaba sendo apenas um campeão de tier baixíssimo.
Do ponto de vista de pessoas como Asahi Lina, que têm conhecimento de programação e de sistemas operacionais acima dos 10% do topo, é natural que alternativas além de RAII sejam melhores; mas, na parte com que os outros 90% lidam, acho que não há nada melhor do que RAII ou Rust.
No entanto, como um dos grandes motivos para precisar garantir estabilidade/segurança de memória são problemas de segurança... acho que esse trade-off é inevitável.
Sem
RAII, desenvolvedores com relativamente menos experiência provavelmente acabariam produzindo bugs em massa.Fora do nível de SO, pelo menos no nível de aplicações...