<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Algoritmos-E-Estruturas-De-Dados | AkitaOnRails.com</title><link>https://akitaonrails.github.io/tags/algoritmos-e-estruturas-de-dados/</link><description>Blog do Fabio Akita do Canal do YouTube 'Akitando' falando sobre tecnologia, carreira e coisas geek.</description><generator>Hugo -- gohugo.io</generator><language>pt-BR</language><lastBuildDate>Mon, 19 Oct 2026 23:20:48 +0000</lastBuildDate><atom:link href="https://akitaonrails.github.io/tags/algoritmos-e-estruturas-de-dados/index.xml" rel="self" type="application/rss+xml"/><item><title>[Akitando #140] Desbloqueando o "Algoritmo" do Twitter - Introdução a Grafos</title><link>https://akitaonrails.github.io/2023/04/11/akitando-140-desbloqueando-o-algoritmo-do-twitter-introducao-a-grafos/</link><guid isPermaLink="true">https://akitaonrails.github.io/2023/04/11/akitando-140-desbloqueando-o-algoritmo-do-twitter-introducao-a-grafos/</guid><pubDate>Tue, 11 Apr 2023 08:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/uIflMYQnp8A"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;O Twitter liberou parte do código que chamou de &amp;ldquo;The Algorithm&amp;rdquo;, que forma o sistema que popula a aba de recomendações &amp;ldquo;Para Você&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Vamos entender qual é o contexto de ciência da computação e história necessários pra começar a entender esse código todo. E uma pequena introdução à ciência por trás de recomendações e grafos em geral.&lt;/p&gt;
&lt;p&gt;Finalmente, vou poder mostrar na prática a diferença de &amp;ldquo;código de curso&amp;rdquo; e &amp;ldquo;código de verdade&amp;rdquo; e exatamente porque software nunca acaba.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/uIflMYQnp8A"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>O Twitter liberou parte do código que chamou de &ldquo;The Algorithm&rdquo;, que forma o sistema que popula a aba de recomendações &ldquo;Para Você&rdquo;.</p>
<p>Vamos entender qual é o contexto de ciência da computação e história necessários pra começar a entender esse código todo. E uma pequena introdução à ciência por trás de recomendações e grafos em geral.</p>
<p>Finalmente, vou poder mostrar na prática a diferença de &ldquo;código de curso&rdquo; e &ldquo;código de verdade&rdquo; e exatamente porque software nunca acaba.</p>
<h2>Capítulos<span class="hx:absolute hx:-mt-20" id="capítulos"></span>
    <a href="#cap%c3%adtulos" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00:00 - Intro</li>
<li>00:00:57 - CAP 1 - Avisos e Descrevendo o Repositório</li>
<li>00:02:51 - CAP 2 - Interpretando Errado: Não Acredite em Blog Posts</li>
<li>00:07:14 - CAP 3 - Derrubando o Meme: Pessoas Idiotas</li>
<li>00:11:30 - CAP 4 - Objetivos do Video: derrubar os Clones. Código de Verdade</li>
<li>00:16:37 - CAP 5 - Entendendo Grafos. Introdução a Relevância</li>
<li>00:21:47 - CAP 6 - Primeira Geração de Relevância: WTF</li>
<li>00:27:49 - CAP 7 - Relevância nos Primórdios da Web: PageRank e HITS</li>
<li>00:35:43 - CAP 8 - Segunda Geração de Relevância: RealGraph e SALSA</li>
<li>00:39:22 - CAP 9 - Escalando com Hadoop e MapReduce: HDFS e Pig</li>
<li>00:43:15 - CAP 10 - Algoritmos Escaláveis de Grafos: Relembrando Mergesort</li>
<li>00:46:58 - CAP 11 - Terceira Geração de Relevância: GraphJet</li>
<li>00:50:36 - CAP 12 - Earlybird: Light Ranker. Apache Lucene</li>
<li>00:51:37 - CAP 13 - Ids para Sistemas Distribuídos: Snowflake</li>
<li>00:55:23 - CAP 14 - Serialização Eficiente sem JSON: Thrift e Protobufs</li>
<li>01:01:07 - CAP 15 - A Controvérsia do &ldquo;Rails Não Escala&rdquo;. Por que Scala?</li>
<li>01:07:37 - CAP 16 - Introdução a Similarity Clusters: Similaridade de Cosseno</li>
<li>01:16:08 - CAP 17 - Resumindo os demais Projetos: Aplicando o que Aprendemos</li>
<li>01:20:22 - CAP 18 - Qualidade do Código: Mini-Assessment</li>
<li>01:22:43 - CAP 19 - Sendo Educado na Web: Memes Idiotas</li>
<li>01:24:32 - CAP 20 - Refletindo sobre o Código: Lições a Aprender</li>
<li>01:27:31 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>Fio no Twitter (<a href="https://twitter.com/AkitaOnRails/status/1641887891327537158?s=20"target="_blank" rel="noopener">https://twitter.com/AkitaOnRails/status/1641887891327537158?s=20</a>)</li>
<li>Twitter: The Algorithm (<a href="https://github.com/twitter/the-algorithm"target="_blank" rel="noopener">https://github.com/twitter/the-algorithm</a>)</li>
<li>Twitter: The Algorithm ML (<a href="https://github.com/twitter/the-algorithm-ml"target="_blank" rel="noopener">https://github.com/twitter/the-algorithm-ml</a>)</li>
<li>Twitter&rsquo;s Recommendation Algorithm (<a href="https://blog.twitter.com/engineering/en_us/topics/open-source/2023/twitter-recommendation-algorithm"target="_blank" rel="noopener">https://blog.twitter.com/engineering/en_us/topics/open-source/2023/twitter-recommendation-algorithm</a>)</li>
<li>Steven Tey: # How the Twitter Algorithm works in 2023 (<a href="https://steventey.com/blog/twitter-algorithm"target="_blank" rel="noopener">https://steventey.com/blog/twitter-algorithm</a>)</li>
<li>Aakash Gupta: The Real Twitter Files: The Algorithm (<a href="https://aakashgupta.substack.com/p/the-real-twitter-files-the-algorithm"target="_blank" rel="noopener">https://aakashgupta.substack.com/p/the-real-twitter-files-the-algorithm</a>)</li>
<li>Earlybird: Real-Time Search at Twitter (<a href="https://stephenholiday.com/notes/earlybird/"target="_blank" rel="noopener">https://stephenholiday.com/notes/earlybird/</a>)</li>
<li>Twitter Search is Now 3x Faster (<a href="https://blog.twitter.com/engineering/en_us/a/2011/twitter-search-is-now-3x-faster"target="_blank" rel="noopener">https://blog.twitter.com/engineering/en_us/a/2011/twitter-search-is-now-3x-faster</a>)</li>
<li>Paper: GraphJet: Real-Time Content Recommendations at Twitter (<a href="https://www.vldb.org/pvldb/vol9/p1281-sharma.pdf"target="_blank" rel="noopener">https://www.vldb.org/pvldb/vol9/p1281-sharma.pdf</a>)</li>
<li>Paper: RealGraph: User Interaction Prediction at Twitter (<a href="https://www.ueo-workshop.com/wp-content/uploads/2014/04/sig-alternate.pdf"target="_blank" rel="noopener">https://www.ueo-workshop.com/wp-content/uploads/2014/04/sig-alternate.pdf</a>)</li>
<li>Paper: Real-Time Twitter Recommendation: Online Motif Detection in Large Dynamic Graphs (<a href="http://www.vldb.org/pvldb/vol7/p1379-lin.pdf"target="_blank" rel="noopener">http://www.vldb.org/pvldb/vol7/p1379-lin.pdf</a>)</li>
<li>Paper: Discovering Similar Users on Twitter (<a href="http://snap.stanford.edu/mlg2013/submissions/mlg2013_submission_20.pdf"target="_blank" rel="noopener">http://snap.stanford.edu/mlg2013/submissions/mlg2013_submission_20.pdf</a>)</li>
<li>GitHub: FlockDB (<a href="https://github.com/twitter-archive/flockdb"target="_blank" rel="noopener">https://github.com/twitter-archive/flockdb</a>)</li>
<li>GitHub: Cassovary (<a href="https://github.com/twitter/cassovary"target="_blank" rel="noopener">https://github.com/twitter/cassovary</a>)</li>
<li>GitHub: GraphJet (<a href="https://github.com/twitter/GraphJet"target="_blank" rel="noopener">https://github.com/twitter/GraphJet</a>)</li>
<li>Twitter: Finagle (<a href="https://twitter.github.io/finagle/"target="_blank" rel="noopener">https://twitter.github.io/finagle/</a>)</li>
<li>Apache Avro (<a href="https://avro.apache.org/"target="_blank" rel="noopener">https://avro.apache.org/</a>)</li>
<li>AkitaOnRails: Chatting with Blaine Cook (Twitter) (<a href="https://www.akitaonrails.com/2008/6/17/chatting-with-blaine-cook-twitter"target="_blank" rel="noopener">https://www.akitaonrails.com/2008/6/17/chatting-with-blaine-cook-twitter</a>)</li>
<li>Fireship: Vector databases are so hot right now. WTF are they? (<a href="https://www.youtube.com/watch?v=klTvEwg3oJ4"target="_blank" rel="noopener">https://www.youtube.com/watch?v=klTvEwg3oJ4</a>)</li>
<li>Slideshare: Restrição == Inovação (<a href="https://www.slideshare.net/akitaonrails/devconf-2019-so-carlos"target="_blank" rel="noopener">https://www.slideshare.net/akitaonrails/devconf-2019-so-carlos</a>)</li>
<li>Elastic: Vector Search (<a href="https://www.elastic.co/what-is/vector-search"target="_blank" rel="noopener">https://www.elastic.co/what-is/vector-search</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Já não é notícia nova, a esta altura acho que todo mundo já viu ou ouviu alguém comentando sobre o código dos algoritmos de recomendação do Twitter, que tio Elon Musk liberou, parcialmente, no último dia de março de 2023. No momento em que estava escrevendo o script deste episódio não se passou uma semana ainda. E até a gravação deste video ainda não foi liberada a segunda parte do código. Mas vamos ver o que temos até agora.</p>
<p>(&hellip;)</p>
<p>No mesmo dia que a primeira parte do código foi liberado, eu e diversos outros programadores e pesquisadores começamos a avaliar o código e surgiu muita coisa interessante. Eu mesmo fui twittando em tempo real o que achei de primeiras impressões. Meu foco foi mais na parte técnica e arquitetura. Os links pros meus tweets originais estão na descrição abaixo e neste video vou elaborar em mais detalhes o que não coube em pequenos tweets.</p>
<p>Antes de começar, preciso deixar alguns avisos muito claros aqui. Este código é incompleto. Ainda falta pelo menos uma segunda parte. E mesmo com essa parte, ainda não vamos ter certeza que tudo vai ser liberado. O que o Twitter soltou não é mais que uma pequena fração de todo o código necessário pra se fazer um verdadeiro clone de Twitter.</p>
<p>Esse código só cobre uma parte dos sistemas que devolvem os tweets recomendados que você vê na aba de &ldquo;Para você&rdquo;. Em termos coloquiais chamamos isso tudo de &ldquo;Algoritmo do Twitter&rdquo;, mas não é um único algoritmo: é uma coleção de dezenas de algoritmos e heurísticas que, funcionando juntos, geram recomendações que tem relevância pros seus interesses.</p>
<p>Mesmo sendo incompleto, estamos falando de mais de 4 mil e seissentos arquivos. Mais de 330 mil linhas de códigos, sendo mais da metade código feito na linguagem Scala, e quase 30% em Java. A próxima linguagem mais relevante depois dessas duas é Rust, na nova parte de machine learning chamada Navi, mas mesmo assim é só 2% do total desse código.</p>
<p>Mais importante, o sistema de build que eles utilizam é o Bazel. Pense NPM se você for de Javascript, ou algo parecido com Gradle se for de Java mesmo. Eu mesmo nunca vi Bazel sendo utilizado num projeto de verdade e cada sub-projeto tem um arquivo de BUILD em Bazel, mas eles omitiram os arquivos WORKSPACE, que são necessários pra conseguirmos realmente compilar e buildar cada projeto.</p>
<p>É possível reconstruir do zero, via tentativa e erro, mas é um trabalhinho bem chato que eu não tenho interesse em fazer e, até o momento, não vi ninguém se prontificando a fazer. Portanto tudo que eu, ou qualquer outro analisar, é baseado unicamente em análise estática do código, que é um jeito mais bonito de dizer que só temos como ler o código ou fazer compilações parciais. Por isso não acredite em tudo que ler em somente um pedaço do código. Deixa eu dar um exemplo.</p>
<p>Alguns pesquisadores foram mais interessados na parte das regras de negócio: os critérios que afetam o alcance de tweets. Em resumo, não teve nenhuma grande novidade bombástica além do que todo mundo mais ou menos já sabia só por observação. Vamos ver dois exemplos, o blog post do desenvolvedor Steven, da Vercel, e o blog post do Aakash Gupta. Ambos os links, novamente, estão na descrição abaixo.</p>
<p>No post do Steven, você vai ler que o algoritmo de ranqueamento vai determinar pesos em algumas ações em cima do seu tweet. Por exemplo, um like tem peso 0.5, um retweet é mais importante, tem peso 1, se alguém clicar no tweet, der reply ou like ou ficar mais que 2 minutos lendo, vai ter peso 11 e assim por diante. Mas, de onde ele tirou essa informação?</p>
<p>Ele coloca o link pro README no diretório projects/home/recap dentro do repositório the-algorithm-ml, que é o repositório menor de machine learning. A contagem de Scala e Java que falei acima foi no repositório principal the-algorithm, que é o maior; mas tem esse outro menor que é basicamente um projetinho Python, com 91 arquivos e umas 6 mil linhas de código pra PyTorch, que eles chamam de &ldquo;heavy ranker&rdquo;.</p>
<p>O problema é que o único lugar nesse código onde esses números de peso aparecem, é na documentação desse README. Olha só, se eu abrir o VS Code no projeto the-algorithm-ml e procurar por esse parâmetro &ldquo;is_negative_feedback_v2&rdquo;, que tem um peso negativo 74, só aparece no README, em nenhum lugar no código tem de fato esse valor.</p>
<p>Existe um arquivo &ldquo;local_prod.yaml&rdquo; que parece que configura esses parâmetros pra produção, e no servidor deles pode ser que esteja mesmo este número, mas no arquivo neste repositório, não tem nada. Portanto, não posso dizer que está nem certo, nem errado, só que é inconclusivo. Só que se você ler o post, vai achar que isso é um fato. E tenho certeza que um monte de gente vai referenciar esse blog post e repetir a informação sem checar o que significa. O Steven não errou nem mentiu, mas ele está descrevendo informação incompleta.</p>
<p>Mas não é só isso. Se for agora no post do tal Gupta, ele vai falar de outros parâmetros de peso. Por exemplo, ele fala que retweets dão um boost de 20 vezes. E de onde ele tira isso? É do repositório principal the-algorithm, no arquivo EarlybirdTensorflowBasedSimilarityEngine.scala no projeto cr_mixer. Lá tem esse trecho da função <code>getLinearRankingParams</code>. Ah, agora não é um README, é trecho de código. Então deve ser confiável, certo? Também não.</p>
<p>Vamos procurar quem chama essa função. Caímos nesse trecho da função <code>getRelevanceOptions</code> que, caso essa variável <code>useTensorflowRanking</code> seja falsa, daí vai chamar a função com os parâmetros que vimos antes, caso contrário, vai chamar essa outra função <code>getTensorflowBasedRankingParams</code>. Vamos ver o que ela define. Voltamos pro arquivo anterior e vemos que ela chama a função <code>getLinearRankingParams</code> abaixo, mas muda o tipo e, mais importante tem esse <code>applyBoosts</code> pra false.</p>
<p>O que isso significa? Será que então todos esses parâmetros com nome &ldquo;boost&rdquo; aqui embaixo vão ser ignorados? Como <code>offensiveBoost</code> ou <code>tweetHasTrendBoost</code>? Aí que tá, não temos como saber. Como disse antes, esse código é incompleto. Até o presente momento não sabemos nem se 100% desse código compila, não sabemos quais dependências estão faltando, não sabemos como buildar e, portanto, não sabemos como executar esse código e nem quais caminhos dentro são realmente executados pra cada tipo de dados e configurações.</p>
<p>E mesmo se conseguirmos fazer rodar, vamos voltar praquele README que mostrei agora pouco, da parte de machine learning com PyTorch. Olha o que diz: &ldquo;não podemos liberar os dados reais de treino por causa de privacidade&rdquo;. Faz sentido, claro, mas sem ter o mesmo material de treino, ou pelo menos o mesmo modelo gerado no final, não temos como reproduzir exatamente o que roda no Twitter e, portanto, não podemos garantir que é exatamente a mesma coisa rodando em produção nos servidores deles.</p>
<p>Portanto, do ponto de vista de auditoria, podemos confiar que, sim, esse código é certamente retirado do código real dentro do Twitter. É código demais pra falsificar, não seria prático. Mas os detalhes do que exatamente roda lá dentro, talvez a gente nunca saiba. Sempre é possível omitir camadas de filtros na fase de mixagem que adicionam ou removem tweets, ou omitir os arquivos de configuração reais que modificam todos esses pesos e boosts.  Não temos como realmente saber, por isso cuidado com afirmações sendo feitas só lendo esse código.</p>
<p>Antes de continuar, eu preciso falar da vergonha alheia que foi o comportamento geral das pessoas frente a esse código. Vamos estabelecer: eu não sou fã de Elon Musk, Dogecoin é shitcoin, e acho uma puta perda de tempo gente que idolatra o cara ou que perde tempo engajando. Ele é só mais um cara que existe. A existência dele não me afeta, assim como a existência de qualquer outro, pra mim, é insignificante. E é só isso.</p>
<p>O Twitter é uma empresa privada. Como qualquer empresa, ela não tem nenhuma obrigação de liberar código. Portanto, o que ela fez ao liberar, pra mim, é um excelente marketing. Uma cortina de fumaça pra desviar atenção, controlar a narrativa e deixar todo mundo coçando a cabeça tentando desvendar esse quebra cabeça; e ao mesmo tempo cutucar os críticos no governo e cutucar os concorrentes como um Zuckerberg da vida. No mínimo vai deixar muita gente chata ocupada tentando abrir uma caixa semi-transparente que não tem chave pra tranca.</p>
<p>A real motivação por trás disso não me interessa. Só me interessa que temos esse código. O que foi liberado, continua sendo um código excelente de explorar. Porém, pessoas idiotas parece que tem talento de encontrar a parte mais insignificante, e viralizar os memes mais idiotas. Se você pesquisar a palavra &ldquo;Elon&rdquo; no VS Code, a primeira coisa que vai aparecer é essa chave &ldquo;author_is_elon&rdquo;, logo em cima de outras chaves &ldquo;suspeitas&rdquo; como &ldquo;author_is_democrat&rdquo; ou &ldquo;author_is_republican&rdquo;. Uau, que vazamento inesperado! O que é isso, a prova que o Elon dá boost nos tweets dele? O que é isso de democratas e republicanos, tem manipulação neles também?</p>
<p>Até gente que se auto-denomina &ldquo;programador&rdquo; ficou fazendo meme disso. É uma vergonha alheia. Se você viu alguém fazendo meme disso, pode des-seguir, o cara é inútil. Deixa eu mostrar como faz. Vamos lá pro código. Esse &ldquo;author_is_elon&rdquo; é chave pra um Map que no final vai se chamar PredicateMap, que é gerado através desta Sequence, onde cada elemento é um tuple com uma string que serve de chave e um FeatureMap, que é uma função booleana. No caso desse trecho do Elon, ele recebe um candidato, provavelmente candidato de tweet pra recomendar, e checa se a tal feature de Author contém esse tal DDGStatsElonFeature. Ênfase em &ldquo;stats&rdquo;.</p>
<p>Bacana, e quem usa esse PredicateMap? Vamos procurar no código. Achamos aqui na linha 82 do objeto ServedEventsBuilder que extende essa trait ClientEventsBuilder. Nesse trecho ele tá iterando por cada predicado naquela PredicateMap e no final o que  tá gerando são counts pra cada predicado. É um contador. E quem vai usar essas contagens? Vamos de novo pesquisar, agora por esse objeto <code>ServedEventsBuilder</code>.</p>
<p>Pronto, chegamos neste <code>HomeScribeClientEventSideEffect</code>. É o que o Twitter chama de &ldquo;side-effect&rdquo;. Pelo que eu já tinha deduzido lendo o código, side-effect é um job de Kafka, um job assíncrono. E Scribe é justamente o servidor de logs open source que foi desenvolvido pelo Facebook. É só um servidor de log, não diferente de um Apache Flume ou Logstash, ou Rsyslog da vida. Esses códigos servem pra gerar métricas. É só isso.</p>
<p>&ldquo;Ah, mas tinha que ser o cuzão egocêntrico do Elon pra ter um código especial pra uma métrica só pra ele.&rdquo; Ué, mesmo se for. Se ele desenbolsou 40 bilhões pra comprar a porra toda, achei foi muito pouco código com o nome dele. Achei que fosse achar classes inteiras renomeadas pra DogeCoin ou algo assim espalhado por aí. Aliás, vocês estão assumindo que esse código foi feito por ordem do Elon.</p>
<p>Outra teoria muito mais plausível é que esse código foi feito justamente pelo povo que ele demitiu, pra monitorar os efeitos dos tweets do Elon antes da compra ter sido realizada. Muito mais plausível. Independente do que for, só vamos saber se, alguém de dentro do twitter, vazar os logs do repositório Git de verdade, com o timestamp do commit. E mesmo assim, commits de Git podem ser facilmente adulterados. Por isso é uma discussão meio inútil.</p>
<p>Todo mundo ficou fazendo meme com um código de log. E vocês se acham programadores? A discussão do porque esse trecho existe é irrelevante e só serve pra demonstrar como é um programador que gosta de fazer bikeshed: discutir coisas inúteis, sem nenhum valor, sem nenhum mérito. Você está perdendo seu tempo. Agora vamos entender como um programador de verdade olha pra esse código.</p>
<p>O objetivo a partir daqui é o seguinte: desmistificar a diferença de código de curso, código de tutorial e código de produção de empresa de verdade. Pra um iniciante que nunca trabalhou ou só trabalhou em projetos pequenos, a diferença vai ser gritante. A outra coisa é como um programador sênior pensa. Infelizmente, não tem como, num único video, com só 2 dias de análise, sem compilar, conseguir ter a visão completa. Então tudo que vou dizer são só minhas primeiras impressões.</p>
<p>Eu já disse isso em outros videos, mas aqui vale repetir: código de curso, código de tutorial, código em video no Youtube, são absurdamente diferentes de código de verdade de produção. Não tem nem comparação. A complexidade e tamanhos são impossíveis de caber em qualquer curso ou tutorial. É inviável. Literalmente, sem exagero, um curso vai te ensinar a fazer uma casinha de rato com caixa de sapato, mas todo projeto relevante de verdade é um Burj Khalifa, o arranha-céus mais alto do mundo que nunca foi construído, e toda a engenharia pra construir isso num terreno de areia, sem receita pré-pronta.</p>
<p>Outra coisa que já disse antes: software nunca acaba. Se seu produto realmente vale alguma coisa, você vai reescrever partes ou até ele inteiro, mais cedo ou mais tarde. E com esse código liberado pelo Twitter, vou finalmente poder ilustrar esses dois pontos. E esse vai ser o objetivo do video.</p>
<p>Eu peguei aleatoriamente o primeiro projeto no Github com nome de &ldquo;twitter clone&rdquo;. Tem dezenas por aí. Esse é feito em Next.js, com Typescript, Firebase, Tailwind, só &ldquo;tecnologia de ponta&rdquo;, como um amador poderia pensar. E ele faz tudo, olha só o Readme: usuários conseguem postar tweets, dar like, retweet, reply, adicionar bookmarks, adicionar imagens, seguir ou des-seguir outros usuários, updates de likes e tudo mais em tempo real. Porra, faz tudo.</p>
<p>Só que não. Isso não passa de uma caixa de sapatos frente ao Burj Khalifa. Vamos ver, com a ferramenta Tokei, vemos que esse projeto tem 158 arquivos e mais de 32 mil linhas de código. Pra um iniciante, sem dúvida, pode parecer gigantesco. Mas não é. Mesmo sendo só uma fração incompleta, o repositório do Twitter que estamos avaliando, tem mais de 4600 arquivos e mais de 330 mil linhas de código. 10 vezes mais código.</p>
<p>Mas esses projetos sequer são equivalentes. Esse tal &ldquo;clone&rdquo; é só o front-end e só as operações mais básicas. Ele jamais aguentaria a escala do Twitter em produção. O &ldquo;the-algorithm&rdquo; que foi liberado é só pra buscar os tweets que aparecem na aba &ldquo;Pra você&rdquo;. Notaram como esses clones omitem a aba &ldquo;pra você&rdquo;? Claro, porque justamente a inteligência pra buscar tweets recomendados, em tempo real, em escala, não é um problema trivial de resolver. Todo clone que você achar, ou vai omitir, ou vai ter uma versão super besta de recomendação. E nunca vão escalar pra ordem de centenas de milhões de pessoas. São todos, caixas de sapato.</p>
<p>E não tem nenhum problema nisso. O foco da maioria desses clones é ser um exercício pra iniciantes de front-end treinarem fazer interfaces parecidas com coisas que já existem. Nada mais, nada menos. Nenhum desses clones, seja clone de Twitter, clone de Instagram, clone de Pinterest, chega perto da unha dos pés das versões de verdade e são incapazes de fazer uma fração do que os originais fazem, ou de aguentar uma fração do tráfego. Essa é a realidade dos clones: são todos meros exercícios. Contanto que vocês entendam isso, não tem nenhum problema se exercitar neles. Todo mundo precisa começar do zero. E esse é o nível zero.</p>
<p>A razão dessa diferença é que é trivial fazer qualquer aplicativo funcionar só na sua máquina, com uma pessoa usando. Mesmo se você virar freelancer, normalmente trabalha em projetos que muito pouca gente usa. 10 pessoas. 100 pessoas. Mesmo mil pessoas, é extremamente pequeno. Empresas grandes como MercadoLivre, iFood, Nubank, lidam com milhões, dezenas de milhões de pessoas. No caso de Facebook, Google ou Apple, são bilhões de pessoas.</p>
<p>E nessa escala, as soluções são completamente diferentes de  todo curso e tutorial que você procurar. Eu afirmo que não existe nenhum curso que cobre casos de larga escala, simplesmente porque todos fazendo cursos nunca tiveram que lidar com esse problema. Eles também simplesmente não sabem que isso existe. Eu mesmo nunca tive que lidar com o caso excepcional de bilhões de pessoas.</p>
<p>É muito caso a caso, em cada empresa gigante, vai ser diferente. Nenhum curso conseguiria cobrir, mesmo se quisessem. Simplesmente não existe esse material disponível, e é uma das razões de porquê esse código do Twitter é relevante: porque não existe código de produção de empresa de verdade disponível pra podermos mostrar, todos são fechados.</p>
<p>Eu trabalhei em vários projetos, incluindo alguns dessa categoria grande. Mas não posso mostrar, porque nenhum de nós deve quebrar confidencialidade e propriedade intelectual de clientes. Por isso eu falo pra não confiar em nenhum programador que não tenha tido esse nível de experiência: eles nunca viram nada além da dimensão de caixa de sapato e acha que o resto do mundo é pequeno assim. E definitivamente não é. Só quem já subiu no topo do Burj Khalifa, sabe a diferença.</p>
<p>A tal aba &ldquo;Pra você&rdquo;, à primeira vista, parece super simples. Na aba &ldquo;seguindo&rdquo; mostra só tweets de pessoas que eu sigo, provavelmente em ordem cronológica. Fácil de fazer, um iniciante consegue imaginar um &ldquo;select top 100 * from tweets order by created_at desc&rdquo; da vida. Agora, no pra você, tem tweets de gente que eu sigo e de gente que eu não sigo, mas que as pessoas que eu sigo interagiram. Então se eu sigo o João e o João retwitou o tweet do Paulo, no meu &ldquo;pra você&rdquo; pode aparecer o tweet do Paulo. Só que não é tão simples assim.</p>
<p>E se quem eu sigo interagiu com 100 tweets? Quais deles deveriam aparecer pra mim? Das pessoas que eu sigo, quais delas são mais relevantes pra aparecer no topo primeiro? Por que eu vejo o like de algumas pessoas mas não de outras? Ah, será que como os posts de blogs tavam tentando explicar, é só botar um peso pra like, outro peso pra retweet e dar um &ldquo;group by&rdquo; ou &ldquo;order by&rdquo; da vida e pegar o topo? Também não.</p>
<p>Como é impossível cobrir o código todo que foi liberado, escolhi começar falando só de um pequeno trecho. Um banco de dados NoSQL de grafos, que inclusive já tinha sido liberado antes como open source, com licença Apache, chamado GraphJet, e a história por trás dele. Tudo isso foi descrito num paper de autoria de Aneesh Sharma e vários outros colaboradores, publicado em 2016.</p>
<p>Pra todo mundo que não fez ciências da computação entender, na faculdade a gente aprende sobre um troço chamado &ldquo;grafos&rdquo;. Na prática, você já deve ter visto e até desenhado. É quando se desenha caixinhas ou círculos que chamamos de nós e ligamos um nó com outro nó com um troço que chamamos de vértices. Podemos representar grafos como matrizes adjacentes ou listas adjacentes. Outra forma de representar grafos é com vetores.</p>
<p>Daí podemos classificar grafos baseados em características como número de nós ou número de vértices, edges ou bordas, direção das bordas. Tem diversos tipos de grafos como unidirecionais. Em Git, por exemplo, você já deve ter ouvido falar que é um DAG, um direct acyclic graph, ou grafo acíclico, que não é cíclico, e direto.</p>
<p>Dado um grafo, temos como navegar de um nó pra outro nó. Pense em nós como uma casa numa rua e os vértices como o caminho, talvez a rua, entre essas casas. Daí temos algoritmos pra navegar como o algoritmo do menor caminho entre dois nós, que é o hello world de grafos. Não só isso, ainda no assunto de classificação, temos nós que ficam nas bordas do grafo que tem poucos vértices e temos nós no meio dos grafos com muitos vértices.</p>
<p>Costumamos chamar esses nós de &ldquo;hubs&rdquo;. Na prática, numa rede social, chamamos de hubs, mas pessoas comuns chamam de &ldquo;influencers&rdquo;. Pessoas comuns são os nós das bordas, com poucos relacionamentos, que são os vértices.</p>
<p>Tudo na natureza pode ser representado como grafos. Como falei antes, evolução de desenvolvimento de software, pode ser representado como um DAG, um grafo de commits, com branches. Um sistema de logística e entregas é orientado num mapa com grafos, pra determinar o melhor caminho de entrega entre dois pontos. O DNA humano pode ser um grafo, onde os nós são as bases como adenina, citosina, guanina e timina, formando nucleobases. As sinapses do nosso cérebro são grafos.</p>
<p>Num ecossistema animal temos a hierarquia da cadeia alimentar, que pode ser representado como um grafo. No mercado temos grafos de empresas e relacionamentos de comprador e vendedor. E assim por diante. No caso do Twitter, que é uma rede social, os nós costumam ser as pessoas, e os vértices são os relacionamentos, quem nós seguimos, quem damos likes ou retweets, quem bloqueamos e assim por diante.</p>
<p>Se você nunca estudou ciências da computação, vai ter um pouco de dificuldade de entender o que vou explicar e certamente não vai entender nada do código do Twitter. Vou tentar simplificar o máximo que der, mas só consigo chegar até certo ponto, então, se interesse em depois ir procurar cursos como nas aulas do MIT sobre grafos. Pra operar em grafos, você vai precisar aprender coisas como Álgebra Linear e Cálculo, pelo menos o Cálculo básico.</p>
<p>E portanto sim, pra entender como uma rede social funciona e o código por trás, precisa entender esses aspectos da matemática. E pra chegar no ponto de recomendação, também precisa entender o básico de estatística e probabilidade, porque recomendação é justamente a probabilidade dos tweets que o sistema recomendar realmente te interessar a ponto de engajar, dar like ou reply, que é a base de machine learning.</p>
<p>Mesmo se você não for implementar do zero inteligência artificial, machine learning, ainda assim precisa entender essa matemática. Machine Learning é, nada mais, nada menos, do que uma coleção de algoritmos de classificação e probabilidade. Se não souber quando pode usar regressão linear, regressão logística, similaridade de cosseno, bayes ingênuo, não vai saber o que fazer.</p>
<p>Cada caso, cada tipo de conjunto de dados e o resultado que quer, depende de saber escolher quais desses algoritmos usar. E pra saber quais usar, precisa saber a matemática por trás. Senão vai usar regressão linear quando não podia e ter resultados errados, que é a coisa mais comum de ver amador fazer. Nem tudo que parece uma reta, é uma reta.</p>
<p>E no caso desse código do Twitter sim, eu encontrei que ele usa coisas como grafos, regressão logística e similaridade de cosseno. Mas estou me adiantando. Vamos voltar ao tal paper do Graphjet de 2016. Um problema, que não é trivial de resolver e não tem uma solução única, porque depende do comportamento dos usuários e dos dados disponíveis, é recomendação. Seja recomendação de produtos num ecommerce, recomendação de leitura num site de notícias, ou recomendação de tweets num Twitter.</p>
<p>Como usuários seguem usuários, naturalmente temos grafos, em particular grafos bipartidos, com usuários de um lado e tweets de outro. Os esforços de recomendação começaram no Twitter por volta de 2010, lembrando que o Twitter começou a operar em março de 2006.</p>
<p>Eu mesmo criei minha conta só um ano depois, em abril de 2007, durante a Railsconf 2008 em Portland, onde eu via todo mundo se cadastrando pra gente compartilhar onde ia ser o próximo happy hour. Lembrando que ainda não existia Messenger do Facebook, não existia Whatsapp, não existia Telegram. O único aplicativo de mensagem que todo mundo usava, era SMS.</p>
<p>De qualquer forma, quando eles começaram a experimentar a recomendar usuários novos pra gente seguir, por volta de 2010, foi com um sistema que chamaram carinhosamente de &ldquo;WTF&rdquo;, What the Fuck. Mentira, era &ldquo;Who To Follow&rdquo;. A arquitetura era como nessa figura. Segundo o paper, no seu núcleo tinha esse Cassovary, que é um motor de processamento de grafos in-memory, ou seja, que mantém os grafos em memória. Isso é importante, porque a maioria dos algoritmos conhecidos pra lidar com grafos assume ter o grafo todo em memória. Guarde essa informação.</p>
<p>Esse projeto Cassovary é open source e podemos encontrar no GitHub deles. Última atualização foi de 2021, e não é mais utilizado hoje. Continuando, esse Cassovary operava em snapshots de grafos de seguidores carregados do HDFS, que é o sistema de arquivos distribuídos do Hadoop. Quem já trabalhou com Big Data, Data Lakes, certamente já esbarrou em instalações de HDFS e no Twitter não era diferente. Só entenda que é um sistema de arquivos distribuído, como se você tivesse uma partição única espalhada em múltiplos servidores. É um nível acima de um simples RAID.</p>
<p>Os grafos de seguidores vinham de outro banco de dados chamado FlockDB que fazia a ingestão desses dados no HDFS. FlockDB acho que foi o primeiro banco de dados de grafos que o Twitter criou e soltou como open source. O que aconteceu foi: eles começaram como qualquer tech startup normal, sem saber o que o produto ia se tornar. No começo usavam um banco de dados normal, MySQL mesmo. Mas quando começaram a fazer sucesso e ver que o problema era maior que um banco relacional, começaram a pesquisar alternativas. E o primeiro resultado foi um banco de dados de grafos, o FlockDB.</p>
<p>O FlockDB dava mais performance pra operações de inserção, updates, já que não precisava das garantias ACID de um banco relacional. Tinha operações extras pra lidar com grafos, como navegar por nós e pelos vértices. E vocês tem que entender que carregar um grafo em memória pode ser pesado. Imagine sua própria conta: você tem dezenas de tweets, você segue dezenas de pessoas, essas pessoas tem dezenas de tweets.</p>
<p>Cada um desses tweets tem contagens de likes, retweets; alguns tweets são replies pra outros tweets. E pra calcular qual deles é mais relevante ou menos relevante, o ideal é ter toda essa estrutura em memória pra conseguir navegar por toda essa informação. Agora pensa num banco de dados relacional como MySQL. Como você resolveria esse problema? Qualquer solução que você imaginar em SQL, pode ter certeza, já foi testado e sabemos que não vai escalar. Essa é um dos poucos cenários que podemos afirmar que um banco de dados relacional não é adequado.</p>
<p>Segundo a página de GitHub do FlockDB, até 2010 o cluster deles armazenava mais de 13 bilhões de bordas e sustentava picos de tráfego de mais de 20 mil escritas por segundo, e mais de 100 mil operações de leitura por segundo. Mas rapidamente chegou um ponto onde isso já não era mais suficiente. Foi quando começaram a puxar esses dados pra HDFS e processando nesse novo projeto que chamaram de Cassovary, um segundo projeto em Java pra lidar com &ldquo;Big Graphs&rdquo;, grafos com bilhões de nós e vértices. Foi a época que as celebridades gigantes começaram a viver no Twitter. Pense nível Kardashians.</p>
<p>Voltando ao paper, a camada de armazenamento do Cassovary dava acesso aos grafos via queries, pesquisas baseadas em vértices. Pense um &ldquo;SQL&rdquo; especializado em grafos. E em cima disso um motor de recomendação computava as sugestões de &ldquo;Who To Follow&rdquo;, o WTF, ou &ldquo;quem seguir&rdquo;. Foi assim que nos primórdios do Twitter a ganhamos sugestões de novas contas pra poder seguir.</p>
<p>Um dos objetivos desse projeto era ver se era possível manter os grafos em memória, in-memory. A pergunta que eles não tinham resposta é: será que o crescimento do volume de dados desses grafos seria mais lento que a Lei de Moore? Se for, o preço de memória iria baratear mais rápido do que os dados cresciam, e daria pra computar tudo em memória. Que é mais simples do que ter que lidar com discos, particionamento, paging, cache e tudo mais.</p>
<p>Como o paper diz, considere um grafo com 10 bilhões de bordas: mesmo uma representação ingênua, simples, ocuparia algo na faixa de 80 gigabytes, que um único servidor consegue aguentar. Você já trabalhou com um servidor, este ano, que tem 80 gigabytes? Se sim, diga nos comentários que empresa é, pra todo mundo entender meu ponto.</p>
<p>Uma das limitações, mesmo naquela época, é que essa sugestões de recomendação de quem seguir, eram calculados em batch, uma vez por dia, não era em tempo real como é hoje. Ou seja, você só tinha recomendações novas uma vez por dia. De qualquer forma, via muita experimentação, muitos testes A/B, eles chegaram em dois conceitos que usam até hoje e que vale explicar. O primeiro se chama círculo de confiança e o segundo foi a adoção de &ldquo;SALSA&rdquo; que é Stochastic Approach for Link-Structure Analysis ou Análise de estrutura de links usando processos estocásticos.</p>
<p>Um exemplo de grafo que eu não mencionei, mas deveria ser óbvio, é a própria World Wide Web. Toda página na internet é um nó, as bordas, vértices, são links de uma página pra outra. No começo da web, já se tinha um número grande de páginas, o suficiente pra ser difícil de achar as coisas. Pense numa era antes de existir Google. A forma rudimentar que existia pra gente se achar era derivado do conceito antiquado de Páginas Amarelas.</p>
<p>Se você for muito novo talvez nunca tenha visto, mas antigamente, quando precisávamos achar um mecânico, um médico ou qualquer serviço, tínhamos um livrão físico de páginas amarelas que era um diretório com todos os serviços da cidade. Cada cidade tinha um livro diferente. Imaginem que novos negócios tinham dificuldade de serem encontrados, porque precisava esperar uma nova edição ser impressa e distribuída pra todo mundo. Páginas de classificados nos jornais meio que cobriam um pouco dessa diferença.</p>
<p>Serviços como Yahoo, Excite, Lycos e outros serviam esse papel na Web. Se eu criasse um site novo, pedia pra ser incluído num desses diretórios pra que as pessoas conseguissem me achar. Esse era o processo. Mas mesmo assim, imagine restaurantes. Qual das centenas de restaurantes numa cidade grande como São Paulo, seria relevante pra mim? Mesmo se tivesse a opção de filtrar por localização e tipo de cozinha. Ainda assim eu tinha que manualmente tentar várias. Era bem trabalhoso.</p>
<p>Portanto, resolver esse problema de relevância entre milhares de páginas web, era muito importante. E lá no meio pro fim dos anos 90 surgiram pelo menos dois estilos diferentes de lidar com isso. A maioria de vocês que estudou o mínimo de grafos talvez estejam familiarizado com o famoso paper de PageRank, literalmente ranking de páginas, inventado pelos fundadores do Google, Larry Page e Sergey Brin em 1996 quando ainda eram estudantes de Stanford.</p>
<p>Procurem no YouTube, tem dezenas de videos que explicam PageRank, mas a idéia básica é a seguinte. Começa pelo básico, o &ldquo;H&rdquo; de HTML é pra Hyperlink. Todo mundo esquece isso, mas o mais importante numa página web é que a página de alguém contenha um link pra sua página. Quanto mais sites diferentes tem links apontando pro seu site, mais relevante ele pode ser.</p>
<p>No PageRank, imaginamos um navegador aleatório, uma pessoa imaginária que vai clicando em páginas e nos links nessas páginas, aleatoriamente. Qual a probabilidade de, só navegando sem rumo, alguém cair na sua página? Esssa probabilidade é o seu score, sua pontuação. Se sua página é famosa, centenas de outras páginas contém link pra sua, a probabilidade desse navegador aleatório chegar na sua página é alta, portanto sua pontuação no ranking, no PageRank, é alta.</p>
<p>Esse foi o algoritmo usado na primeira versão do Google, o site que matou todos os diretórios estilo páginas amarelas. Porque agora era possível fazer um programa que sai vasculhando todas as páginas na web, avaliando os hyperlinks entre eles, e atribuindo pontuações nesse ranking. Daí, quando fazemos uma pesquisa, ele nos mostra primeiro as páginas mais bem pontuadas. É por isso que logo na primeira página, os primeiros links costumam ser os corretos.</p>
<p>Com isso eliminamos a necessidade de seres humanos ficarem manualmente classificando páginas em diretórios e atualizando a relevância na mão, que sempre foi demorado, trabalhoso, tedioso e com muita margem de erro. Essa talvez tenha sido a primeira categoria de trabalhadores da web que perderam seu trabalho pra um algoritmo, e não será a última. Tudo que é feito manualmente e pode ser automatizado, certamente será. Todo trabalho repetitivo é automatizável, sabemos isso desde o começo da Web.</p>
<p>Mas todo algoritmo tem seu ponto fraco e rapidamente as pessoas começaram a notar que se o lance é esse número de links pra sua página, que tal criar um monte de sites fantasma que fazem links pra uma página que você quer tornar relevante? E assim surgiram os primeiros web farms, fazendas de servidores fake, e uma galera que vendia relevância pro seu site, fazendo um monte de site falso linkar pra sua. Seja criando sites bestas em série ou invadindo sites dos outros pra colocar links.</p>
<p>Isso aconteceu só lá no começo dos anos 2000. Por isso o Google evoluiu e deixou de usar PageRank e passou a usar, por exemplo, TrustRank, que combate web spam identificando quem é site legítimo e quem não é, e descontando pontuação. Ao longo dos anos o Google sempre foi aprimorando esses algoritmos, não é um produto fixo. Parte do trabalho de agências web era ficar monitorando quando o Google lançava uma nova versão pra modificar as estratégias pra levar relevância pros seus clientes. Esses truques baratos não funcionam mais hoje.</p>
<p>Mas não era só isso. Mesmo sem spammer e gente maliciosa, as primeiras versões do PageRank ainda sofriam de um problema grave. Pra ilustrar, veja meu conteúdo, meu canal e meu blog, que é de um domínio muito específico de assuntos de tecnologia pra programadores. Talvez eu faça um video explicando em detalhes sobre o algoritmo do Twiter, exatamente o que estou fazendo agora. Pra programadores é relevante.</p>
<p>Porém, imagine que um bosta como um Felipe Neto também faça um video falando sobre o algoritmo do Twitter. Segundo o PageRank, que é um ranking global, quer você goste dele ou não, se pesquisar no Google de 1997 &ldquo;algoritmo do Twitter&rdquo;, esse video do Felipe Neto iria aparecer na frente do meu. Essa é a desvantagem de um algoritmo de ranking global como o PageRank original.</p>
<p>Mas como eu falei, as pesquisas em torno de grafos evoluíram rápido. Por exemplo, em 1999 surgiu o algoritmo HITS que significa Hyperlink-Induced Topic Search, de Jon Kleinberg. HITS opera num grafo direcionado, onde páginas web representam nós e hyperlinks são bordas direcionadas. Até aqui, igual PageRank, mas o HITS assinala duas pontuações pra cada página web: uma pontuação se é um &ldquo;hub&rdquo; e outra pontuação se é uma &ldquo;autoridade&rdquo;.</p>
<p>Hubs são páginas que linkam pra diversas outras páginas. Pense um site do Estadão ou da Folha. São hubs de notícias que linkam pra trocentas outras páginas fora. Ou influencers como o Nelipe Feto, é um hub. Já autoridades são páginas que recebem muitos links de muitos hubs. Pense a página da Receita Federal em época de imposto de renda. Todo mundo faz link pra ela, pra avisar que tá vencendo a data ou ensinando como preencher seu imposto. Mas todo mundo pode ser ao mesmo tempo um hub e uma autoridade, alguns são mais hubs, alguns são mais autoridades num domínio em específico. Por isso duas pontuações distintas.</p>
<p>Portanto, o canal de um Nelipe Feto da vida teria uma pontuação de Hub muito maior do que de autoridade, que é exatamente o perfil do influencer médio: serve pra direcionar tráfego pra todo mundo, mas ele mesmo não é autoridade de quase nada. Já caras como eu geram muito pouco tráfego pros outros, mas todo mundo me menciona e linka pra mim em assuntos de um domínio específico de programação e tecnologia.</p>
<p>Com isso, minha pontuação de autoridade, num video sobre &ldquo;algoritmo do Twitter&rdquo;, seria maior que do Nelipe e eu apareceria no topo da pesquisa, mesmo ele tendo muito mais tráfego e sendo muito mais famoso. Essa é a vantagem de algoritmos da família do HITS, se comparado ao PageRank original: ele torna esse jogo bem mais justo do que uma pontuação única global, e tenta melhorar a confusão entre influencer e autoridade. Só porque alguém tem muito seguidor, de jeito nenhum o torna autoridade de coisa nenhuma.</p>
<p>Porém, se antes, a forma ingênua era ter uma única pontuação global do PageRank, em HITS teríamos sub-grafos pro domínio ou tópico, por exemplo, programação, gastronomia, viagens, cinema e cada vértice teria duas pontuações, de hub ou autoridade, por tópico. Portanto dá pra imaginar que é mais caro computar HITS do que PageRank. Claro, o que temos hoje num Google é uma amálgama dessas técnicas e muitas outras que surgiram ao longo do tempo. O que nos leva de volta a SALSA.</p>
<p>SALSA poderia ser considerado uma evolução da idéia de HITS pra análise de links, mas uma das idéias é não precisar navegar por todos os vértices e pontuar o grafo inteiro. Em vez disso ele implementa a idéia de andarilho aleatório ou random walker. É que nem pesquisas de opinião pública. Todo mundo tá acostumado a ver agências de fake news, cof, quero dizer, imprensa tradicional, fazendo pesquisas de opinião na rua pra dizer &ldquo;ah, metade dos brasileiros considera o governo atual uma bosta&rdquo;. Mas como eles sabem disso? Eu não lembro de ninguém me perguntando isso.</p>
<p>Essa é a mágica da estatística. Podemos fazer sampling, ou seja, pesquisar só uma fração da população. Quanto mais aleatório for esse sampling, melhor. Um coisa é perguntar pra 100 pessoas só de São Paulo, outra coisa é perguntar pra 4 pessoas de cada estado. Assumindo que escolhemos bem esse grupo, dentro de uma margem de erro, podemos afirmar que a população em geral tem essa ou aquela opinião, economizando ter que perguntar pros mais de 200 milhões de cidadãos do país. É uma puta economia de 200 milhões pra 100. Entendem?</p>
<p>Mesma coisa num grafo. Eu não preciso necessariamente andar o grafo inteiro, posso escolher um grupo aleatoriamente, em vez de usar o modo iterativo original do HITS, economizando muito processamento e tendo resultados que caem em margens de erro estatisticamente aceitáveis. Pra otimizar mais isso, ele também reorganiza os nós e vértices num grafo bipartido, que ajuda a separar os papéis de hubs e autoridades.</p>
<p>Posso separar nossos nós em dois conjuntos distintos: um com páginas que linkam pra outras páginas, os hubs, e outro grupo com páginas que recebem links, as autoridades. Não vale a pena eu detalhar hoje, mas gravem esses dois conceitos de random walk e grafo bipartido pra estudar depois se tiverem interesse.</p>
<p>Expliquei tudo isso porque o paper do Twitter menciona que eles desenvolveram um algoritmo de recomendação baseado em SALSA, que é um algoritmo de random walk na mesma família de HITS, originalmente desenvolvido pra ranking de pesquisa web. O algoritmo constrói um grafo bipartido de uma coleção de websites de interesse (hubs de um lado e autoridades de outro), e cada passo no SALSA navega por duas bordas - um pra frente e outro pra trás. - agora você sabe o que esse parágrafo do paper está dizendo.</p>
<p>E ele continua dizendo: nós adaptamos SALSA pra recomendação de usuários da maneira mostrada nesta figura. O lado de hubs na esquerda é populado com o tal círculo de confiança do usuário, os usuários que ele escolheu manualmente seguir. As &ldquo;autoridades&rdquo; no lado direito são populados com usuários que esses hubs interagem. Depois que esse grafo bipartido é construído, rodamos múltiplas iterações de SALSA, que assinala pontuações.</p>
<p>Daí os vértices do lado direito são ranqueados por essa pontuação e tratados como recomendações de usuários. Os vértices do lado esquerdo também são ranqueados e interpretados como similaridade entre usuários. Baseado no princípio da homofilia, que é só um nome bonito pra dizer que pessoas com interesses parecidos tendem a se agrupar mais, podemos apresentar esse resultado como recomendações, que antigamente era uma seção chamada &ldquo;similares a você&rdquo;.</p>
<p>Resolvi dar uma detalhadinha a mais até aqui pra tentar explicar como foi o raciocínio por trás da primeira versão do motor de recomendação, mas agora vou tentar andar um pouco mais rápido, porque esse foi o sistema WTF que não existe mais. Sim, toda essa pesquisa, experimentação, durou pouco tempo. Apesar de ser um sistema bom, que funcionou bem, ele ainda não é o que conhecemos hoje. E em 2012 eles começaram a perseguir o tal projeto Cassovary que mencionei antes.</p>
<p>Pra ter recomendações ainda melhores, eles queriam usar mais sinais dos logs de comportamento dos usuários. Comportamento de likes, retweets, replies, follow e tudo mais. Mas isso torna os metadados do grafo ainda maiores e a premissa da primeira versão, que era tentar manter tudo em memória, já não cabia mais. Daí veio a decisão de tentar usar Hadoop, em particular Apache Pig que, pra quem não sabe, é tipo um compilador que produz sequências de programas MapReduce.</p>
<p>Hadoop é uma plataforma de tecnologias criadas pra lidar com o problema de Big Data no auge da Bolha da Internet no começo dos anos 2000. Pra muita gente ainda hoje, o conceito de Big Data não é muito palpável, porque ninguém lida com isso no dia a dia. Varia de produto pra produto, mas quando estamos gerando coisa como gigabytes por dia de dados, terabytes por mês, estamos em território de Big Data. O que você faz, com um banco de dados que tem centenas de terabytes? Deita e chora, porque não importa quão bom seja seus índices e queries, sua aplicação vai ficar absurdamente lenta, só pelo tamanho.</p>
<p>Nessa ordem de grandeza estamos falando que não é possível manter todos os dados numa única máquina, mesmo sendo um servidor grande, seja por limitação física ou de custo. Daí a única solução é a estratégia de Napoleão: particionar, dividir esse tanto de dados em múltiplos servidores, e daí conquistar. Por isso a base do Hadoop começa com o HDFS, que é o Hadoop File System, um sistema de arquivos distribuído.</p>
<p>Agora o problema é o seguinte: na faculdade você aprendeu a lidar com dados todos no mesmo lugar. Por exemplo, digamos que você tem uma lista de produtos e preços e queremos achar o produto mais barato, como faz? Simples, escolhe um algoritmo qualquer de ordenação como um Quicksort ou Mergesort da vida, e pega o primeiro elemento que vai ser o mais barato. Pra fazer isso, precisamos iterar elemento a elemento da lista pelo menos uma vez. Mas e agora, e se estamos com 5 servidores e essa lista foi dividida por 5?</p>
<p>Intuitivamente, poderíamos pensar, beleza, vou ter que repetir a ordenação uma vez em cada máquina. Daí no final vou ter uma lista parcial com os 5 produtos mais baratos achados em cada um dos 5 servidores, daí faço uma última ordenação nessa lista e finalmente acho o mais barato. Cada um dos servidores vai rodar em um quinto do tempo do que se fosse a lista completa, mas no final, ainda tem pelo menos mais uma operação de ordenação extra e aí tenho o resultado. Então seria tecnicamente um pouco mais &ldquo;lento&rdquo; rodar em vários servidores separados, mas vai ser menos caro.</p>
<p>Isso de rodar um algoritmo em pedaços dos dados, mapear os resultados parciais e depois reduzir na resposta final, é o que, a grosso modo, chamamos de MapReduce, que é parte fundamental de como fazer processamento de dados em Hadoop. Novamente, não vou detalhar MapReduce então anote pra pesquisar mais depois. MapReduce ficou famoso porque, depois do PageRank, é como o Google conseguiu operar em larga escala e deixou todo mundo no chinelo no começo dos anos 2000.</p>
<p>Hoje em dia é como toda grande empresa de tecnologia funciona, mas 20 anos atrás, essa forma de pensar em processamento de dados foi fundamental pra possibilitar termos produtos que aceitam quantidades massivas de pessoas e transações, literalmente ultrapassando a população total de diversos países. Tem mais gente no Twitter do que a população dos Estados Unidos, metade da população do mundo tá em produtos da Meta.</p>
<p>Por isso, pra segunda versão do motor de recomendação, o Twitter deixou o FlockDB e WTF pra trás e perseguiu o tal projeto Cassovary pra rodar em cima do HDFS do Hadoop, usando MapReduce. Mas eles tiveram vários problemas. Mesmo em 2012 já era sabido que grafos não performavam bem em Hadoop. Isso porque a maioria dos algoritmos conhecidos pra processar grafos são iterativos, lembra que falei pra guardar essa informação?</p>
<p>Pra ilustrar. Pense nos diferentes algoritmos de ordenação que aprendeu na faculdade. Alguns professores cometem o pecado de não explicar porque existem tantos e porque foram criados. Por exemplo, meu professor na faculdade mandou a gente decorar lá Quicksort, Mergesort, mas não explicou que mergesort foi criado pelo lendário Von Neumann pro problema de listas de dados que não cabiam na memória RAM da época, então começavam gravados numa fita. Daí o algoritmo vai percorrendo os elementos da fita, sem estourar a RAM, e vai gravando ordenado numa segunda fita. Se tiver essa imagem de fitas na cabeça, mergesort finalmente faz sentido. Sem isso, é um algoritmo que não faz sentido existir.</p>
<p>Em 2012 ainda não existia o equivalente mergesort pra grafos que não cabem em memória e precisam ficar divididos em partições de HDFS distribuídos em múltiplos servidores. Hoje em dia já existe um suporte maior. Pense um Apache Spark com GraphX. Mas 10 anos atrás, era um uso inédito ainda. O próprio paper descreve que eles estavam cientes dessas desvantagens. O paper menciona que o gargalo era na materialização dos vizinhos dos vértices, que requeria um enorme shuffling de dados, que é pegar dados de diversos servidores pra formar o grafo necessário pro processamento.</p>
<p>Nessa parte do paper ele referencia outro paper, o &ldquo;Discovering Similar Users on Twitter&rdquo;, onde é explicado como exatamente eles conseguiram passar as barreiras de usar Hadoop, adaptando o algoritmo de PageRank personalizado pra ambiente distribuído. Como já expliquei, esse algoritmo é largamente utilizado pra computar similaridade entre nós em um grafo, mas é iterativo e requer data shuffling frequente. Quando falo data shuffling é tipo reembaralhar dados. Pensa como isso é difícil quando seus dados estão distribuídos em diferentes servidores.</p>
<p>Pra resolver, os autores do paper propõe uma modificação ao algoritmo de PageRank personalizado chamado &ldquo;One Pass PPR&rdquo;, que reduz o número de iterações e minimiza esse reembaralhamento pra cada iteração. Nesse caso, os dados são particionados baseados nos IDs dos usuários e processados numa única passada sobre os dados, usando uma combinação de pontuações local e global. O algoritmo também usa técnicas de caching pra evitar recomputar resultados intermediários, reduzindo o número de iterações totais.</p>
<p>Além disso os autores também propõe técnicas de compressão pra reduzir o tamanho do grafo e possibilitar processamento mais rápido em sistemas distribuídos como Hadoop. É muito detalhe pra tentar explicar hoje, mas esse é o resumão desse outro paper que também recomendo darem uma olhada depois.</p>
<p>É nele que se resolve o problema de conseguir dizer que quem segue um Lebron James tem alta probabilidade de seguir um Kobe Bryant ou quem segue uma Lady Gaga costuma seguir uma Taylor Swift. E é onde nasce o framework que é utilizado até hoje e que vou retornar pra explicar, sobre a arquitetura de geração de candidatos e modelo de aprendizado baseado em regressão logística e similaridade de cosseno pra achar usuários similares no Twitter.</p>
<p>Voltando ao Cassovary, é só uma parte da solução, é quem dá acesso aos dados dos grafos em Hadoop. A solução geral é chamada de RealGraph, que é uma representação composta do grafo de seguidores e grafos de interação induzidos através de logs de comportamento. Essa representação é construída de múltiplos pipelines de funcionalidades que são extraídas, limpas, transformadas e juntadas a partir dos logs crús. O RealGraph é armazenado em HDFS. E esse nome &ldquo;RealGraph&rdquo; vamos encontrar no código em Scala do repositório que foi liberado, como podemos ver no VS Code. Toda vez que ver &ldquo;RealGraph&rdquo;, provavelmente é isso.</p>
<p>Mas esse segundo motor de recomendação ainda tinha um grande defeito, só servia pra recomendar usuários similares pra você seguir e só atualizava poucas vezes ao dia, não era nem perto de ser tempo real. Funcionava, mas ainda não é a aba &ldquo;pra você&rdquo; que tem no Twitter hoje. Pulamos do WTF de 2010, pro Cassovary, Hadoop e RealGraph de 2012, e agora saltamos pra 2014 e um novo paper que descreve algumas novas estratégias pra particionar e organizar esses grafos. Como podem ver, não era um problema trivial de resolver.</p>
<p>No caso de Twitter, e até em sistemas de mensageria em geral, existe uma particularidade nos dados: você nunca precisa do grafo inteiro dos usuários. Por exemplo, pouca gente tá interessado em ver tweets de mais de 1 ou 2 dias atrás das pessoas que segue. Então mesmo que seu influencer favorito tweet todos os dias, o que seria um volume grande de tweets pro grafo, podemos limitar uma janela de 1 ou 2 dias pra todo mundo e lidar com um sub-grafo muito menor.</p>
<p>Pensando assim não precisamos lidar com operações como &ldquo;deletar&rdquo; um tweet do grafo, por exemplo. Basta pensar em expiração. É mais ou menos o mesmo pensamento por trás de caches como Memcached ou Redis da vida, que usamos hoje. Não pense em como apagar dados velhos, estabeleça uma janela de tempo e deixa expirar sozinho, que é boa prática de qualquer tipo de caching.</p>
<p>Não só isso, mas não precisamos guardar o timestamp exato de cada tweet no grafo. Isso vem da observação que a maioria das interações, como likes, retweets, replies, costumam acontecer muito próximo do momento que o tweet foi criado. Pra maioria de nós é interessante saber que o tweet foi postado 1 hora atrás, 5 horas atrás, 1 dia atrás, mas não interessa exatamente quando. É menos um dado pra ter em memória e mais uma economia.</p>
<p>Pensando em tudo que eles aprenderam até agora, iniciariam um terceiro motor de recomendação em cima de outro projeto que eles também tornariam open source, chamado de GraphJet. Ele mantém um grafo bipartido que registra integrações de usuários a tweets durante as mais recentes &ldquo;n&rdquo; horas, com uma API super simples só com as operações que eles sabiam que realmente iam precisar, sem fazer overengineering de criar um banco de dados de grafos genérico demais. Finalmente tinham experiência suficiente pra conseguir especializar a solução.</p>
<p>O resto do paper entra em detalhes sobre como foi a estratégia de mapeamento de IDs, estratégias de gerenciamento de memória pra balancear reembaralhamento e distribuição dos dados. Em resumo, eles conseguiram chegar num modelo onde só o processamento pra encontrar usuários e tweets recomendados ficou na casa abaixo dos 10 milissegundos, que é perto do ideal pra conseguir ter resultados em real-time, onde a maior parte dos gargalos era a comunicação com os diversos sistemas de filas, como o Apache Kafka que usam até hoje.</p>
<p>Esse paper é bem bacana e recomendo lerem com muita calma e anotando cada termo que não conhecem pra pesquisar depois. Ele descreve as três gerações de motores de recomendação dentro do Twitter até 2016. Não parei pra ver se tem papers mais novos que continuam essa história, mas graças a isso o código liberado que tem vários trechos que mencionam RealGraph ou GraphJet, agora devem fazer mais sentido. Entenderam? Sem esse tipo de pesquisa, só lendo o código liberado, ninguém nunca ia entender do que se trata. Precisa do contexto histórico.</p>
<p>E isso é só a parte do motor de recomendação, existem diversas outras funcionalidades no Twitter, por exemplo, o motor de pesquisa, que é quando você faz pesquisas por palavras-chave e ele retorna tweets com esse conteúdo, que nem uma pesquisa no Google. Na figura da solução do Cassovary, que puxa dados do HDFS, que vieram do FlockDB, pra cima tem fetchers que mandam pra um serviço chamado Blender.</p>
<p>Blender foi um front-end escrito em Java em cima do Netty, que é a grosso modo o equivalente a um Node.js pra Java. Por baixo usa o lendário Apache Lucene como motor de procura. Lucene é feito em Java e a gente usa faz anos. Toda vez que um site tiver um campo de pesquisa, e os resultados forem bons, pode ter certeza que tem Lucene por trás. O produto mais famoso que é construído em cima do Lucene é o Elastic, também conhecido como Elasticsearch, ou o Solr da própria Apache. Full text search, correção ortográfica, auto complete, tudo isso já é problema resolvido por Lucene, Solr ou Elastic.</p>
<p>Só encontrei um post no blog de engenharia do Twitter e outro post de um ex-programador que trabalhou no Twitter por volta de 2012, mas além do Blender existia um outro sistema relacionado à parte de pesquisa em tempo real, que é diferente do sistema de recomendação, chamado Earlybird. E eu menciono isso porque &ldquo;Earlybird&rdquo; é outro termo que aparece bastante no código que foi liberado recentemente. Então agora você sabe.</p>
<p>Aproveitando que comecei a falar de projetos open source do Twitter, na parte do paper onde ele explica o desenvolvimento da solução GraphJet, falei que tem uma seção que fala da estratégia de IDs. Isso gerou outro projeto open source chamado Snowflake, que eu não conhecia também. Mas em dados distribuídos, IDs únicos é um problema. Por exemplo, numa tabela de banco de dados MySQL, rodando na sua máquina local, você coloca um inteiro auto incremental e esquece. Cada nova linha vai ganhando um ID como 1, 2, 3 &hellip; 10, 11, 12 &hellip; 1001, 1002 e assim por diante.</p>
<p>Ids incrementais tem diversos problemas, um deles é segurança, mas o outro é conseguir particionar esses IDs. E quando eu tenho um banco de dados distribuídos em 2 servidores? Vou só dizer que no primeiro servidor vai de 1 a 1 milhão, no segundo servidor vai de 1 milhão a 2 milhões? Isso não escala. Se você for mais esperto vai lembrar, ah, podemos usar coisas como UUID ou GUID da Microsoft, IDs de 64 bits ou 128 bits aleatórios. Ou até espaços menores de 48 bits, como MAC address de placas de rede.</p>
<p>Quanto maior o ID, mais espaço vai ocupar em disco e quando carregar em memória. E se vamos desperdiçar esse espaço, o ideal é que não seja aleatório e sirva algum propósito. Daí temos IDs semi-aleatórios. No caso da estratégia do Snowflake o ID é um número aleatório prefixado com um timestamp, um identificador do servidor onde está, e uma sequência numérica aleatória. Dessa forma consigo agrupar tweets que foram criados num determinado período de tempo, tenho a localização no cluster de servidores, mas também sei que não vai ter colisão desse ID com outro ID em outro servidor. Quer dizer, até pode ter colisão mas a probabilidade é hiper baixa, perto de zero, na prática.</p>
<p>Menciono isso porque se procurar no VS Code, olha só, Snowflake aparece em diversos lugares do código. E achamos trechos que usam a função <code>timeFromIdOpt</code> onde passamos um ID de Snowflake e ele retorna um timestamp, daí podemos fazer operações, como ver se foi criado antes ou depois de determinada data. Se estudar bancos de dados distribuídos, vai ver como existem diferentes estratégias de IDs únicos pra diferentes necessidades.</p>
<p>Um banco de dados como Cassandra usa outra estratégia, chamada TimeUUID, que é muito similar, já que também tem um componente de timestamp embutido no ID, o que facilita fazer queries de períodos de tempo e análise de time series, séries de tempo e processamento de eventos. Um DynamoDB da Amazon tem um identificador único chamado de &ldquo;chave de partição&rdquo; que é usado pra particionar dados em múltiplos servidores no cluster. A chave de partição é combinado com uma &ldquo;chave de ordenação&rdquo;, que é usado pra ordenar os dados dentro de uma partição.</p>
<p>Sem fugir demais do assunto, mas já que falei disso, preciso pelo menos explicar outro conceito. Em bancos de dados distribuídos, um problema seria se um servidor ficar mais cheio que outros servidores. Idealmente queremos balancear o volume de dados entre cada servidor. Uma das formas de fazer isso é o que chamamos de &ldquo;Consistent Hashing&rdquo;. Esse algoritmo mapeia chaves de partição, como o Snowflake do Twitter ou TimeUUID do Cassandra em nós virtuais num anel, um ring. Cada nó virtual representa um servidor físico no cluster.</p>
<p>À medida que novos nós são adicionados ou removidos do cluster, os ranges de partição são redistribuídos por todos os nós virtuais, garantindo distribuição uniforme das chaves de partição. Novamente, é um problema bem conhecido e bem resolvido nos mais diversos bancos de dados distribuídos. Eu lembro quando líamos papers da Amazon sobre isso lá por 2008 e 2009. Mas pra um estudante de ciência da computação hoje, continua sendo um problema fascinante de entender, já que o conceito é bem simples. Um ID é muito mais do que só uma etiqueta qualquer. Ela serve pra facilitar pesquisas e pra facilitar balanceamento de sistemas distribuídos.</p>
<p>E pra terminar essa parte de open source, tem outros dois projetos que são mencionados que acho importante explicar. O primeiro se chama Thrift e o segundo é Protocol Buffers ou Protobufs. Acho que muitos de vocês já devem ter ouvido falar pelo menos de Protobufs. Bem a grosso modo, pense micro-serviços ou endpoints de APIs hoje. A maioria de vocês deve estar acostumado a ter endpoints que se comunicam ou simplesmente cospem dados em formato JSON. É o hello world de APIs em todo tutorial e curso. Só que JSON não é a única forma e pra muitos casos, é a pior opção.</p>
<p>JSON é um formato excelente pra endpoints de APIs que seres humanos como eu ou você vamos manipular. É indicado pra coisas como APIs de ecommerce, onde queremos puxar coisas como principais ofertas, pra mostrar no nosso blog. Mas pra comunicação entre sistemas, por exemplo, o backend do ecommerce conversar com meios de pagamento ou logística. Ou mesmo pra comunicação entre micro-serviços dentro da mesma empresa, é um dos protocolos mais porcaria que existem.</p>
<p>Como um exemplo bem, mas bem tosco, imagine este pequeno JSON de um produto de ecommerce. Quem já trabalhou com ecommerce sabe que é bem maior que isso, mas pra ilustrar aqui no video é suficiente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>{
  &#34;product_id&#34;: &#34;PS5-001&#34;,
  &#34;name&#34;: &#34;PlayStation 5&#34;,
  &#34;description&#34;: &#34;The PlayStation 5 is the next generation of gaming consoles, with advanced graphics, haptic feedback, and lightning-fast load times.&#34;,
  &#34;price&#34;: 499.99,
  &#34;availability&#34;: true,
  &#34;images&#34;: [
    {
      &#34;url&#34;: &#34;https://example.com/images/ps5-001-front.jpg&#34;,
      &#34;type&#34;: &#34;front&#34;
    },
    {
      &#34;url&#34;: &#34;https://example.com/images/ps5-001-back.jpg&#34;,
      &#34;type&#34;: &#34;back&#34;
    }
  ]
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>JSON é um protocolo texto, onde o schema vai embutido junto com cada registro. Como falei, isso facilita pra pessoas comuns conseguirem ler sem muita dificuldade. Agora imagine um schema de Protobuf assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>message Product {
  string product_id = 1;
  string name = 2;
  string description = 3;
  float price = 4;
  bool availability = 5;
  repeated Image images = 6;
}

message Image {
  string url = 1;
  string type = 2;
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note, ele define exatamente que tipo de dados, portanto qual o tamanho exato, de cada campo de dados. Dessa forma não preciso de coisas como aspas, vírgulas, nada disso, posso concatenar um valor atrás do outro, porque o tamanho vai ser fixo. No final, o mesmo JSON, convertido em Protobuf vai gerar um linguição binário que, em hexadecimal, seria assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>0a 07 50 53 35 2d 30 30 31 12 10 50 6c 61 79 53 74 61 74 69 6f 6e 20 35 1a 42 54 68 65 20 50 6c 61 79 53 74 61 74 69 6f 6e 20 35 20 69 73 20 74 68 65 20 6e 65 78 74 20 67 65 6e 65 72 61 74 69 6f 6e 20 6f 66 20 67 61 6d 69 6e 67 20 63 6f 6e 73 6f 6c 65 73 2c 20 77 69 74 68 20 61 64 76 61 6e 63 65 64 20 67 72 61 70 68 69 63 73 2c 20 68 61 70 74 69 63 20 66 65 65 64 62 61 63 6b 2c 20 61 6e 64 20 6c 69 67 68 74 6e 69 6e 67 2d 66 61 73 74 20 6c 6f 61 64 20 74 69 6d 65 73 2e 20 32 25 15 01 20 f3 26 9d 3a 40 28 01 32 2b 0a 1f 68 74 74 70 73 3a 2f 2f 65 78 61 6d 70 6c 65 2e 63 6f 6d 2f 69 6d 61 67 65 73 2f 70 73 35 2d 30 30 31 2d 66 72 6f 6e 74 2e 6a 70 67 22 04 66 72 6f 6e 74</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É isso que a nova API com Protobuf vai cuspir. Como disse antes, ao contrário do JSON, esse formato é 100% hostil pra um ser humano conseguir ler, porém, é 100% perfeito pra um computador ler. A fase de converter o JSON numa estrutura de dados é muito mais simples. Um protobuf é muito próximo de uma struct de C, só que mais genérico pra funcionar em qualquer linguagem. O JSON do exemplo é um string que pesa quase 200 bytes. Já esse linguição, que contém exatamente os mesmos dados, pesa menos de 80 bytes. Mesmo nesse exemplo super tosco, estamos falando de uma economia de 2.5 vezes na transferência de rede, sem contar a economia de processamento.</p>
<p>Lembra que falei que o Twitter lida com milhões de tweets todo dia? Imagine se internamente, em todo sistema, de pesquisa, banco de dados distribuído, sistema de recomendação, filas de processamento, ficasse trafegando tudo em JSON. Terabytes e terabytes de banda sendo desperdiçados. Por isso o Twitter adotou o equivalente a Protobufs do Google, o Thrift do Facebook. Thrift e Protobuf é quase a mesma coisa.</p>
<p>A única pergunta que você poderia ter é: por que o Twitter não usou o Protobuf do Google então? Em vez de reinventar a roda? De fato, Protobufs foram criados antes, sendo usados dentro do Google desde pelo menos 2001, mas só foi lançado publicamente em 2008. Nesse meio tempo surgiu o Facebook, que teve problemas similares pra resolver e inventaram o Thrift, que lançaram em Abril de 2007. Por isso o Twitter só tinha a opção do Thrift em 2007. Mas pra novos projetos, especialmente que interagem com componentes como PyTorch ou Tensorflow, que também veio do Google, eles passaram a usar Protobufs e você vai achar os dois no código.</p>
<p>Thrift e Protobufs são protocolos de serialização de dados. Esse tipo de protocolo é ideal pra comunicação entre micro-serviços. Só que diferente de JSON ou XML, a forma de fazer essa comunicação não é via HTTP, como num web service, e sim via RPC ou Remote Procedure Call, que formaliza como um serviço chama uma função, ou procedure, de outro serviço. No caso do Google eles tem o gRPC que é um framework de RPC que tem como objetivo prover comunicação eficiente e escalável entre serviços na rede.</p>
<p>Twitter não usou gRPC. Finagle é o equivalente de gRPC feito pelo Twitter, que usa Thrift pra serialização de dados em vez de Protobufs. Tem os mesmos objetivos de lidar com comunicação de alto throughput, baixa latência, na rede. Finagle é implementado em cima de Netty também, usando muita funcionalidade de programação assíncrona com Futures de Scala, que é o equivalente Promises de Javascript de hoje. E novamente, o Twitter Finagle foi lançado em 2011, o gRPC do Google só sairia por volta de 2015. O Twitter estava tendo que inventar tecnologias que não existiam ainda, por isso é um case fascinante.</p>
<p>Mas claro, Google sendo um nome muito maior e mais reconhecido que Twitter, matou os projetos Thrift e Finagle. Num projeto moderno, você deveria escolher Protobufs e gRPC ou alternativas mais modernas. Hoje, o Thrift ainda existe como Apache Thrift mas da Apache temos outras alternativas como Apache Avro, que foi criado mais pro cenário de streaming de dados serializados. Ou seja, quando você abre um streaming de um serviço pra outro serviço e trafega registros serializados com Avro entre eles.</p>
<p>Apache é um nome muito reconhecido pra quem é de Java e também no espaço de data sciences e machine learning. Quem lida com Kafka e é de Java, deve ter usado Avro como opção de serialização pra jobs em vez de Protobufs ou JSON. JSON é mais pra linguagens menores, como Javascript ou mesmo Ruby e PHP. Protobufs são mais usados em soluções que envolvam tecnologias do Google, como em Android. De qualquer forma, vale a pena estudar todas. Não ache que o mundo acaba em JSON ou XML. Tem coisa muito melhor e muito mais eficiente que é usado por sistemas realmente de grande volume. Pro ecommerce da esquina, de fato, tanto faz.</p>
<p>Falando em Java, acho que vale a pena eu relembrar a parte inicial dessa história. O que aconteceu justamente entre 2007 a 2010. Essa era a época que eu era mais participativo na comunidade Ruby on Rails e, claro, Twitter era um dos exemplos de maior sucesso de Rails. Mas aí veio a notícia bombástica: o Twitter declarou que estava migrando de Ruby pra Scala. E daí veio o meme que não morre de &ldquo;Rails não escala&rdquo;.</p>
<p>A forma como o Alex Payne, um dos principais desenvolvedores do Twitter descreveu, ou melhor, não descreveu os problemas que estavam enfrentando, tudo que eu expliquei até aqui, causou má interpretação e muita gente falando muita merda. Eu conversei com outro dos principais desenvolvedores daquela época, Blaine Cook, em junho de 2008. E mesma naquela época, eu também não tinha uma visão completa dos acontecimentos. Depois leiam a entrevista original que está no meu blog.</p>
<p>Por exemplo, da forma como Alex Payne falou, deu impressão que ele estava dizendo que Ruby on Rails não suportava conectar com múltiplos bancos de dados, o que não era verdade. Sim, não havia uma chavinha fácil, mas era basicamente uma linha extra que precisava ser colocada na classe pai de todos os ActiveRecords e dava pra selecionar onde fazer a conexão. Eu e vários outros escrevemos tutoriais de como fazer isso.</p>
<p>Mas a verdade é que o problema não era esse. Ninguém de fora do Twitter tinha muita noção do volume de dados que eles estavam tendo que lidar. E isso era 2007. Lembrem-se, a Amazon AWS tinha lançado não fazia um ano. Produtos que hoje são arroz com feijão, como SQS, DynamoDB, Route53 nem tinham sido lançados ainda. Firebase ainda não existia. Porra, coisas como Redis ainda não tinha sido inventado. GitHub ainda não tinha sido inventado.</p>
<p>A verdade é que particionar dados, organizar um MySQL em shards, e conseguir fazer uma arquitetura coesa em cima disso, não era trivial, e o Twitter estava numa fase de crescimento rápido. Não é que Ruby on Rails não escalava pra escala do Twitter. Nada em Python escalaria. Nada em PHP escalaria. Node.js ainda não tinha sido inventado também, mas também não escalaria. Entenda, havia poucas opções.</p>
<p>Possivelmente a opção mais completa teria sido começar a implementar um banco de dados de grafos em cima de Erlang, algo inspirado no banco de dados Mnesia que vem no OTP de Erlang. Porém, já viram Erlang? Hoje ele ainda é pouco usado, e em 2007 menos ainda. É verdade que grandes sistemas de telecomunicações já usavam fazia anos, mas era um salto bem grande pra uma tech startup tomar.</p>
<p>Quais as alternativas? Eles precisavam de uma linguagem pra escrever componentes como sistemas de filas, coisas no nível de Kafka, lembrando que Kafka só seria lançado em 2011. Coisas no nível de Cassandra, que também só seria liberado publicamente pelo Facebook em 2008. Neo4J talvez fosse o componente mais próximo de se encaixar nos problemas do Twitter, mas era super novo, tinha sido lançado em 2007.</p>
<p>Entendem? Imagine você sendo um desenvolvedor do Twitter, vendo milhares, centenas de milhares de tweets sendo postados todos os dias, e tendo que lidar com grafos de usuários e tweets, sendo que algoritmos de grafos nunca tinham sido testados com volumes desse tipo. Numa época onde toda tecnologia de Cloud e NoSQL e Devops ainda não tinha sido inventado, onde linguagens mais comuns como Ruby, Python, PHP, Javascript, não estavam ajudando e programar em C++ ou Java, parecia ser um passo pra trás.</p>
<p>Elixir não tinha sido inventado ainda. Go não tinha sido lançado ainda. Rust não tinha sido inventado ainda. LLVM ainda era uma grande novidade, já que a Apple tava só começando a mover suas ferramentas e linguagens de GCC pra Clang, como conto no episódio da Apple e GPL. Mas havia uma única linguagem que talvez pudesse fazer sentido, a linguagem Scala, que tinha sido lançado já em 2001, pelo visionário Martin Odersky.</p>
<p>Odersky é um desses gênios incompreendidos, tanto que tirando povo da área de Java, duvido que qualquer um aqui já tenha ouvido falar dele. Mas deveriam. O cara é Ph.D de ciência da computação pela ETH de Zurique e trabalhou sob a supervisão de ninguém menos que Niklaus Wirth, o inventor de várias linguagens como Modula-2 e Pascal, e o autor do livro de estruturas de dados e algoritmos que eu usei na faculdade.</p>
<p>Odersky participou e fez contribuições no mundo Java desde a versão 1.1, tendo trabalhado no compilador, na implementação de Generics, e algumas linguagens experimentais como a Pizza e Funnel, que influenciaram o design do Java.</p>
<p>Muito antes de programação funcional ficar hypezinho de hipster sem graça, como é hoje, no começo do século, ele já vislumbrava uma linguagens que destilasse o melhor da orientação a objetos, como usar traits, que são como protocolos de Objective-C, em vez de herança excessiva, mesclado com conceitos de programação funcional. Scala já tinha higher order functions, já usava tipo Opcional, evitando os famosos NullPointerException, pattern matching e tudo que se vê em linguagens mais modernas de hoje.</p>
<p>Então era de fato a linguagem mais moderna que se podia escolher em 2007, mas rodando em cima da JVM, que era a virtual machine mais madura que existia. Dava pra criar aplicações que só seriam possíveis em C++ com uma fração da dor de cabeça. Pra isso custaria um pouco mais caro em uso de recursos do que se fosse puramente nativo, mas a JVM naquele ponto oferecia um ecossistema que ninguém mais tinha.</p>
<p>E o objetivo do uso de Scala não era pra fazer front-end-zinho, era pra fazer os diversos componentes que eu expliquei que ainda não existiam. Começou com o projeto open source Starling, que é mais ou menos um mini Kafka, alguns anos antes do Kafka. Inventaram o RealGraph e GraphJet antes de um banco de dados de grafos maduro existir. Utilizaram coisas como Hadoop, que também faz parte do ecossistema Java. Nada disso seria possível sem ser, ou em Java ou em C++.</p>
<p>Hoje em dia eles teriam muito mais opções, como o próprio Elixir, Go ou Rust, e se quisessem arriscar em linguagens mais exóticas teriam D, Nim, Zig, talvez Swift, Kotlin. Mas de novo, era 2006 pra 2007 e o relógio tava correndo. Em retrospecto, foi uma decisão muito acertada. E é por isso que o código que foi liberado é 2 terços Scala e Java. Muito das pesquisas que o Twitter fez, junto com trabalhos do Facebook, Google, Apple, Amazon, gerou o atual ecossistema de linguagens, ferramentas e plataformas pra lidar com Big Data, Data Science, Machine Learning, culminando com Deep Learning e a atual geração de inteligência artificial que todo mundo tá babando. Mas foi assim que as coisas começaram, no início do século.</p>
<p>Agora que temos uma noção melhor do contexto histórico, dos projetos e papers de pesquisa gerados, podemos começar a entender melhor o código liberado. Esse código é um conjunto de diversos sub-projetos. Se abrirmos o primeiro README na raíz do repositório, eles descreveram o que são cada um desses projetos. Por exemplo, SimClusters-ann é o serviço que retorna candidatos de recomendações de tweets dado embeddings de um cluster de similaridade.</p>
<p>Esse serviço usa o algoritmo de similaridade aproximado de cossenos. Já vou explicar isso. Segundo o README, um job constrói o mapeamento entre SimClusters e Tweets. O job grava os top 400 tweets de um SimCluster e os top 100 SimClusters pra um tweet. Pontuação de favoritos e pontuação de seguidores são dois tipos de pontuação que um tweet pode ter. Os top 100 SimClusters baseados nessas pontuações é que chamam de Tweet SimCluster Embedding.</p>
<p>A similaridade de cosseno entre dois Embeddings apresenta o nível de relevância de dois tweets num espaço de SimClusters. A pontuação varia de 0 a 1. A alta pontuação de similaridade de cosseno, maior que 0.7 significa que os usuários que gostam de dois desses tweets compartilham os mesmos SimClusters.</p>
<p>Similaridade de cosseno é um conceito de Álgebra Linear pra medir quão similar são dois vetores em termos de direção e magnitude. Eu gosto desse conceito porque expliquei isso desde pelo menos 2015 até minha última palestra pública em 2019. Quem assistiu minha palestra &ldquo;Restrição == Inovação&rdquo; vai se lembrar disso, e vou aproveitar pra usar os slides que eu usava nessa palestra.</p>
<p>Comece visualizando a página inicial do Google, o campo onde se digita palavras-chaves. Podemos digitar &ldquo;apple&rdquo; e recebemos páginas indexadas da web que tem essa palavra. Um amador que acabou de aprender SQL poderia pensar que é alguma coisa parecido com &ldquo;select urls from pages where text like &lsquo;%apple%&rsquo;&rdquo;.</p>
<p>Essa é a solução que faríamos no começo dos anos 90, antes de Larry Page e Sergey Brin inventarem PageRank, antes de Jon Kleinberg inventar HITS. Essa linha de SQL de fato vai achar todas as páginas onde aparece a palavra &ldquo;apple&rdquo;, mas não vai saber como ordenar: qual delas é mais relevante e deveria estar no topo da lista? Pra saber relevância, precisamos de Álgebra Linear.</p>
<p>Tudo depende de como você enxerga o que é uma página web. Todo mundo enxerga um documento, uma coleção de palavras, que é o texto desse documento. Digamos que nosso banco de dados só tem 3 documentos indexados, como neste slide. Pra simplificar bastante, vamos assumir que nosso vocabulário é bem limitado e os documentos só tem as palavras apple e banana. O documento 1 tem 4 apples e 1 banana, documento 2 tem 3 bananas e 1 apple, e documento 3 tem 5 bananas e nenhum apple.</p>
<p>Podemos representar esses documentos como vetores, como  neste slide. Próximo passo, criamos um índice, que é uma lista que diz em quais documentos aparece a palavra apple e quais aparece banana. Pense em algo como o índice do seu banco de dados MySQL ou Postgres ou SQL Server da vida. Agora vamos digitar a palavra &ldquo;apple&rdquo; no campo de pesquisa desse nosso Google de mentira.</p>
<p>O truque começa em representar essa pesquisa, essa query, também com um vetor. No caso apple sendo 1 e banana sendo 0. É como se fosse um documento também. Pelo índice, podemos descartar o documento 3, já que apple não aparece nenhuma vez. Mas ainda temos o documento 1 e o documento 2. Qual deles é o mais relevante pra aparecer no topo da lista? De bater o olho sabemos que é o documento 1, porque aparecem a palavra apple 4 vezes, e no documento 2 só aparece 1 vez. Mas como um algoritmo conseguiria computar isso?</p>
<p>Pra isso desenhamos esses vetores num espaço vetorial, um Vector Space. No caso só temos duas dimensões, porque só temos duas palavras no nosso vocabulário. Apple é o eixo X e banana é o eixo Y. Então o vetor que representa o documento 1 seria assim: 4 unidades pra direita no eixo X, e 1 unidade pra cima no eixo Y. Já o documento dois seria só 1 unidade pra direita no eixo X mas 3 unidades pra cima no eixo Y. Finalmente o vetor da query é só uma unidade pra direita no eixo X, só 1 palavra apple, entenderam?</p>
<p>O lance com vetores é que agora temos ângulos entre o vetor de pesquisa e os vetores de documentos. E pra calcular a similaridade de cosseno usamos dot product, que é produto escalar. Funciona assim: vamos multiplicar apple com apple, banana com banana e somar os dois. Então vetor de pesquisa é 1 e 0, 1 apple e 0 bananas e vetor do documento 1 é 4 apples e 1 banana então a conta seria 1 vezes 4 mais 0 vezes 1, que dá 4, esse é o produto escalar.</p>
<p>Daí fazemos a mesma coisa entre vetor de pesquisa, de novo, e o vetor do documento 2, então o produto escalar vai ser, 1 apple vezes 1 apple mais 0 banana vezes 4 bananas, dando 1. Como o produto escalar 4 do documento 1 é maior que o 1 do documento 2, sabemos que o documento 1 tem mais similaridade com a pesquisa e, portanto, é o mais relevante pra aparecer no topo da lista.</p>
<p>Esse exemplo é super trivial porque só usamos duas palavras, então temos só duas dimensões. Mas vamos complicar. E se tivéssemos um novo documento indexado que tenha a palavra coco? O vetor dele poderia ser 1 apple, 0 banana e 2 cocos. Todos os outros vetores precisam ser atualizados pra conter coco, mas no caso tem 0 cocos então não muda nada. Pra representar o espaço vetorial, precisamos de uma nova dimensão Z. O vetor desse novo documento seria desenhado com 1 unidade pra direita no eixo X e 2 unidades pra baixo no eixo Z.</p>
<p>E podemos calcular o produto escalar entre a pesquisa e esse novo documento também. Em geometria, a gente consegue desenhar no papel até 3 dimensões, mas claro, o vocabulário de uma língua como português é muito mais que só 3 palavras. Num dicionário como o Aurélio, vai ter mais de 500 mil palavras. Portanto podemos ter potencialmente 500 mil dimensões nesse espaço vetorial. Na realidade vai ser a quantidade de palavras únicas que tem num documento, ou página web de verdade. O script deste episódio, por exemplo, tem mais de 3 mil palavras únicas. É um vetor de 3 mil dimensões.</p>
<p>Felizmente, produto escalar é uma operação muito rápida de se calcular em qualquer computador moderno, só somas e multiplicações simples. E é assim que funciona a base de todo tipo de algoritmo de relevância e recomendação. É isso que tem por baixo da biblioteca Apache Lucene que falei antes. E se olhar a documentação de um Elastic vai ver que menciona VSM, ou Vector Space Model, que é esse gráfico que vim mostrando, muito maior do que os 3 do exemplo. E é isso que esse projeto SimClusters vai fazer, só que com vetores de usuários e tweets.</p>
<p>No caso do Twitter, esse primeiro projeto se chama SimCluster-ANN. SimCluster é similarity cluster, e esse &ldquo;ANN&rdquo; provavelmente significa &ldquo;Approximate Nearest Neighbor&rdquo; ou vizinho mais próximo aproximado. Em machine learning e data mining, pesquisa de vizinho mais próximo é o problema de encontrar o ponto de dado mais próximo em um espaço de muitas dimensões, dado um certo ponto de pesquisa, onde soluções exatas são inviáveis pelo tamanho do conjunto de dados.</p>
<p>Algoritmos de vizinho próximo aproximado, como Locality-sensitive hashing ou LSH e métodos baseados em árvore como KD-trees, oferecem uma forma eficiente de resolver esse problema fazendo um trade off de algum grau de precisão por uma melhoria significativa em performance. Então é mais uma otimização do Twitter pra ganhar performance, mais ou menos como o random walk no algoritmo de SALSA que expliquei antes.</p>
<p>Nesse sub-projeto, o conceito importante são embedding spaces, que tem como objetivo responder a pergunta &ldquo;quais tweets e usuários são similares aos meus interesses?&rdquo;. Embedding funciona gerando representações numéricas dos interesses dos usuários e conteúdo de tweets. Daí podemos calcular a similaridade entre quaisquer dois usuários, tweets ou pares de usuários e tweets num espaço de embedding.</p>
<p>Justamente um dos espaços de embeddings mais úteis do Twitter são os SimClusters. SimClusters descobrem comunidades ancoradas por um grupo de usuários influentes usando um algoritmo customizado de factorização de matrizes. Existem umas 145 mil comunidades, que são atualizadas a cada três semanas. Usuários e tweets são representados em espaço de comunidades e podem pertencer a múltiplas comunidades. Elas podem ter algumas milhares até milhões de usuários. Segundo o blog de engenharia, essa imagem representa algumas das maiores comunidades.</p>
<p>Sem entrar em muito mais detalhes, vamos bater o olho em alguns outros projetos descritos naquele arquivo README. Veja um aqui chamado &ldquo;real-graph&rdquo;. Eu falei de RealGraph antes. Veja este &ldquo;Tweepcred&rdquo;, que é o algoritmo de PageRank pra calcular reputação dos usuários. Temos esse &ldquo;recos-injector&rdquo; que é descrito como um processador de eventos em streaming pra construir os streams de entrada pra serviços baseados em GraphJet. Já sabemos o que é GraphJet, mas &ldquo;recos&rdquo; eu acho que significa &ldquo;recomendações&rdquo;, então é um injetor de recomendações pra GraphJet.</p>
<p>Pulando lá pra baixo temos esse &ldquo;light-ranker&rdquo; que é um ranqueador mais leve usado pelo índice de pesquisa, o tal Earlybird que mencionei antes, que é um projeto legado e parece que ainda é usado; o componente construído em cima de Apache Lucene. O heavy-ranker está no outro repositório &ldquo;the-algorithm-ml&rdquo;, que o blog post do Steven, que falei lá no começo, menciona e segundo o README é o responsável pela aba &ldquo;Pra você&rdquo; e tem aquela lista de pesos.</p>
<p>Nesse ponto da cadeia, depois da fase de candidate sourcing, temos uns 1500 candidatos que podem ser relevantes. Pontuação serve pra prever a relevância de cada tweet candidato e é o sinal primário pra ranquear tweets na timeline. Nesse estágio, todos os candidatos são tratados igualmente. O ranqueamento sai de um rede neural de uns 48 milhões de parâmetros que é continuamente treinada com interações de tweets pra otimizar o engajamento positivo (como likes, replies e retweets), segundo o blog de engenharia do Twitter.</p>
<p>Sendo bem honesto, eu não tenho certeza de como exatamente isso funciona ainda. Olha o diagrama que tem no próprio arquivo README que lista os projetos. Os dados vem dessas caixinhas intituladas &ldquo;Grafo Social&rdquo;, &ldquo;engajamento de tweets&rdquo; e &ldquo;dados dos usuários&rdquo;. Daí temos features, como o GraphJet, SimClusters, TwHIN que é outro projeto no repositório the-algorithm-ml, e serve pra pré-treinar os tais embeddings.</p>
<p>Daí temos o RealGraph, TweepCred e Trust &amp; Safety, que parece ser um projeto de tensorflow pra treinar modelos pra filtrar coisas como conteúdo pornográfico, adulto, ou o que ele considera como &ldquo;tóxico&rdquo;. Não sabemos exatamente porque não foi liberado o modelo e nem os dados de treino. É um projeto bem pequeno e quem entende de Tensorflow poderia dizer um pouco mais, mas não tem muito pra se ver.</p>
<p>Continuando, temos essa coluna que ele chama de Home Mixer. Daí temos 3 fases: uma que ele chama de candidate sourcing, que é literalmente &ldquo;escolher os candidatos&rdquo;, candidatos de recomendação. Esses candidatos passam pelo tal heavy ranker, que parece ser o modelo de machine learning via PyTorch. O resultado disso passa pra etapa final de heurísticas e filtros.</p>
<p>Então, pra se chegar nos tweets recomendados na aba &ldquo;Pra você&rdquo;, na etapa de sourcing já acontece diversos filtros pra selecionar os candidatos de tweets que você pode gostar de ver. Esses &ldquo;filtros&rdquo; passam pelos projetos de SimCluster, RealGraph, T&amp;S, Tweepcred, os GraphJets e são mesclados pelo projeto cr_mixer. Isso vale comentar. Os tweets recomendados, mais ou menos, são metade de tweets gerados pelas pessoas que você segue, o que ele chama de &ldquo;In-Network&rdquo; e a outra metade é de tweets de pessoas que você não segue, que ele chama de &ldquo;Out-of-Network&rdquo; e quem puxa isso é esse projeto cr-mixer.</p>
<p>Dado esse conjunto inicial de tweets, In e Out of network, aí vem a parte de recomendação propriamente dito usando o light ranker, o projeto Earlybird, e daí o heavy ranker. Mas antes de mandar pra você, ainda passa por um pipeline de filtros. Pelo projeto home mixer propriamente dito, esse visibility-filters que é onde está implementado a política de censura do Twitter.</p>
<p>Por último, tem esse projeto Navi, que parece mais recente, escrito em Rust, mas é super pequeno e não faz muita coisa. Parece mais um adaptador pra falar com os componentes de Tensorflow e PyTorch e um tal de Onnx, que eu não conhecia, mas parece que significa Open Neural Network Exchange, que é um formato open source pra representar modelos de machine learning. Foi originalmente desenvolvido pela Microsoft e Facebook em 2017 pra facilitar interoperabilidade entre diferentes frameworks de deep learning, por exemplo, entre PyTorch e Tensorflow. Mas realmente não sei como ele é usado neste projeto ainda.</p>
<p>Se tiverem interesse em saber mais, cada um desses sub-projetos tem um README também, então recomendo que comecem por eles. Mas como disse no começo, se tentar rodar <code>bazel build</code> em qualquer diretório, ele vai reclamar da falta de um arquivo de Workspace. Então, até o Twitter liberar isso, ou alguém se dar o trabalho de gerar na mão, não temos como compilar e nem de tentar rodar nada.</p>
<p>Por isso que é meio chato, porque mesmo lendo essas coisas todas na documentação e no próprio código, não temos como testar. Basta um if que a passamos batido e talvez algum desses componentes nem rodem. É muito difícil saber o comportamento exato dum sistema grande desses sem ter uma massa de dados de entrada pra ver o que realmente é cuspido na saída.</p>
<p>De qualquer forma, do ponto de vista de organização, além da falta de arquivos pra fazer build, também senti muita falta de arquivos de testes automatizados. Não tem como saber se eles tiraram antes de abrir em público, se ainda vai ser liberado, ou se realmente não existem. Espero que existam, porque dar manutenção num sistema grande desses, sem testes, seria uma grande burrice.</p>
<p>Se eles tivessem liberado os arquivos de testes, nós teríamos uma idéia muito melhor de como esse sistema de comporta, mesmo sem poder compilar e rodar. Testes servem como se fosse uma documentação viva de um sistema. Teria sido muito mais fácil confirmar tudo que analisamos.</p>
<p>Mas no geral, minha opinião é que o código Scala parece muito bem feito, dado o contexto que se trata de um legado de mais de 10 anos. Eu esperaria algo muito mais bagunçado, muito mais macarrônico. Comparado com o código do Twitch, que eu analisei em outro video quando foi vazado, eu preferiria muito mais trabalhar neste código do que no outro.</p>
<p>O básico tá bem feito: métodos curtos, baixa complexidade ciclomática, nomenclatura bem feita, nada de variáveis &ldquo;x&rdquo; ou &ldquo;y&rdquo; que não dá pra saber do que se trata. Pouca coisa hard-coded, tudo organizado em arquivos de configuração, e até que bem comentados onde precisa. Não é comum ver projetos legados com esse nível de qualidade. Não é nem de longe perfeito, mas até que me surpreendeu.</p>
<p>Obviamente, esse repositório é só um espelho incompleto do repositório de verdade, que permanece fechado dentro do Twitter, sob confidencialidade. O código que foi liberado também não pode ser usado por nenhum projeto fechado sem liberar as modificações feitas em cima desse código. É isso que a licença AGPL que eles colocaram significa: eles não querem que ninguém adicione em outro projeto e mantenha fechado.</p>
<p>Depois que soltaram esse código, um bando de imbecis ficou spamando a seção de Issues, mas teve um ou outro pull request que eles aprovaram já, nada significativo. Só a remoção daquele código de métrica que tinha o &ldquo;author_is_elon&rdquo; que imbecis adoraram ficar fazendo meme. Não teve nenhuma contribuição significativa ainda e nem espero que vá ter, até que eles liberem os arquivos de Workspace do Bazel.</p>
<p>Eu não tenho muito senso de humor pra memes idiotas e gente que acha bacana ficar pichando uma seção que deveria ser séria como a área de Issues. Quer fazer meme idiota, fica no Reddit ou no 4chan. Não tem o mínimo de etiqueta, não participa. Participação desse tipo é só idiota, não tem nenhum valor e não contribui com absolutamente nada. É só certificado de idiota mesmo.</p>
<p>Ao longo de mais de 10 anos, o Twitter fez sua parte e contribuiu tecnicamente. Os papers que falei, os projetos open source, os diversos blog posts de engenharia, inspiraram muitos outros projetos a tentar arquiteturas e tecnologias que, na época, sequer existiam ou se existiam, ninguém sabia se funcionava. Independente da sua opinião política, que é irrelevante, o Twitter certamente contribuiu ordens de grandeza mais do que qualquer um dos engraçadinhos que não sabem respeitar um trabalho de engenharia.</p>
<p>Ter acesso a esse código não me torna mais fã do Elon Musk, mas só serve pra confirmar muitas coisas que eu já sabia sobre os primeiros engenheiros do Twitter: eles foram muito bons, especialmente pra época. E pra vocês que são iniciantes, aproveitem a oportunidade pra fuçar tanto o código quanto os papers que eu citei. É assim que um produto em produção evolui. Só nesse sistema de recomendações, vimos 3 gerações diferentes de soluções, cada uma gerando mais experiência pra fazer o próximo ser melhor. Código de produção de verdade, de um produto que tem uso, nunca acaba.</p>
<p>Você que é empreendedor querendo entender mais de programação, entenda: se seu código não está evoluindo, é porque seu produto é irrelevante. O Twitter existe desde 2006, e o código nunca chegou numa versão que todo mundo tirou a mão e falou &ldquo;tá perfeito, não precisa mexer mais&rdquo;. O mesmo acontece com qualquer produto, ecommerce, elearning, fintech, todos continuam evoluindo, pivotando, ajustando e consertando pra ficar cada vez melhor. O Twitter de hoje é bem diferente do Twitter de 2007, mesmo que só pelo front-end não pareça que tenha mudado muita coisa. A cara não muda tanto quanto o backend.</p>
<p>Dá pra ficar literalmente dias, indo de arquivo em arquivo, tentando desvendar quem chama quem, quem depende de quem, em que momentos, quais serviços rodam pra quais tipos de dados. Mas é super trabalhoso fazer isso sem conseguir rodar nada e sem testes. Eu não acho que vale a pena, mas na internet sempre tem alguém com mais boa vontade de fazer trabalho chato como esse. Com apenas 2 dias olhando pra esse código, eu só consigo descrever até aqui.</p>
<p>E com tudo isso, pelo menos espero que pra vocês que sejam iniciantes, tenham tido uma perspectiva diferente do que imaginavam que seria um código de verdade de produção. Mesmo sem entender nada, deve dar pra começar a entender melhor a dimensão. Repetindo: esse repositório liberado só tem parte do código do sistema de recomendação. Não tem o front-end web, não tem o app mobile, não tem as ferramentas de administração, não tem o ferramental de monitoramento, enfim, só estamos vendo uma fração do que é feito o Twitter.</p>
<p>Antes de terminar, enquanto estava escrevendo este episódio, o canal Fireship lançou também um video curto falando disso. Não tem nada demais, mas eles lançaram um segundo especialmente sobre &ldquo;Vector databases&rdquo;. Só assistindo esse video dele, duvido quem a maioria tenha entendido. Eu expliquei o que é isso durante este video, e se você já não tinha conhecimento prévio de Álgebra Linear, provavelmente entendeu mas não compreendeu.</p>
<p>Enfim, o Fireship menciona bancos de dados modernos de vetores como Pinecone e Weaviate e disse que vai fazer um curso sobre isso. Não sei se foi piada, como eles sempre fazem, ou se vão mesmo, mas vou ficar de olho pra saber. Problema de um curso desses é o famoso &ldquo;olha como se faz um hello world, é super simples&rdquo;, mas sem entender tudo que falei hoje, você vai usar sem entender. Mas assistam lá depois. Vou deixar o link na descrição abaixo.</p>
<p>E finalmente, isso responde outra pergunta que todo mundo tem: precisa saber matemática pra ser programador? E a resposta é não. Pra fazer coisa simples, sabendo matemática básica de regra de três e porcentagem, dá pra ir se virando. No dia a dia de software pequeno, se souber pelo menos usar uma calculadora, já funciona. Agora, sem saber Álgebra Linear, Cálculo, Estatística e Probabilidade, você nunca vai trabalhar em projetos como esse do Twitter, é uma impossibilidade. Matemática é opcional, inglês é opcional, tudo depende de quão pouco você quer ter de opções. Se ficaram com dúvidas, mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e não deixem de compartilhar o video com seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>machine-learning</category><category>ciencia-da-computacao</category><category>akitando</category></item><item><title>[Akitando] #122 - Detecção e Correção de Erros | Introdução a Redes Parte 2</title><link>https://akitaonrails.github.io/2022/07/13/akitando-122-detecao-e-correcao-de-erros-introducao-a-redes-parte-2/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/07/13/akitando-122-detecao-e-correcao-de-erros-introducao-a-redes-parte-2/</guid><pubDate>Wed, 13 Jul 2022 10:50:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/3W-8TQJwuWY"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Apesar de ser Parte 2 da mini-série de redes, vou falar pouco sobre redes propriamente ditas e focar em um aspecto que tem a ver com mais coisas que só rede: detecção e correção de erros.&lt;/p&gt;
&lt;p&gt;Já se fez essa pergunta? Dados são transmitidos como ondas eletromagnéticas por cabos de cobre ou sem fio. Bits são gravados magneticamente em mídias como HDs ou Blu-Rays. Como que nenhum bit nunca é gravado ou transmitido errado? E se é, como poderíamos saber se 1 único bit é entendido errado?&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/3W-8TQJwuWY"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Apesar de ser Parte 2 da mini-série de redes, vou falar pouco sobre redes propriamente ditas e focar em um aspecto que tem a ver com mais coisas que só rede: detecção e correção de erros.</p>
<p>Já se fez essa pergunta? Dados são transmitidos como ondas eletromagnéticas por cabos de cobre ou sem fio. Bits são gravados magneticamente em mídias como HDs ou Blu-Rays. Como que nenhum bit nunca é gravado ou transmitido errado? E se é, como poderíamos saber se 1 único bit é entendido errado?</p>
<p>Mas não se preocupem, apesar de não ser diretamente &ldquo;redes&rdquo; é importante saber o episódio de hoje, mas no próximo episódio vou realmente mostrar mais de &ldquo;redes&rdquo;.</p>
<h2>Conteúdo<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00 - Intro</li>
<li>01:20 - Recapitulando RAID-0 e RAID-1</li>
<li>05:37 - Bit Rot, Bit Flip e Raios Cósmicos</li>
<li>10:37 - Hamming Code</li>
<li>15:11 - Hamming e Cartões Perfurados</li>
<li>15:53 - Eficiência de bits de paridade</li>
<li>19:37 - Checagem de erros em rede</li>
<li>20:51 - Usernet e Par2</li>
<li>24:23 - M-Discs</li>
<li>27:27 - Data Scrubbing</li>
<li>28:40 - Conclusão</li>
<li>29:43 - Bloopers</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>WHAT CONNECTS AN INCIDENT IN 2003 WITH SPACE, ASSEMBLY ELECTIONS, INTEL, AEROSPACE AND ELECTRIC CARS? (<a href="https://www.linkedin.com/pulse/what-connects-incident-2003-space-assembly-elections-intel-jogani/"target="_blank" rel="noopener">https://www.linkedin.com/pulse/what-connects-incident-2003-space-assembly-elections-intel-jogani/</a>)</li>
<li>How An Ionizing Particle From Outer Space Helped A Mario Speedrunner Save Time (<a href="https://www.thegamer.com/how-ionizing-particle-outer-space-helped-super-mario-64-speedrunner-save-time/"target="_blank" rel="noopener">https://www.thegamer.com/how-ionizing-particle-outer-space-helped-super-mario-64-speedrunner-save-time/</a>)</li>
<li>COSMIC RAY FLIPS BIT, ASSISTS MARIO 64 SPEEDRUNNER (<a href="https://hackaday.com/2021/02/17/cosmic-ray-flips-bit-assists-mario-64-speedrunner/"target="_blank" rel="noopener">https://hackaday.com/2021/02/17/cosmic-ray-flips-bit-assists-mario-64-speedrunner/</a>)</li>
<li>Bit Rot: What It Is and How To Stop It From Destroying Your Data (<a href="https://getprostorage.com/blog/bit-rot-stop-destroying-your-data/"target="_blank" rel="noopener">https://getprostorage.com/blog/bit-rot-stop-destroying-your-data/</a>)</li>
<li>How to send a self-correcting message (Hamming codes) (<a href="https://www.youtube.com/watch?v=X8jsijhllIA"target="_blank" rel="noopener">https://www.youtube.com/watch?v=X8jsijhllIA</a>)</li>
<li>Hamming codes part 2, the elegance of it all (<a href="https://www.youtube.com/watch?v=b3NxrZOu_CE"target="_blank" rel="noopener">https://www.youtube.com/watch?v=b3NxrZOu_CE</a>)</li>
<li>What is error correction? Hamming codes in hardware (<a href="https://www.youtube.com/watch?v=h0jloehRKas"target="_blank" rel="noopener">https://www.youtube.com/watch?v=h0jloehRKas</a>)</li>
<li>Reed–Solomon error correction (<a href="https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction"target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Reed%E2%80%93Solomon_error_correction</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>No episódio anterior eu quis trazer algumas noções de como dados trafegam por redes cabeadas ou wireless. Isso porque ultimamente andei brincando com coisas como meu novo NAS. Mas antes de poder falar dele e coisas que andei prometendo como Docker, achei que finalmente chegou a hora de falar um pouquinho sobre redes. Como já tinha falado antes, estou longe de ser especialista de redes, então quero explicar as coisas do ponto de vista mais de alguém que é programador e usa a rede mais do que monta redes.</p>
<p>No episódio de hoje quero explorar o motivo de porque eu montei um NAS: evitar perder dados. A maioria das pessoas hoje tá acostumado a um mundo plug and play, sempre conectado, serviços de cloud e tudo mais e tem muito menos preocupação com seus dados. Na real, a maioria não tem dados tão importantes que se perder vai ser um grande problema mesmo. Por isso acho que não tem problema. Se perder, perdeu. Se doeu, deveria ter se precavido. Mas programadores eu acho que deveriam ter um pouco mais de cuidado, especialmente porque lidam com dados dos outros.</p>
<p>Sobre isso tem dezenas de assuntos que poderíamos falar, como segurança de dados, mas hoje quero falar só sobre os blocos mais básicos de como erros podem acontecer e como a ciência da computação vem resolvendo esses problemas desde pelo menos os anos 60.</p>
<p>(&hellip;)</p>
<p>Veja meu NAS. Ele está construído numa configuração RAID-6. Mas vamos falar de novo do formato mais simples, só 2 HDs. Se ainda não assistiu minha playlist de armazenamento, vou resumir só essa parte de novo. Com dois HDs eu posso fazer 2 coisas: escolher segurança ou escolher performance. Se eu for um gamer que posso baixar tudo de novo da Steam caso perca os HDs, talvez faça sentido colocar em modo RAID-0. Nesse modo o sistema operacional vai enxergar os dois HDs num único volume lógico. Se os HDs tem 1 terabyte cada, em RAID-0 o volume lógico, o meu &ldquo;C:&rdquo; no Windows, vai ter 2 terabytes inteiros e vou ter quase o dobro de performance, porque posso escrever nos dois ao mesmo tempo.</p>
<p>Lembram o que já expliquei antes sobre arquivos e transmissão de dados em geral? A maioria das pessoas olha um Windows Explorer e assume que a menor unidade são arquivos. Os sistemas operacionais escondem os detalhes e mesmo programadores só lidam no máximo com arquivos na maior parte do tempo. Mas isso é ineficiente. HDs não são dispositivos de arquivos, são dispositivos de blocos. Por isso o serviço da Amazon onde posso instalar um HD virtual na minha máquina virtual do EC2 se chama EBS ou Elastic Block Store. Ele nos dá um dispositivo de blocos. Eu explico isso nos videos sobre armazenamento, assistam lá depois.</p>
<p>No episódio passado eu falei que comunicação de redes usa hoje em dia comutação de pacotes. Pacotes são essencialmente blocos de bits. Por isso tenha na cabeça que tanto arquivos no seu sistema como transfência desses arquivos pela rede, acontecem de forma fragmentada, um pacote de cada vez e não um arquivo inteiro de uma vez. Por isso tenho o dobro de performance num RAID-0. Se o sistema só enxergasse arquivos, quando fosse gravar, gravaria o arquivo inteiro só em um HD, daí a velocidade máxima seria só desse HD.</p>
<p>Mas como na realidade a partição é uma tabela que tem um registro dizendo &ldquo;arquivo hello.mp3 tem 2000 blocos de 4 kilobytes cada, e os blocos de ID 1 até 1000 estão no HD 1 e os blocos de 1001 até 2000 estão no HD 2&rdquo;, daí ele pode mandar gravar os blocos nos dois HDs ao mesmo tempo, e por isso eu tenho mais velocidade tanto pra ler quanto pra gravar, porque o arquivo pode ser segmentado nos dois HDs. Mas também por isso esse sistema de RAID-0 tem um grande calcanhar de aquiles: e se um dos HDs sofrer um defeito e morrer?</p>
<p>Se um HD morrer, você perdeu tudo dos dois HDs. Entenderam? Por que alguns blocos dos arquivos no HD que está vivo estavam no HD que morreu, então você ficou com um monte de arquivos quebrados que não servem pra nada. Por isso que você nunca deve usar RAID-0 se os dados que tiver sejam importantes. Se era só pra games que você pode recuperar tudo depois, beleza. Compra outro HD, remonta o RAID-0, formata tudo, reinstala o Windows do zero e baixa tudo de novo da Steam. A importância aqui era a performance e os dados tinha backup fora, então ok.</p>
<p>Mas se vai misturar com trabalho, e coisas que se perder vão te dar prejuízo, e só puder comprar dois HDs, então coloque eles em RAID-1. Esse é o sistema de espelhamento. O seu sistema operacional vai enxergar os dois HDs de 1 terabyte como se fosse um único HD de 1 terabyte. Toda vez que mandar gravar um arquivo, o sistema vai mandar todos os blocos pros dois HDs e fazer uma cópia idêntica, bloco a bloco. Então a performance vai ser a de 1 único HD, um pouco menos até porque agora vai ter o trabalho de gravar no outro HD também. Quando ler, não vai ter quase diferença. Em termos de performance, vai perder um pouco, então, qual a vantagem?</p>
<p>A vantagem é que se um dos HDs morrer, o sistema automaticamente elege o outro como HD primário e na prática você nem vai notar. Seu sistema não vai crashear e vai poder continuar usando tudo como antes. Zero perda de dados. Agora é só comprar outro HD, substituir o que falhou, e no próximo boot o sistema vai copiar tudo do HD que sobrou pro HD novo, criando outra cópia espelho. Aqui trocamos um pouco de performance e metade do espaço por segurança.</p>
<p>Pra entender os outros níveis de RAID com mais HDs, de novo, veja minha playlist sobre armazenamento pra entender mais sobre isso de blocos e redundância de HDs, mas pra hoje eu precisava que vocês tivessem na cabeça esse modelo de tradeoff de performance, segurança e redundância. Isso porque poderia parecer que um RAID-1 é perfeito e vai deixar seu sistema protegido. Isso é verdade até certo ponto. É muito raro de se ver, mas um evento que acontece é bit rot ou degradação de bit ou simplesmente bit flip.</p>
<p>Entenda, qualquer sistema de armazenamento, seja um HD mecânico, seja um SSD que usa chips NAND flash como também SD cards ou pendrives, seja memória RAM, tudo está sujeito a uma hora sofrer algum tipo de degradação. O melhor é quando a falha é bem aparente e ele pára de funcionar totalmente. O problema é quando ele continua funcionando mas os bits armazenados não são mais confiáveis. Tudo é gravado magneticamente. Informação é um linguição de bits. Bits são cargas eletromagnéticas, armazenadas ou transportadas num fio de cobre ou em ondas pelo ar. Qualquer tipo de interferência eletromagnética poderia flipar bits aleatoriamente.</p>
<p>Por isso existe blindagem e todo um processo de fabricação pra evitar ao máximo qualquer tipo de interferência externa, mas nada é perfeito. Escrevendo sobre isso me lembrou de duas historinhas que seria legal vocês saberem. Em 18 de maio de 2003 aconteceu a primeira eleição com voto eletrônico da cidade de Schaerbeek em Bruxelas. Milhões votaram eletronicamente pela primeira vez. Tudo correu conforme esperado até o dia de contar e checar os votos.</p>
<p>Aí a comissão da eleição notou duas coisas fora do comum. Primeiro porque uma candidata razoavelmente desconhecida ganhou milhares de votos a mais do que seria esperado. Mas a segunda coisa que foi mais absurda é que o total de votos foi maior do que o total de pessoas que votaram! Claramente isso é um problema e obviamente vocês imaginam o tanto de gente apontando o dedo um pro outro atrás de fraude. Teve diversas investigações pra entender o que aconteceu, se foi um bug ou manipulação, mas uma coisa chama a atenção. O excesso de votos contados dava exatamente 4096 votos.</p>
<p>Pra todo mundo, 4096 é só um número, mas pra gente de computação, 4096 é um número muito específico. Se vocês não sentiram alguma coisa quando ouviram o número, precisam praticar mais, porque é exatamente 2 elevado a 12. Lembra quando eu falo que vocês precisam se acostumar a converter números decimais e pensar em binário? Um número é uma cadeia de bits, digamos de 32 bits. Se você flipar o bit na posição 13, vai dar esses 4096 a mais se lá estivesse zero. Não quer dizer que não houve fraude, mas é muita coincidência alguém que fosse fraudar pensar num número exatamente assim.</p>
<p>O objetivo não é detalhar o caso, então depois Googlem pra saber como foram as investigações, de fato encontraram bugs de segurança e coisas assim no software. Todo software de votação, ainda mais na primeira vez, ainda mais se não foi visto e revisto, testado e retestado e auditado por diferentes grupos de programadores independentes, certamente tem bugs importantes. Não é talvez, é certeza absoluta. Mas mesmo assim, nenhum dos bugs encontrados conseguia explicar esse acidente em particular. E uma hipótese que não dá pra provar, mas tem chances de acontecer, é que esse bit foi flipado por raios cósmicos.</p>
<p>Pois é, caso você não saiba, estamos o tempo todo sendo bombardeados por milhares de partículas subatômicas que vieram de explosões de Supernovas, que caem na Terra como raios cósmicos. Muitos experimentos delicados são feitos abaixo da terra, com muita blindagem, pra tentar minimizar o efeito desses raios, onde qualquer mínima variação da ordem de um elétron, pode mudar os resultados. Dependendo do estudo que achar pode encontrar que é possível ter 1 bit flip por gigabyte de memória por ano. Sabe aquela tela azul de Windows que só aconteceu uma vez e nunca mais e não teve motivo nenhum? Talvez tenha sido um bit flip de raios cósmicos.</p>
<p>Tem muito mais histórias do que essa das eleições e só mais uma que eu achei interessante porque é um assunto que eu gosto, foi num speedrun de 2013 de Super Mario 64, do usuário DOTA_Teabag. Speedrun pra quem não sabe é competição pra ver quem consegue acabar um jogo no menor tempo possível. Durante esse speedrun, ele esbarrou num glitch considerado impossível sem manipular o jogo. Num determinado pedaço da fase, ele tava simplesmente pousando numa plataforma e fez um warp pra cima do teto sem nenhuma razão.</p>
<p>Todo mundo da comunidade tentou replicar isso, usando emuladores, replicando exatamente os passos do Teabag frame a frame com scripts, mas ninguém conseguiu replicar esse efeito. Isso porque pra realizar esse warp requer o que se chama de um single event upset que é fora do controle do jogador. A única forma de replicar o warp é fazendo um bit flip no número que representa o peso do Mario. Se você flipar 1 bit mudando de C5 pra C4 hexadecimal isso dá o peso exato pra fazer o warp pra cima naquele exato momento.</p>
<p>De novo, é impossível dizer que foi isso, mas a hipótese de novo é que raios cósmicos fliparam esse 1 bit naquela hora, causando o warp. Mas o ponto é que bit flips acontecem, e raios cósmicos é só mais um fator numa enorme lista de fatores que podem levar à corrupção de dados. Em particular quando usamos mídias magnéticas pra armazenar e transportar dados. E se tudo isso ainda não te convenceu, não precisa ir muito longe: quantos de vocês lembram da época de CDs piratas de games e como vocês gostavam de esfregar o disco na camiseta e mesmo o disco tendo vários riscos, ele ainda funcionava. Como pode essas coisas?</p>
<p>Isso é tudo pra vocês entenderem que erros e corrupção de dados são inevitáveis e se vocês nunca notaram isso é um testamento de como a ciência da computação já conseguiu resolver a grande maioria desses problemas e pra todo mundo, tudo simplesmente só funciona. Vamos recapitular. Imagine um arquivo de imagens como esse gato. Obviamente é sempre um gato. Mas o que a gente vê é a representação visual de um linguição de bits. Eu expliquei isso no episódio sobre compressão. Só por conveniência pra mostrar aqui na tela posso representar essa linha contínua de bits quebrando em várias linhas, digamos, em linhas de 11 bits. E eu posso organizar mais ainda cada linha de 11 bits como um bloco.</p>
<p>Em particular, vou organizar os 11 bits da primeira linha num bloco de 16 posições, numerado de zero a quinze. As posições 0, 1, 2, 4, e 8 vamos reservar e eu preencho as 11 posições seguintes com meus bits de dados. Agora vamos olhar metade dos bits e checar o que chamamos de paridade. Paridade é literalmente se temos um número par ou número ímpar de bits 1. Nesse caso temos 2 bits 1, então é par. E assim preenchemos o bit de paridade na posição 1 com zero.</p>
<p>Agora olhamos o próximo grupo e checamos a paridade. Temos 3 bits 1, é ímpar então na posição 2 preenchemos esse outro bit de paridade com 1. Agora que vimos os grupos de colunas na vertical, vamos checar grupos na horizontal. Esse próximo grupo também tem número ímpar de bits 1. Portanto no bit de paridade da posição 4 preenchemos com 1. E finalmente checamos o último grupo, que também tem número ímpar de bits 1, portanto preenchemos o bit de paridade da posição 8 com 1.</p>
<p>Finalmente, checamos a paridade do bloco inteiro. Temos 8 bits 1, é par, então o bit de paridade da posição 0 vai ser zero. Pode parecer complicado mas aguentem até o final do exemplo pra eu explicar o que estamos montando aqui. Nesse momento criamos um bloco de 16 bits pra carregar 11 bits de dados e 5 bits de checagem de paridade. Agora faz de conta que estamos transmitindo esse bloco pela internet, sujeito a todo tipo de interferência eletromagnética, erros de burst, degradação do material do fio e tudo mais, e um ou mais bits desse bloco vai ser flipado no meio do caminho. Mas na outra ponta a gente não sabe disso.</p>
<p>Agora a outra ponta recebeu esse bloco. Duvido que vocês lembrem de cabeça como era o bloco original, então vamos checar a paridade. Checamos o primeiro grupo e vemos número par de bits 1, então o bit de paridade na posição 1 está correto com zero e sabemos que a 2a e 4a colunas parecem ok. O próximo grupo tem um número par de bits 1 mas no bit de paridade na posição 2 está 1, o que é um problema. Sabemos que tem um erro em algum lugar nessas últimas duas colunas. Mas como já sabemos que a quarta coluna tava ok, o problema tá em algum lugar na 3a coluna.</p>
<p>Checando o próximo grupo, temos um número ímpar de bits 1 então o bit de paridade na posição 4 está correto sendo 1, portanto sabemos que a 2a e 4a linha estão ok. E no último grupo temos um número par de bits 1 mas o bit de paridade na posição 8 está 1 em vez de zero. Então temos algum problema nessa duas últimas linhas. Mas já sabemos que a 4a linha tava ok pela checagem anterior, portanto o erro tem que estar na 3a linha. E como já sabemos que tem algum erro na 3a coluna, cruzando as duas sabemos que deve ter um erro na posição 10. Esse bit foi flipado.</p>
<p>Mais do que isso, olhando o bloco inteiro, vemos que a paridade é impar, então no bit de paridade do bloco deveria estar 1 em vez de zero. Isso nos dá confiança de saber que só houve 1 bit flip e não dois, portanto só o bit da posição 10 foi flipado. E se mudarmos de 1 pra zero, jogarmos fora os 5 bits de checagem, temos o bloco correto original de 11 bits que foi enviado.</p>
<p>O que fizemos aqui foram duas coisas: nós tivemos a capacidade de checar a existência de um bit flip, e ainda tivemos a capacidade de recuperar o bit que foi danificado e recuperar o bloco inteiro. O que vocês acabaram de ver foi o algoritmo de correção de erros Hamming Code, nomeado depois de seu criador Richard Hamming. E essa é uma das formas que temos hoje pra correção de erros.</p>
<p>Essas animações devem ser familiares, porque peguei emprestado do excelente canal 3Blue1Brown, que fez dois videos excelentes dois anos atrás explicando exatamente como funciona o Hamming Code. E ele fez isso em conjunto com outro canal que vocês já viram eu mostrar aqui, o do Ben Eater, que implementou Hamming Code em protoboards, em hardware, pra demonstrar como é possível colocar correção de erros em hardware. Eu altamente recomendo que assistam os videos deles pra saberem em detalhes como que isso funciona. Eu assisti esses videos dois anos atrás e tava esperando uma oportunidade pra encaixar eles nos meus videos. Quem seguiu minha recomendação de seguir os canais deles já deve ter visto.</p>
<p>Richard Hamming é um pesquisador da época que se programava em cartões perfurados. Imagina você levar dias escrevendo um programa nesses cartões daí leva pra uma máquina mecânica que vai ler esses cartões através dos furos. Mas vira a mexe ele erra um furo e registra a informação errada. Se até hoje aquelas porcarias de máquinas de refrigerante não conseguem identificar seu dinheiro, imagina uma máquina mecânica velha que tem que ler centenas de cartões sem errar nenhum furo. Isso acontecia tanto que deixou o Hamming putaço. E não existe melhor forma de invenção do que não se acostumar com problemas e sim coçar a própria coceira. E assim ele fez, e inventou todo esse sistema de paridade do Hamming Code pra checar e corrigir erros.</p>
<p>A gente usa Hamming Code até hoje em coisas como memória ECC ou Error Correction Code em RAM de workstations e datacenters. A memória que você tem no seu PC provavelmente não é ECC, mas o servidor de cloud que armazena seus dados certamente tem ECC. E é por isso que coisas como raios cósmicos não são aparentes no nosso dia a dia. Outra forma de correção de erros é o Reed-Solomon Code, que é usado em CDs e Blu-Rays e é por isso que mesmo você riscando o disco, esfregando na sua camiseta, ainda é possível ler os dados.</p>
<p>Mas o mais astuto entre vocês assistindo poderia pensar. Peraí, você tá me dizendo que pra um bloco de 11 bits eu precisei reservar mais 5 bits pra redundância. Ou seja, 45% do espaço e banda de transferência foi desperdiçado pra isso? Então num Blu-Ray de 40 gigabytes eu precisaria ter quase 20 gigabytes a mais só pra redundância?</p>
<p>Mas o mais astuto do mais astudo entre vocês talvez tenha notado que eu fui fazendo checagens naquele bloco de metade em metade. Toda vez que em ciência da computação você vê procuras feitas de metades de metades deveria vir à cabeça procura binária. Lembra os episódios onde falei de árvores e porque árvores são uma das coisas mais importantes que todo programador deveria ter em mente?</p>
<p>Checamos 2 paridades de 2 grupos de colunas verticais, depois 2 paridades de 2 grupos de linhas horizontais. Num bloco pequeno de 16 bits precisamos fazer 4 perguntas de paridade pra localizar e corrigir o bit flipado. Mas e num bloco bem maior, de 256 bits? Vamos precisar fazer só 8 perguntas de colunas e linhas, e pra isso precisamos reservar só 8 bits de redundância. Ou seja, só 3% do tamanho dos dados e não 45% como era no bloco de 16 bits. Quanto maior for o bloco, menor vai sendo a quantidade de bits de redundância necessário. Esse é o poder da procura binária. E eis porque vocês precisam estudar algoritmos e estruturas de dados.</p>
<p>Estamos usando blocos de 16-bits só porque é mais fácil de colocar aqui na tela ou pra você ver no seu celular. Mas um bloco de sistema de arquivos costuma ter pelo menos uns 4 kilobytes de dados. Um bloco de rede costuma ter pelo menos 1500 bits. Mas em rede não se usa nem Hamming e nem Reed Solomon. Só pra coisas como programa espacial da Nasa, onde o Voyager por exemplo usa Reed Solomon Code pras transmissões. Hoje em dia se usa variações de Reed Solomon pra fitas magnéticas, CDs, Blu-rays e toda mídia de armazenamento, incluindo HDs. Você nem sabe disso, mas toda vez que um bloco de dados é gravado no disco, o sistema calcula esses bits de redundância e grava no lugar apropriado no HD.</p>
<p>Então tá resolvido tudo então? Só usar Hamming ou Reed Solomon em tudo que tá tudo certo? Mais ou menos. Por mais que eles sejam altamente eficientes e não gastem tantos bits assim, ainda assim são bits extras, pra cada bloco de bits. A indústria de HDs por exemplo vem pesquisando outras formas que sejam quase tão eficientes quanto Reed Solomon, mas perdem em 0.1% dos casos, como o LDPC code. Reed Solomon tem complexidade O(n^2) o que significa que pra discos gigantes de dezenas de terabytes leva muito tempo pra corrigir erros. Particularmente ruim em data centers que tem arrays com centenas de HDs. Um LDPC atinge 99.9% de recuperação comparado com Reed Solomon mas com complexidade O(n). É uma área ainda com pesquisas ativas.</p>
<p>Recuperar erros não só gasta bits extras de armazenamento como exige processamento pra fazer essa correção. Imaginem agora switches de rede em escala de internet. Por isso eu acho que em rede não se fala tanto em correção de erros e sim em checagem de erro. É muito menos trabalho identificar que tem um erro, mas não recuperar esse erro. Se você tá fazendo aquele download longo e vê a velocidade cair porque a internet da sua região é bem instável, não é tentativa de correção de erros que tá acontecendo e sim repetição de pacotes.</p>
<p>Em rede, funciona mais ou menos assim. Seu arquivão de download é quebrado em vários pacotes, enviados um atrás do outro. Seu PC vai recebendo esses pacotes e remontando o arquivo. Mas ele faz uma checagem antes. Essa checagem é parecida com a checagem de bits de paridade do Hamming Code, mas um pouquinho só mais sofisticado. No caso de internet falamos de checksum, em particular, Internet 16-bits. Esse checksum é uma soma dos bits do bloco dividido por palavras de 16-bits. Existem formas melhores como Fletcher&rsquo;s checksum ou CRC, que é Cyclic Redundancy Check. Mas eu acho que ainda se usa Internet 16-bits mesmo.</p>
<p>De qualquer forma, entenda que existe essa checagem via checksum pra cada pacote. Se for detectado que houve um bit flip ou coisas como burst, que é comum, o pacote é rejeitado e seu PC manda aviso de volta pro servidor, &ldquo;olha, o pacote ID xpto veio cagado, manda de novo&rdquo;. Esse é NAK que falei antes. Daí o servidor manda o mesmo pacote outra vez. É um dos motivos de porque você vê a velocidade de download caindo e flutuando. No caso de rede é mais barato pedir o pacote de novo do que tentar corrigir ele. Correção é mais importante quando você não tem pra quem pedir uma cópia original, como arquivos no seu HD, que só você tem.</p>
<p>No começo da internet a gente usava um serviço chamado Usenet, que usava o protocolo NNTP. Pensa tipo um sistema de fóruns. Assim como o protocolo SMTP de email ou HTTP de web, era outro protocolo baseado em texto puro de 8 bits. Ele foi feito pra transmissão de texto e não arquivos binários. Pense em usenet como Wordpress que povo tenta entuchar mais coisa do que ele foi planejado pra fazer. Usenet foi feito pra sincronizar artigos de texto entre diferentes servidores Usenet. Mas obviamente, a gente achou jeitos de entuchar arquivos binários pra fazer pirataria. Estamos falando de uma era anos antes de bittorrent.</p>
<p>Eu mesmo fiz muito isso na faculdade. A gente baixava imagens, software e tudo mais da Usenet. O problema é que por diversas razões, os arquivos podiam acabar corrompidos. Erros de conversão. Erros nos discos rudimentares dos servidores da época e tudo mais. Mas o problema é que invariavelmente eles acabavam corrompidos. Primeiro, a gente precisa resolver o problema de trafegar arquivos binários num protocolo de textos.</p>
<p>Os mais astutos já tem a resposta na ponta da língua. Base64. É uma forma de transformar binários em textos de 7-bits. Base64 não é encriptação de jeito nenhum. O problema é que se você tentar abrir um arquivo binário num notepad, não tem como dar copy e paste em outro notepad e salvar o arquivo, vai corromper tudo, porque um editor de textos não reconhece todos os símbolos binários. A solução é converter o binário num sub-conjunto de ASCII que um editor de textos entenda e depois converter de volta num binário. O custo disso é gastar um pouco mais de espaço.</p>
<p>Cada dígito de Base64 representa exatamente 6 bits de dados. Então 3 bytes de 8 bits cada do arquivo, ou seja, 3 x 8 ou 24 bits, podem ser representados por 4 dígitos de 6 bits, que dá 24 bits também. Isso significa que a versão Base64 do arquivo vai ser 133% maior do que o original. Um arquivo binário de 100 megabytes vai virar 133 megabytes em Base64.</p>
<p>Como arquivos podem ter tamanhos variados, como um filme longo por exemplo, ou o instalador do Photoshop ou coisas assim, pra Usenet não reclamar, a gente quebrava esse binários primeiro em múltiplos binários usando o bom e velho formato rar ou WinRar, porque assim como Zip ele compactava o arquivo, mas diferente de zip, ele permitia quebrar em múltiplos arquivos com final .rar2, .rar3 e assim por diante. E depois que baixava todos os rars, o WinRar conseguia remontar e descompactar o original.</p>
<p>Então o processo era simples: faz vários arquivos rar, converte em texto Base64, na época usando a ferramenta uuencode do UNIX, que significa unix to unix encode. Postava cada pedaço em um artigo da Usenet. Do outro lado a gente baixava o base64 de cada pedaço, usava uudecode pra conseguir os binários de volta, e usava um equivalente WinRar pra descompatar os arquivos originais. Era assim que a gente fazia antes de Google Drive, Mega ou BitTorrent.</p>
<p>Como disse antes, a Usenet não foi feita pra transportar arquivos, e por inúmeras razões, incluindo defeitos nesse monte de passos que a gente tinha que fazer, alguns bits acabavam corrompidos. Então a gente precisava de uma forma de garantir que dava pra recuperar de alguma forma após o download, caso notássemos que o arquivo veio corrompido. E aí surgiu a idéia do Parchive ou arquivos .par ou a versão melhor que foi o Par2. Agora você podia baixar os arquivos rar e também os arquivos par2, que era bem menores. E se o rar estivesse corrompido na hora de descompactar, podia experimentar recuperar usando os arquivos .par2.</p>
<p>E o que é Par2? É basicamente aqueles bits de redundância como no exemplo do Hamming Code, só que são os bits pra Reed Solomon Code, como eu disse que funciona pra CDs. Os primórdios da Internet era muito legal porque a gente era obrigado a aprender essas coisas se quisesse baixar pirataria. Eu me lembrei disso porque recentemente decidi fazer backup dos videos originais aqui do canal em M-Discs ou Millennial Disc, a evolução dos discos Blu-Ray. É tecnologia nova, de 2010 pra cá. E me perguntaram se não era bom eu fazer parchives pra gravar junto, eu até fiz, mas acho que não precisava.</p>
<p>De curiosidade pra quem não sabia, CDs, DVDs e Blu-rays comerciais comuns, vem com uma camada de proteção pra proteger contra riscos leves, aquela esfregada leve na camiseta. Porém, essa camadinha é feita de material orgânico que reage com oxigênio e umidade do ar. Significa que mesmo que você guarde com todo cuidado possível, eventualmente essa camada vai degradar e você pode acabar com um disco que não dá mais pra ser lido. Pode demorar 10 anos, demorar um pouco mais, mas é questão de tempo. Sua coleção de CDs não vai durar pra sempre a menos que esteja numa câmara a vácuo com zero umidade e temperatura controlada. E mesmo com os bits de redundância de Reed Solomon, você vai perder dados se essa camada danificar o suficiente.</p>
<p>Por isso escolhi M-Discs. Eles são basicamente Blu-rays só que a camada de proteção é feita de material inorgânico, não reagente, não oxidante. Só pra ter uma imagem, pensa uma camada de proteção feita de pedra. Em testes de mais de 250 horas sob temperaturas de 90 graus Celsius, 85% de umidade, mesmo assim os discos permanecem intactos. Por isso se chamam millennial discs, são discos feitos pra durar mil anos, sob condições adversas. Perfeitos pros meus backups. Alguns vieram sugerir fitas magnéticas, mas nesse mesmo nível de durabilidade, custam ordens de grandeza mais caro por gigabyte.</p>
<p>E eu disse que não precisava ter manualmente criado arquivos par2 com os bits de Reed Solomon Code porque um Blu-ray por padrão já tem esses bits. Hoje em dia tem poucos motivos pra se preocupar com bits de redundância porque a maioria dos sistemas de armazenamento modernos tem esse cuidado, incluindo seu SSD e seu HD. Só sua RAM que ainda não porque memória ECC é bem mais cara. Mas como no mundo caseiro a gente dá refresh nessa RAM porque bootamos toda hora, a chance de um bit flip causar problemas é muito baixa. De vez em quando, uma tela azul de Windows sem motivo nenhum, é a rara consequência que você vai encontrar.</p>
<p>Hoje em dia não precisamos baixar arquivos par pra recuperar downloads corrompidos, mas muitos sites de downloads você já deve ter visto um código md5 ou sha1 junto pra checar a integridade do binário. Eu já expliquei sobre isso no episódio de criptografia, mas um sha1 é um algoritmo de hashing, um tipo de checksum. Se baixarmos a ISO de uma distro de Linux, e tiver sequer um bit flip, passando por sha1 o hash resultante vai ser completamente diferente do hash original que deveria ser. Por isso é um mecanismo importante pra sabermos se não houve bit rot ou mesmo manipulação no meio do caminho. Sha1 é muito superior a uma mera checagem de paridade, mas também custa mais caro pra processar.</p>
<p>Menciono isso porque já que comecei falando de NAS, em sistemas de RAID como o Linux Raid ou sistemas de arquivos realmente modernos como Btrfs ou ZFS que expliquei no último episódio da série de armazenamento, eles oferecem proteção contra erros e exigem rodar uma tarefa em background com alguma periodicidade chamada Data Scrubbing. No ZFS tem um comando justamente chamado <code>scrub</code> pra isso, detectar e corrigir data rot.</p>
<p>No meu NAS também tem essa opção de agendar um data scrubbing a cada 3 meses ou o período que eu quiser. Não precisa rodar sempre. Mesmo o HD tendo suporte a Reed Solomon Code, nem isso garante que vai estar tudo ok. Checar e corrigir erros é uma tarefa que custa processamento e tempo, por isso não se roda toda hora. Ele vai usar coisas como checksum de cada arquivo, equivalente ao que falei de checar o hash depois de um download, pra ver se o arquivo está intacto. Isso exige ler e calcular em cima de todos os bits do arquivo.</p>
<p>Por isso custa processamento. Se coisas como o checksum ou paridade não baterem, ele usa a cópia redundante do RAID pra reconstruir o pedaço com danificado. E vai fazendo isso arquivo a arquivo. Se tiver poucos erros é fácil de consertar, mas se deixar acumular por chegar num estado que é impossível recuperar, por isso se roda data scrubbing periodicamente.</p>
<p>Como disse antes, pra maioria de vocês, tudo isso é invisível. Se um pacote na rede é corrompido, seu sistema pede pro servidor reenviar os pacotes. Se um arquivo é levemente corrompido na hora de gravar no seu HD, ele tem sistemas pra se auto corrigir. Com exceção de desastres graves, você raramente tem problemas de ver corrupção de dados. Seu streaming sempre funciona, as páginas que carrega sempre vem inteiros, e tudo isso porque pesquisadores como Hamming, Reed, Solomon, Shannon e muitos outros criaram as bases pra que tudo isso funcione. Mas de qualquer forma é bom saber porque as coisas funcionam num ambiente hostil onde corrupção de dados deveria ser o normal.</p>
<p>O objetivo desse episódio era complementar o que falei nos episódios de armazenamento, no episódio sobre compressão, e o anterior onde comecei a falar sobre transmissão de dados e redes. Viemos muito longe desde um simples telefone de copinho e barbante e agora você sabe porque. Se ficaram com dúvidas mandem nos comentários abaixo. Se curtiram o video deixem um joinha, assinem o canal e compartilhem o video pra ajudar o canal. A gente se vê, até mais.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>redes</category><category>armazenamento-e-backup</category><category>akitando</category></item><item><title>[Akitando] #118 - Fiz um servidor de "SQL"?? | Entendendo Banco de Dados</title><link>https://akitaonrails.github.io/2022/05/09/akitando-118-fiz-um-servidor-de-sql-entendendo-banco-de-dados/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/05/09/akitando-118-fiz-um-servidor-de-sql-entendendo-banco-de-dados/</guid><pubDate>Mon, 09 May 2022 11:20:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/_7nISfpofec"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;O que tecnologias de compiladores e estruturas de dados tem a ver com banco de dados? Resolvi brincar de fazer um pequeno banco &amp;ldquo;stupid&amp;rdquo; pra vocês terem uma visão diferente sobre o que é um banco de dados por baixo dos panos. Disclaimer: isso não vai ser um tutorial de SQL, mas acho que pode mudar a visão de vocês sobre bancos.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/_7nISfpofec"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>O que tecnologias de compiladores e estruturas de dados tem a ver com banco de dados? Resolvi brincar de fazer um pequeno banco &ldquo;stupid&rdquo; pra vocês terem uma visão diferente sobre o que é um banco de dados por baixo dos panos. Disclaimer: isso não vai ser um tutorial de SQL, mas acho que pode mudar a visão de vocês sobre bancos.</p>
<h2>CONTEÚDO<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00:00 Intro</li>
<li>00:00:35 Problema de Tutoriais/Cursos</li>
<li>00:02:51 Banco de Dados de Usuários</li>
<li>00:03:29 &ldquo;Simulando&rdquo; banco de dados</li>
<li>00:04:58 &ldquo;Fake&rdquo; SQL?</li>
<li>00:07:16 Construindo o Fake SQL</li>
<li>00:10:59 Experimente com Código!</li>
<li>00:15:39 Testando Fake SQL com Jest</li>
<li>00:19:38 Adicionando &ldquo;SQL&rdquo; no &ldquo;Fake SQL&rdquo;</li>
<li>00:22:01 Conhecendo Antlr4</li>
<li>00:24:39 Copiando do SQLite3</li>
<li>00:30:34 Criando meu Listener de Parse Tree</li>
<li>00:39:41 O que é um Índice?</li>
<li>00:49:10 Bancos de Dados tem Interpretadores!</li>
<li>00:49:51 Transaction Log!</li>
<li>00:55:33 Como &ldquo;grava&rdquo; um banco de dados em disco??</li>
<li>01:05:57 B-Trees</li>
<li>01:08:30 Fazendo Cliente-Servidor!</li>
<li>01:09:55 Conclusão</li>
<li>01:12:26 Bloopers!</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>GitHub do Stupid Database - código do episódio (<a href="https://github.com/akitaonrails/akitando_episode_0118"target="_blank" rel="noopener">https://github.com/akitaonrails/akitando_episode_0118</a>)</li>
<li>GitHub do repositório de gramáticas do Antlr (<a href="https://github.com/antlr/grammars-v4"target="_blank" rel="noopener">https://github.com/antlr/grammars-v4</a>)</li>
<li>SQLite Amalgamation (<a href="https://www.sqlite.org/amalgamation.html"target="_blank" rel="noopener">https://www.sqlite.org/amalgamation.html</a>)</li>
<li>Visualização de AVL Tree (<a href="https://www.cs.usfca.edu/~galles/visualization/AVLtree.html"target="_blank" rel="noopener">https://www.cs.usfca.edu/~galles/visualization/AVLtree.html</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Finalmente resolvi falar um pouco sobre bancos de dados. Esse é provavelmente o primeiro grande desafio pra todo mundo que começa a carreira de programação e parece uma parede muito alta pra escalar, muitos acham intimidante, e muitos começam a usar, decoram alguns comandos SQL, mas não tem muita certeza do que diabos estão fazendo. O objetivo de hoje é tentar dar um modelo mental que vocês consigam enxergar servidores de bancos de dados de uma forma menos complicada.</p>
<p>(&hellip;)</p>
<p>Alguns avisos antes de começar. O episódio de hoje não vai explicar nenhum banco de verdade, seja Postgres ou Mongo. Quem está esperando um tutorial de SQL, não vai ser este video. O começo vai parecer estranho se estava esperando isso, mas tenha paciência e continue acompanhando, eu acho que vocês vão ficar surpresos. Até pra quem já entende de banco de dados, no mínimo vai ter material aqui pra ajudar vocês a explicarem pros seus colegas iniciantes.</p>
<p>Outra coisa, todos os exemplos de código que vou mostrar serão em Javascript. Eu pessoalmente teria feito tudo em Ruby ou Crystal que são linguagens que sou mais confortável, mas acho que a maioria das pessoas assistindo é mais Javascript, então vamos de Javascript. Quem for de outras linguagens deve conseguir acompanhar porque não vou usar nada exótico. Tudo que eu fizer aqui dá pra fazer em Python, PHP, C# ou o que quiserem. Desta vez todo o código está disponível num repositório no GitHub que vou deixar o link nas descrições abaixo. Inclusive fica de lição de casa refazer o que eu mostrar aqui na linguagem favorita de vocês. O código desse repositório é ingênuo de propósito porque foi um exercício que eu decidi fazer durante o feriado de Tiradentes e eu me coloquei um limite de 2 a 3 dias pra fazer tudo.</p>
<p>A motivação desse episódio é porque a menos que eu esteja muito enganado, a maioria dos tutoriais e cursos sobre banco de dados começa mais ou menos assim. Faça &ldquo;apt install mariadb&rdquo;, que é um fork do MySQL. Daí termina de instalar, inicia o serviço, abre o console que conecta no servidor e começa: agora vamos fazer &ldquo;create table users&rdquo; bla bla e depois &ldquo;insert into users&rdquo; bla bla e finalmente &ldquo;select name from users&rdquo; e já vai direto pra explicar SQL. Pior ainda se começa num tutorial de framework, que aí você já cai num Hibernate ou Sequelize e sequer sabe o que tá acontecendo por baixo.</p>
<p>Só que isso eu acho que é ir do zero a 100 muito rápido. Onde que os dados ficam gravados? Onde é esse tal de &ldquo;banco&rdquo;? Como que uma linguagem como SQL atua em cima desses dados? Pra que serve um índice? Qual a diferença disso pra eu simplesmente gravar um JSON num arquivo? Vamos dar uma pausa e começar do começo, bem do começo. E no começo tudo que vocês sabem é lidar com variáveis e collections como arrays ou sets ou, no caso de Javascript, objects.</p>
<p>Pois bem, pra começar resolvi escrever o banco de dados mais simples que dá pra fazer. Chamei ele de Stupid Database, porque não é pra ninguém achar que isso é um banco de verdade. Ele só se comporta como um. Tenho uma tabela de usuários, num array simples, onde cada linha é um objeto de Javascript, como podemos ver aqui. Na 1a linha, com ID 1 temos ninguém menos que nosso querido Peter Parker. Não esse, esse outro aqui. Com uns 25 anos, da cidade de Nova Iorque. Na 2a linha, com ID 2, temos o Clark Kent com 35 anos, de Metropolis, e assim por diante. Um total de 37 linhas nesse array, com várias pessoas desconhecidas, de nomes comuns.</p>
<p>O que é isso? É um array de objetos. Também podemos chamar isso de &ldquo;tabela&rdquo;. Olha só. Se eu abrir um console de Node.js, carregar esse arquivo com <code>db = await import('./lib/users-database.mjs')</code> e fizer <code>console.table(db.users)</code>, ele vai printar exatamente como qualquer um esperaria ver uma tabela, certo? Pra todos os efeitos e propósitos, essa é uma tabela mínima.</p>
<p>Ok, mas se é uma tabela, é possível inserir novas linhas, certo? Claro. Podemos criar um novo usuário, vamos dar o nome de outra pessoa desconhecida de nome comum, Levi Ackerman, uns 37 anos, de Paradis. E pra inserir num array de Javascript usamos a função <code>.push</code>. E no nosso caso, inventei que o id de cada linha é um número crescente que começa em 1, então essa nova linha vai ser o tamanho da tabela mais um. E pronto, esse seria o equivalente em SQL a fazer um <code>insert into users</code> da vida.</p>
<p>Beleza, mas então qual é o equivalente a um <code>select</code> de SQL? Simples, uma das formas é a função <code>filter</code>. Eu passo pra função outra função que ele vai executar em cada objeto, um por um em cada linha desse array, no caso, procurando o objeto que tem o nome &ldquo;Levi Ackerman&rdquo;. O resultado disso é outro array só com os objetos encontrados. Podemos passar de novo pelo comando <code>console.table</code> e voilá, esse é o resultado correto, que seria o equivalente em SQL a fazer <code>select * from users where name = 'Levi Ackerman'</code>.</p>
<p>Aí nesse ponto você deve estar cético, pensando aí. Porra, Akita tá de sacanagem. Isso aí é só uma porcaria de um array, não é um banco de dados. Eu quero ver comando de SQL, porra! Ok, talvez você tenha razão. Então deixa eu ir pro terminal e executar um programinha que eu escrevi, um REPL. Lembram o que é um REPL? O comando <code>node</code> onde eu estou é um REPL, um console que interpreta os comandos que eu digito imediatamente e já me dá o resultado. O REPL <code>node</code> interpreta javascript e executa. Pra quem é de Python, se digitar <code>python</code> vai abrir o REPL de Python, que executa imediatamente os comandos em Python que digitar, e assim por diante. Várias linguagens tem REPLs que já vem na instalação ou que você pode instalar separadamente, mas só lembrar que REPL é um console interativo da linguagem. É uma ferramenta comum em linguagens dinâmicas.</p>
<p>Enfim, vou executar o REPL que eu escrevi e chamei de <code>repl.mjs</code>. Olha só, abriu uma linha de comando. Vocês queriam ver SQL? Tá bom, vamos começar digitando <code>insert into users (name, age, city) values ('Levi Ackerman', 37, 'Paradis')</code>. Opa, retornou o número 38. Agora vamos ver se inseriu mesmo, vou digitar a query <code>select * from users where users.city = 'Paradis'</code>. E, olha só, achou esse senhor desconhecido de nome Levi, como deveria.</p>
<p>Pronto, não era SQL que vocês queriam ver? Tá aqui. Mas isso não é MySQL, não é Postgres, nada nessa manga, nada nessa outra, é só Javascript puro. Vamos fazer outra query, quero todos os indivíduos nessa tabela com idades entre 60 e 2000 anos, porque alguns indivíduos tem expectativa de vida bem alta ultimamente. Como que faz? <code>select * from users where users.age between 60 and 2000</code>, é assim que vocês aprenderam né? E olha só, obviamente, achou certinho. Olha lá o Steve Rogers com 100 anos, acho que é 100 né? Ou nosso amigo Thor com 1500 anos. Só não comer açúcar, fazer dieta intermitente, e vive saudável muitos anos.</p>
<p>Eu espero que eu tenha conseguido deixar vocês confusos. O que diabos tá acontecendo aqui? Eu abri uma linha de comando bizarra e comecei a digitar SQL como se fosse um Postgres da vida, e ele claramente tá pesquisando em cima daquele array de objetos em Javascript. Como isso é possível? E esse é o real objetivo desse episódio, eu quero mostrar como eu fiz isso e em paralelo vocês entenderem mais ou menos como um banco de dados de verdade é feito. Então vamos lá.</p>
<p>Primeiro de tudo vamos deixar super claro: isto é meramente um exercício educacional, um código que eu me limitei em uns 2 a 3 dias pra fazer. Todo código que vou mostrar a partir de agora não tem condições de ser usado em produção de verdade. Pra isso ele precisaria ser bem mais complicado pra levar em consideração coisas como autenticação e autorização, locks e concorrência, otimizações de performance, otimizações de redundância, etc e tudo isso só ia dificultar a explicação. Por isso eu fiz só o mínimo do mínimo necessário pra ficar fácil de explicar, que é o objetivo de todo código educacional.</p>
<p>O segredo de tudo começa na biblioteca que eu chamei de <code>lib/fake-sql.mjs</code>. Essa é uma coleção de funções que eu escrevi com nomes estratégicos. Deixa eu mostrar. A primeira é a função <code>from</code> que recebe como argumentos as variáveis <code>table</code> e <code>conditions</code>. Lá na frente eu vou carregar o array <code>users</code> e outro array chamado <code>heroes</code>, tudo dentro de um hash chamado <code>database</code>, daí se fizer <code>database['users']</code> eu acesso o mesmo array que tava mostrando até agora.</p>
<p>E aqui uma das vantagens de usar uma linguagens dinâmica interpretada como Javascript. Já que performance e segurança não são preocupações pra hoje, eu posso fazer um protótipo simplesmente montando aquele comando de <code>filter</code> como fiz antes concatenado com a variável <code>conditions.where</code>. Se não ficou claro, deixa eu mostrar o que essa função faz. Vamos abrir o console do Node e deixa eu dar uns copy e paste só pra importar esse arquivo &lsquo;fake-sql.mjs&rsquo; e o mesmo &lsquo;users-database.mjs&rsquo; de antes. Não se preocupem com essa parte que é só carga.</p>
<p>Pronto, agora vamos fazer a mesma pesquisa de antes onde eu quero todo mundo com idades entre 60 e 2000 anos. É só fazer <code>from('users', { where: 'users.age &gt;= 60 &amp;&amp; users.age &lt;= 2000'})</code> que é a mesma coisa que o <code>between 60 and 2000</code> que fiz em SQL. De propósito chamei essa função de <code>from</code> e fiz um hash com uma chave <code>where</code> pra ficar meio parecido com SQL. E olha só, voltou o mesmo resultado de antes. Tô vendo o Steve Rogers, o Thor, a Diana e o Hank Pym. Essa função é o grosso do meu simulador de SQL, todo o resto agora é perfumaria.</p>
<p>Pra parecer mesmo SQL falta começar com <code>select</code> né? Pra que serve o select? Serve pra eu pegar só as colunas, ou chaves, que eu quiser, em vez de todas. Vamos criar a função <code>select</code> lá na biblioteca então. E olha só, parece mais complicadinho. Essa função recebe como argumentos uma lista de colunas e o resultado do <code>from</code>, ou seja, aquele array de resultados.</p>
<p>A primeira metade é outra trapaça. Se eu pedir &ldquo;*&rdquo; ele vai substituir por uma lista com todas as chaves dos objetos. Aqui estou assumindo que todos os objetos do resultado sempre tem as mesmas chaves. Não estou checando nada e não vou introduzir o conceito de Schemas, por isso falei que tô trapaceando. Enfim, daí ele vê se o argumento foi a palavra &ldquo;count&rdquo;, daí devolve direto só o total de objetos no array de resultados. E caso contrário, pega a string que passei e dá split por vírgula, pra transformar num array.</p>
<p>O resultado dessa função é um <code>map</code>. Lembra o que faz um map? Ele pega uma coleção, transforma cada elemento e devolve tudo em outra coleção, convertendo cada objeto da coleção original usando essa função que passo de parâmetro. Em resumo, vai transformar meu objeto original num outro objeto só com as chaves que pedi pra selecionar. Vamos ver na prática lá no mesmo console de Node.</p>
<p>Estou de novo no console e faço <code>select('name,age')</code>. Aqui é uma escolha de design, eu poderia passar como parâmetro um array com os strings &rsquo;name&rsquo; e &lsquo;age&rsquo; mas só pra ficar mais &ldquo;limpo&rdquo; nessa sintaxe que tenta imitar SQL, decidi passar tudo grudado num único string. Por isso, do lado de dentro, preciso fazer um split. Obviamente esse é o jeito menos otimizado de passar parâmetros, mas como eu disse antes, é só um exercício.</p>
<p>Aproveitando a deixa, é isso que eu falo em vários videos: experimente com código. Faça experimentos como esse na sua máquina, código que não é de nenhum projeto do trabalho, é só um treino sem compromisso. E aí experimente designs que normalmente ninguém deixaria você fazer no trabalho. Só quando se experimenta coisas que ninguém faria é que às vezes pode dar inspiração pra coisas que podem ser úteis.</p>
<p>Pra quem curte anime, isso me lembra o Food Wars, já viram, Shokugegi no Souma? É uma história de competição numa escola culinária onde o protagonista, por acaso, adora fazer comidas bizarras que nunca dão certo. Esse é o motivo. Se você nunca tentou fazer os códigos mais impensáveis, nunca vai chegar num design inovador de verdade. Abra um diretório vazio, começa a escrever, apaga, joga fora, vai treinando. Ninguém precisa nem saber.</p>
<p>Enfim, voltando ao console, depois de passar a lista de colunas que queremos, como segundo parâmetro colocamos a mesma função <code>from</code> de antes. Olha o resultado: uma lista filtrada só com as colunas que queremos, sem a coluna de cidade ou id, no caso. E veja de novo a implementação. Eu pego o resultado e faço um map. A função de transformação faz um array só com as colunas que eu escolhi. E no final faço um <code>reduce</code> pra recriar objetos com o nome das colunas como chave.</p>
<p>Parece um pouco complicado se você nunca usou &ldquo;map&rdquo; e &ldquo;reduce&rdquo;, que existem em todas as linguagens e servem pra transformar listas e reduzir o resultado num acumulador. Teste só esse trecho num script com vários tipos de listas pra entender o que isso tá fazendo. É bem básico e você precisa saber. De novo, exercite até conseguir enxergar esse trecho e saber exatamente o que está acontecendo. Tem outras formas de escrever a mesma coisa pra ter o mesmo resultado, eu poderia não ter usado map e reduce e feito só com <code>for</code>.</p>
<p>Enfim. Olha o que estou fazendo. Só de escrever duas funções, esse &ldquo;select&rdquo; e &ldquo;from&rdquo; que recebe um hash de condições com uma chave &ldquo;where&rdquo;, já consigo simular o básico do básico de um select de SQL. Só pra ver mais um exemplo, num SQL você sabe que poderia fazer <code>select name from users where bla bla order by name desc</code> pra ordenar o resultado em ordem decrescente de nome. Como daria pra fazer nesse meu fake SQL? Vamos criar uma nova função chamada <code>orderBy</code> que recebe como parâmetro o nome da coluna por onde queremos ordenar, daí um segundo parâmetro pra dizer se é ordem ascendente ou descendente e como terceiro parâmetro, de novo o resultado da função <code>from</code>.</p>
<p>Essa implementação é simples, é basicamente o que qualquer iniciante de Javascript que já estudou algoritmos simples de ordenação já exercitou: implementar a função de comparação que se passa pra função <code>sort</code> que todo array tem. Novamente, se nunca tentou ordenar um array procure no google &ldquo;como ordena um array em javascript&rdquo; e vai achar a explicação dessa função. Não tem nada de especial aqui.</p>
<p>Vamos voltar pro console de Node. Como que fica pra usar isso? Aqui é que vai ficar um pouco diferente de um SQL normal porque o &ldquo;ORDER BY&rdquo; costuma ir no final do comando, mas na minha versão em Javascript, vai antes. E fica assim <code>select('name', orderBy('name', 'desc', from('users', bla bla bla)))</code>. E olha só, funcionou direitinho, retornou ordenado em ordem decrescente, Lois Lane na frente, Barbara Gordon no final. Pára e pensa, com a função <code>from</code> filtramos a tabela inteira pra pegar só quem é de Gotham ou Metropolis. Agora ordenamos o resultado, e finalmente selecionamos só as colunas que queremos. E por que essa ordem? Porque poderíamos escolher ordenar por idade mas no final só selecionar a coluna de nome. Por isso a ordenação vai antes de selecionar as colunas pro resultado final.</p>
<p>Só pra terminar essa parte, vamos ver rapidamente só mais duas funções. Como faz o equivalente a um &ldquo;insert&rdquo; de SQL? Essa é fácil. é só usar a função <code>push</code> que todo array tem como já mostrei antes. No caso, eu estabeleci que todo objeto vai ter um ID que é numérico e cresce incrementalmente, como muita primary key padrão que todo mundo cria, só pra ficar mais fácil. Vamos no console do Node e testar isso. Mesma coisa do primeiro exemplo <code>insert('users', { name: 'Levi Ackerman', age: 37, city: 'Paradis' })</code>. E devolve 38 que é o ID que ele calculou pra esse objeto.</p>
<p>Agora pra simular delete como no SQL é a mesma coisa que o filter da função <code>from</code> pra encontrar os objetos que queremos, daí só usamos a função <code>pop</code> que todo array de Javascript tem, e isso vai arrancar os objetos da lista. Nesse código-fonte eu implemento outras funções como o equivalente a &ldquo;update&rdquo; e tem uma versão inacabada de como seria um &lsquo;inner join&rsquo; e &lsquo;outer join&rsquo; se tiverem curiosidade de tentar terminar da forma correta. E é assim que vou fazendo um fake-sql pra simular os diversos aspectos que tem num banco de dados SQL comum.</p>
<p>Mas claro, mesmo sendo um exercício, já tem código suficiente aqui pra confundir a gente. Então esse é um bom momento pra fazer alguns testes unitários pelo menos né? Lembram como faz? Primeiro é só fazer um <code>npm install --save-dev jest</code> pra instalar o framework de testes Jest. Agora abrimos o arquivo <code>package.json</code> e colocamos esse comando grandão em scripts, test. Eu precisei usar essa flag experimental pra conseguir carregar os módulos ES certinho. Não sei se é o melhor jeito, mas foi como funcionou aqui.</p>
<p>Agora é só rodar <code>npm test</code> no terminal e ele vai executar os testes que eu já tinha feito no arquivo <code>stupid.test.js</code>. O comando do Jest vai vasculhar por arquivos terminados em &ldquo;.test.js&rdquo; e rodar os testes que tem lá dentro. E como é esse arquivo? Vamos abrir rapidinho só pra ver alguns exemplos. As primeiras linhas são pra carregar as funções do fake-sql e as tabelas de users e heroes.</p>
<p>Criamos um bloco de describe pra função &ldquo;select&rdquo; que é de longe quem tem mais casos de testes. Sabe todos aqueles testes que fizemos manualmente no console do Node? Pois é, é o que queremos automatizar em forma de testes unitários. Por exemplo, o primeiro teste é que deve achar o nome, então executa aquela mesma linha de select name from users where users.name igual Peter Parker. Pega o array de resultados, escolhe o primeiro objeto e espera que a propriedade “name” desse objeto seja Peter Parker, “to be” Peter Parker, se for, esse teste passa. E vai fazendo isso pra outros exemplos.</p>
<p>Vocês podem olhar esse arquivo com calma depois que tá no repositório. Link na descrição abaixo como falei, mas veja o último que é mais um teste integrado. Eu começo fazendo dois novos inserts, um pra tabela users com o objeto do Katar Hol, e outro insert na tabela heroes dizendo que na verdade ele é o Hawkman! Quem diria? E ligando com o que seria equivalente a uma chave estrangeira numa tabela de SQL, fazemos a propriedade user_id desse objeto ser igual ao valor que voltou no insert anterior. E é assim que seria um sisteminha CRUD da Liga da Justiça, pensa o estag do Bruce Wayne fazendo isso.</p>
<p>Só pra comparar depois, pedimos a contagem das duas tabelas e guardamos nessas duas variáveis de total. Agora fazemos um select com o innerJoin que eu não mostrei o código, mas se você já aprendeu SQL, inner join é uma forma de fazer a intersecção de duas tabelas de resultado. Ele vai ligar o resultado desse <code>from</code> feito na tabela users com a tabela heroes via a chave <code>user_id</code>. E esperamos receber uma tabela de resultado com as colunas name e alterego. Alter ego só tem na tabela heroes.</p>
<p>Então esperamos que na primeira linha do resultado, a propriedade name seja Katar Hol e o alterego seja Hawkman. Continuando, digamos que eu errei a idade do Katar, não era 35 e sim 37. Então eu posso usar essa outra função <code>updateFrom</code> que eu não mostrei o código. Quero que o age, a idade, mude pra 37, e isso nos objetos que voltar da mesma função <code>from</code> onde a cidade é Thanagar.</p>
<p>Eu faço de novo um select pela mesma condição e checo que a idade que voltou mudou pra 37 mesmo. E estamos quase no fim. Agora quero testar a função <code>deleteId</code> que também não mostrei o código. Vou apagar os objetos com os ids user_id e hero_id que tinha guardado lá em cima. E pra saber se apagou mesmo, peço um select count pra trazer a contagem das duas tabelas e tem que ter um objeto a menos do que tinha antes de quando eu gravei os totais depois do insert lá em cima. Vamos pro terminal, se rodar <code>npm test</code>, olha só, tudo passa corretamente. E agora eu tenho um mínimo de testes caso eu queira brincar de mexer mais nesse código.</p>
<p>Testes é outro assunto super longo, mas mesmo num exercício de fim de semana como esse, ter testes me ajudou muito. Se não tivesse eu teria levado o dobro do tempo pra fazer as coisas. Mas eu não segui nenhum tipo de TDD, eu escrevia um pedaço e testava depois, mudava o design, mudava o teste, e fui fazendo assim. Não pensa tanto em procedimento, só faz a porra do teste.</p>
<p>Até esse ponto implementei um monte de funções de javascript com nomes como select, insert, updateFrom, from, innerJoin, orderBy e assim por diante pra imitar os nomes dos comandos mais comuns que todo banco de dados SQL tem. E como exercício, eu poderia parar por aqui. Entenderam mais ou menos como é possível simular um fake SQL em cima de arrays de javascript? Mas claro, não é isso que você queria ver. Por mais parecido que seja, isso ainda não é SQL. Funções de Javascript tem um monte de parênteses, o orderBy tá na ordem errada, tá parecido mas tá longe de estar igual a SQL.</p>
<p>O que eu quero é poder ter um script como eu fiz nesse arquivo <code>test.sql</code> que também tá no repositório. Olha só, vários comandos SQL igualzinho o que você conseguiria executar no MySQL, SQL Server. Pra executar esses SQL eu fiz outro script chamado <code>stupid.mjs</code>. Vamos ver o finalzinho dele. Olha só, ele abre o arquivo <code>test.sql</code>, passa o script nessa função esquisita <code>parseScriptWithSplitQueries</code> e pra cada linha devolvida dá um eval. O que acontece se executar isso? Vamos voltar pro terminal e executar.</p>
<p>E voilá, ele executa todos os comandos SQL! Eu dei <code>console.log</code> do que tá rodando, e são minhas funções Javascript e não o SQL. Como que faz isso? Vamos voltar praquele script e o segredo começa naquela função esquisita <code>parseScriptWithSplitQueries</code>. Ela passa o conteúdo do arquivo <code>test.sql</code>, que são aqueles montes de comandos SQL, pra essa classe SplitQueries. Se olhamos lá em cima, ela é importada desse outro arquivo <code>parser/SplitQueries.mjs</code>. Vamos abrir ela.</p>
<p>Pronto, a primeira coisa que ele faz é importar essa biblioteca chamada Antlr4. E vamos pausar aqui pra eu explicar. Como expliquei no começo, no feriado de Tiradentes eu tava pensando em como ia explicar banco de dados pra vocês, aí tive a idéia de fazer um do zero. Se eu terminasse o episódio aqui, acho que já dava pra ter o primeiro gostinho de como um banco funciona por baixo. Esse código que mostrei até agora é super trivial de fazer, não foi mais que algumas poucas horas brincando. Mas a cereja do bolo seria mostrar como que comandos SQL de verdade funcionam e apesar das funções javascript que fiz terem nomes parecidos com SQL, ainda não é SQL. E aí fiquei coçando a cabeça, porque eu queria mostrar SQL, mas eu não queria gastar muito mais que um dia pra isso.</p>
<p>O episódio que lancei antes desse justamente explica o que é uma linguagem compilada e interpretada, e mencionei que tanto um compilador quanto um interpretador começam com um parser. E uma das formas de se fazer um parser é usar um software chamado Antlr4, que é feito em Java. Naquele episódio eu disse que não ia explicar ele. Pois bem, vou explicar rapidinho agora. O antl4 é um gerador de parsers. Lembram o que é um parser? É quando pegamos uma gramática, passamos o código fonte por um lexer pra conseguir uma coleção de tokens e passamos esses tokens pra um parser pra conseguir uma ParseTree.</p>
<p>Num interpretador ou compilador, essa ParseTree é transformada numa Abstract Syntax Tree ou AST, que daí passa por uma série de otimizadores pra reescrever o código da forma mais eficiente possível, e aí finalmente gerar o código de máquina tanto pra executar na hora, caso seja um Just in Time Compiler ou gerar um binário num arquivo, caso seja um Ahead of Time Compiler ou AOT. No meu caso, eu não preciso fazer tudo isso, só preciso ir até o ponto onde gera uma Parse Tree, pra a partir dela, fazer um transpiler, que vai converter SQL em Javascript. Entenderam onde eu quero chegar?</p>
<p>Escrever uma gramática do zero é extremamente tedioso e trabalhoso. Mas minha vantagem é que não estou inventando uma nova linguagem, pelo contrário, eu quero um SQL que seja o mais compatível possível com o SQL de verdade. E aqui vem outra vantagem do Antlr4. Ele tem uma comunidade muito esforçada que já se deu ao trabalho de escrever a gramática de praticamente todas as linguagens que você já ouviu falar e um monte que vocês nem sabiam que existiam. Vou deixar o link do repositório nas descrições abaixo, mas olha essa página do GitHub que coisa linda.</p>
<p>Lembram que eu falei da linguagem BCPL? Quer saber como é a gramática? Basta entrar aqui nessa pasta e abrir o arquivo <code>bcpl.g4</code> que é a extensão de gramática do antlr4. Olha o que eu expliquei no episódio anterior. Começa definindo o que ele entende por &ldquo;letras&rdquo;, são todas as maiúsculas e minúsculas. E essa distinção é importante porque tinha linguagens antigas que só entendiam comandos em maiúsculo. Continua definindo, o que é um dígito em octal, um dígito em hexadecimal, o que são dígitos numéricos.</p>
<p>E qual é o tamanho da gramática pra entender BCPL inteiro? Que é a linguagem que inspirou o C? Nada mais que 131 linhas, contando linhas em branco. E se a gente voltar pra primeira página, olha lá embaixo, uma pasta chamada &ldquo;python&rdquo;, dentro tem python2, e no python3 temos de novo arquivos .g4 que ele separou pro lexer e pra gramática do parser. E mesmo esse arquivo tem menos de 200 linhas.</p>
<p>Mas de volta à primeira página, o que nos interessa mesmo é essa pasta sql lá embaixo. Tem as gramáticas do mysql, postgres mas o que eu queria, e ainda bem que tinha, é do sqlite3, que é o banco de dados sql mais simples e provavelmente o mais usado do mundo. Isso porque enquanto um postgres da vida é usado em servidores, todo dispositivo IoT, internet das coisas, que é qualquer dispositivo com um mínimo de processamento com acesso a internet, precisa de um banco de dados local. Pense um roteador de wifi da sua casa. 99% de certeza que dentro usa um sqlite3. Pensa vários apps no seu celular Android, vários sqlite3. Pensa uma geladeira inteligente, certamente tem sqlite3 dentro.</p>
<p>Aliás, o que estou construindo neste episódio estou chamando de stupid database, porque é o banco mais besta e ao mesmo tempo mais simples com objetivo educacional, mas se quiser ver o menor banco de dados SQL realmente completo e funcional, tem que estudar o código C do Sqlite3. Existe um subprojeto chamado Sqlite Amalgamation que concatena os mais de 100 arquivos de código fonte num único arquivo &ldquo;sqlite3.c&rdquo; e por isso se chama amalgamation.</p>
<p>O sqlite3 inteiro consiste de 75% de código C e o resto é arquivos .h de cabeçalhos. Esse arquivão dá um total de quase 240 mil linhas ou 145 mil se tirar linhas em branco, totalizando 8.4 megabytes de código fonte. Se um dia realmente quiser saber em detalhes como um banco de verdade funciona, precisa entender como o sqlite3 funciona, O código fonte tá disponível pra ler e ir testando pedaço a pedaço. Bom trabalho de iniciação científica ou TCC pra quem tá na faculdade de ciências da computação.</p>
<p>Mas, explicar 145 mil linhas de código em um único episódio seria impossível então espero que se contentem com essa minha versão de stupid database. Enfim, falei tudo isso pra dizer que foi nesse respositório de gramáticas do Antlr4 que peguei emprestado os arquivos &ldquo;SqliteLexer.g4&rdquo; e &ldquo;SqliteParser.g4&rdquo;. E como eu não ia ter comandos como &ldquo;create table&rdquo;, &ldquo;alter table&rdquo; e coisas assim, saí apagando da gramática tudo que não ia suportar até ficar com uma versão mais reduzida. Ainda sobrou muita coisa que eu não implemento, mas apaguei o suficiente pra facilitar meu trabalho.</p>
<p>Daí eu precisei instalar o antl4, e pra isso é só ir no site deles e ver como instala, não vou detalhar aqui. Assumindo que já tá instalado, no terminal é só rodar o comando <code>antl4 -Djavascript</code> pros dois arquivos SqliteLexer e SqliteParser.g4. Esse flag &ldquo;-D&rdquo; diz em que linguagem ele vai gerar o lexer e parser. E dá pra gerar em praticamente todas as linguagens que quiser, seja python, java e no nosso caso, javascript. Isso gera esses arquivos &ldquo;.interp&rdquo; e olha só SqliteLexer.js e SqliteParser.js. Esses arquivos a gente não precisa mexer. Nem vou abrir porque pode assustar os mais fracos de coração.</p>
<p>O mais importante é que ele gerou esse SqliteParserListener.js. Esse podemos abrir e vamos descobrir um monte de funções vazias. Que diabos que são essas funções? E eles estão em pares, veja só aqui no começo temos uma função chamada &ldquo;enterParse&rdquo; e depois &ldquo;exitParse&rdquo;. Se abrirmos aquele arquivo que pegamos emprestado, o SqliteParser.g4 podemos ver aqui no começo que &ldquo;parse&rdquo; tem esse &ldquo;sql statement list&rdquo; e EOF que é End of File ou final do arquivo. Daí vemos que sql statement list tem vários sql statements e sql statement pode ser vários tipos como delete statement, insert statement, select ou update. Entenderam? É assim que ele começa definindo as regras do que esse parser consegue entender, e são justamente as operações que queremos. Então já sabemos que o ponto de entrada no parser vai ser por essa regra “parse” que engloba o arquivo inteiro de comandos SQL que passarmos pra ele.</p>
<p>A gente não mexe nesse arquivo de Listener também, porque se amanhã eu mexer no arquivo .g4 e mandar regerar com o comando <code>antl4</code>, daí vai sobrescrever nesses arquivos todos. Por isso criei esse outro chamado CustomListener.js. Ele é basicamente uma classe que extende da classe SqliteParserListener e lá embaixo eu copio todas aquelas funções vazias pra sobrescrever elas. Agora sim posso começar a brincar.</p>
<p>Eu não vou mostrar tudo em detalhes, depois vocês podem olhar o código fonte no meu repositório. Mas só pra entender vamos voltar pro arquivo principal desse diretório, o SplitQueries.mjs que é pra onde eu tava mandando os comandos SQL do arquivo <code>test.sql</code> lembram? Agora que expliquei o que são os principais arquivos, veja que nesse script eu importo todos eles, o Lexer, o Parser e o meu CustomListener. Ele começa recebendo um input, que pode ser uma linha de SQL ou várias linhas de SQL como o conteúdo do arquivo <code>test.sql</code>.</p>
<p>Uso a função InputStream da biblioteca antlr4 pra quebrar num stream de caracteres. Lembra o que são streams? Lembro que já expliquei isso em algum episódio, mas pra quem não sabe, iniciantes estão acostumados a carregar o conteúdo de um arquivo tudo de uma vez em memória, mas e se o arquivo não couber na memória? O jeito mais eficiente é ir lendo pedaços do arquivo de cada vez e ir processando aos poucos, pra isso abrimos um stream. Enfim, passamos esse stream pro Lexer, que criar uma lista de tokens a partir desse stream. Ou seja, ele recebe a linguiçona de strings e vai quebrando numa lista de tokens.</p>
<p>Agora passamos esses tokens pro Parser e em seguida pedimos pra construir a Parse Tree, a árvore, como expliquei no episódio anterior. Pra você que é de front-end, é como quando o navegador carrega uma página HTML, que é um stream de caracteres, e gera o DOM, a árvore que representa aquele HTML. Agora criamos uma instância do nosso CustomListener passando a referência pra esse array “result” que é onde vamos escrever o javascript final. Pra gerar essa parse tree, começamos da primeira regra que tem no nosso arquivo .g4 do parser, que é a regra chamada &ldquo;parse&rdquo;, por isso chamamos <code>parser.parse</code>.</p>
<p>Finalmente, fazemos o antl4 passear pela parse tree, nó a nó, por isso essa função chama <code>ParseTreeWalker</code> e damos como parâmetro pra ele o Listener, que tem as regras de conversão, e a árvore propriamente dita. E é aqui que entra meu CustomListener. Vamos entrar lá de novo. Aqui eu vou dar uma trapaceada porque quero gerar o javascript baseado no meu fake sql, lembra? As funções em javascript que eu criei chamada &ldquo;select&rdquo;, &ldquo;from&rdquo;, &ldquo;orderBy&rdquo;. Pra isso eu vou pegar os pedaços que a ParseTree vai me dando e vou preenchendo nesse objeto construído pela função <code>buildSqlStruct</code> que eu inventei. Ela tem os pedaços que formam um comando SQL. Olha só como tem um array pra colunas, outro array pra condições, e flags pra saber se tem &ldquo;order by&rdquo; e coisas assim.</p>
<p>Logo embaixo tem a função <code>enterParse</code>, mas nesse momento eu não preciso fazer nada. Deixa ele ir navegando pela árvore até achar um comando como &ldquo;insert&rdquo; ou &ldquo;select&rdquo;. Digamos que eu tenha um comando como <code>insert into users (name, age, city) value ('Katar Hol', 37, 'Thanagar')</code> como mostrei lá no começo. Um comando sql normal de insert. Olha o nosso arquivo de parser .g4, a regra do insert se chama <code>insert_stmt</code> de statement. Então provavelmente quando o parser achar o insert, vai chamar a função <code>enterInsert_stmt</code> passando um contexto nessa variável <code>ctx</code> que é a sub-árvore do comando de insert.</p>
<p>Essa parte foi a mais trabalhosa que é decifrar o que vem nesse contexto. E já aviso que a documentação que achei online não foi muito útil não. Na prática, o que eu aprendi a fazer foi executar o parser com um comando SQL de teste e vasculhar o que vem no contexto usando o bom e velho <code>console.log</code> e eu entendi que o que importa é saber quem são os nós filhos, ou seja <code>ctx.children</code>. Então, vamos lá na função de entrada do insert e deixa eu colocar esse <code>console.log</code> aqui. Se eu printar o ctx inteiro vai ser super longo, então só imprimo o nome da classe a o <code>getText()</code> que é representação em texto desse nó. Vamos abrir o REPL e mandar o comando insert de teste.</p>
<p>Olha só o que ele imprime: os nós, que são instâncias de TerminalNode costumam ser tokens como o comando &ldquo;insert&rdquo; e as outras partes do comando como &ldquo;into&rdquo;. Daí quando tem um nome de tabela, com outra super classe, no caso Table_nameContext, daí vem outro TerminalNode que é a abertura de parênteses. Daí temos um Column_nameContext que é o campo &ldquo;name&rdquo; e assim por diante. Foi isso que o Lexer fez, ele quebrou o comando e o Parser organizou nesse sub-árvore de nós que podemos navegar. Vamos voltar pro código no Listener.</p>
<p>Já sabemos que é só navegar por cada nó dos filhos do contexto, então pra cada <code>child</code> de <code>ctx.children</code> eu faço um switch case. Se a super classe for Table_nameContext eu preencho o nome da tabela naquele objeto sqlStruct. Próximo nó, se for um TerminalNode, não faço nada. Próximo nó, se for um Column_nameContext vou acumulando numa lista de colunas no objeto sqlStruct e mesma coisa se forem ExprContext, são os valores, então vou acumulando na lista <code>values</code> no sqlStruct. Quando acabar a lista de nós filhos, sai dessa função. Daí o Walker vai chamar a função de saída que, por convenção, vai se chamar <code>exitInsert_stmt</code>, por acaso é a função logo embaixo nesse Listener.</p>
<p>Pronto, quando entra nessa função sabemos que navegamos por todo o comando insert. Agora é só pegar o que cadastramos no objeto sqlStruct e literalmente montar uma string com a versão do comando em javascript. Primeiro eu monto uma string combinando o nome de cada coluna como chave, dois pontos, o valor, que é a sintaxe de javascript pra montar um objeto. Depois eu começo a montar a função &ldquo;insert&rdquo;, concateno o nome da tabela e depois junto os valores com vírgula entre chaves. Isso vai gerar esse comando em javascript que eu mostro no REPL antes de executar.</p>
<p>Expliquei meio rápido mas deu pra entender a idéia? Todo comando que eu passar praquela função SplitQueries vai passar pelo Lexer e Parser, e vai gerar uma árvore, a Parse Tree. Daí eu ando nessa árvore, nó a nó, usando as funções do Listener. Toda vez que quero implementar um comando SQL, pego a função de entrada, como a <code>enterInsert_stmt</code>, pego o contexto que é a sub-árvore e vou dividindo os pedaços que preciso e acumulando na minha estrutura sqlStruct. E na função de saída monto a string com a versão em Javascript, que é o que a função SplitQueries vai retornar, no caso pro REPL.</p>
<p>Vamos abrir o script do tal REPL, a linha de comando que vim mostrando até agora. Eu uso uma biblioteca chamada &ldquo;prompt-sync&rdquo; que faz exatamente o que o nome diz: abre um prompt que fica esperando eu escrever algum comando. Tá vendo ele escrevendo &ldquo;stupid query&rdquo;? Daí eu digito o comando insert e ele captura nessa variável <code>query</code>, que vai passar pra função <code>parseScriptWithSplitQueries</code> que tá definido lá em cima. Igual eu tava fazendo no script <code>stupid.mjs</code> que lia comandos do arquivo <code>test.sql</code>.</p>
<p>Essa função passa o script pro SplitQueries, que é o parser, que vai navegar e transformar a árvore e cuspir um string com o comando javascript do nosso fake-sql. Pra cada linha que ele cuspir dou um <code>eval</code> e executo, imprimo o resultado na tela com o <code>console.table</code> e no final abro o prompt de novo, e fica nesse loop, que é o que um repl tem que fazer mesmo. E pronto, é assim que se constrói um interpretador simples de SQL em javascript.</p>
<p>O que eu fiz no Listener pra converter o insert em javascript agora preciso fazer a mesma coisa pros comandos delete, update, e select, que é o mais complicado de todos. Vamos só bater o olho no select. Começa pela função <code>enterSelect_stmt</code>. Diferente do insert que forcei olhar pros nós filhos direto, aqui só crio o objeto sqlStruct que vou precisar preencher e deixo o Walker passear pelos próximos nós pra ir coletando o que eu preciso. Por isso falei que é mais complicadinho, porque precisa implementar várias regras diferentes do Listener que não vou mostrar em detalhes aqui.</p>
<p>Em particular um select é complicado porque nas condições que começa depois do Where eu posso ter uma série de expressões, e expressões dentro de expressões. Começo a capturar lá em cima na função <code>enterExpr</code>ession. E aqui eu chamo uma função que vai ser recursiva, justamente porque pode ter expressões dentro de expressões. Falei que ia ser complicado. Não tenta entender linha a linha num primeiro momento, começa tentando entender só o conceito.</p>
<p>Não vou nem tentar ir passo a passo, de novo, fica de lição de casa, mas só pra mostrar rapidinho, essa função <code>recursiveChildren</code> vai tentando interpretar uma expressão de cada vez. Por exemplo, em SQL podemos fazer algo como &ldquo;age between 60 and 2000&rdquo; e em javascript isso tem que virar &ldquo;age &gt;= 60 &amp;&amp; age &lt;= 2000&rdquo; e esse tratamento começa nesse if que checa se esbarrou num &ldquo;between&rdquo; e cria uma estrutura pra isso.</p>
<p>Ou pior, em SQL podemos ter algo como &ldquo;city in (&lsquo;Gotham&rsquo;, &lsquo;Metropolis&rsquo;)&rdquo; e em javascript isso tem que virar &ldquo;[&lsquo;Gotham&rsquo;, &lsquo;Metropolis&rsquo;].includes(city)&rdquo; e de novo olha aqui no código o if que ativa se esbarrar no token &ldquo;IN&rdquo; pra criar uma estrutura de <code>range</code>. Continuando, em SQL o &ldquo;NOT&rdquo; vem antes da expressão, então quando falo &ldquo;NOT age &gt; 40&rdquo; tem que virar &ldquo;age &lt;= 40&rdquo; que é o inverso. Lá em cima eu fiz uma função chamada <code>invertOp</code> pra inverter todos os operadores. Se for igual vira diferente, se for maior, vira menor ou igual e assim por diante.</p>
<p>Enfim, pra conseguir pegar a maioria dos casos mais comuns de select, eu tive um bom trabalhinho. Por isso que agora consigo ir no meu repl e digitar vários tipos de SELECT e ele vai conseguir montar. Quando terminar de transformar todos os nós que preciso da sub-árvore do SELECT, vai cair na função de saída, o <code>exitSelect_stmt</code>. Só deem uma olhada. No final vai ter preenchido estruturas como sqlStruct.orderby, sqlStruct.table, sqlStruct.conditions e assim por diante. Daí é só montar a string com minha sintaxe de javascript do fake sql e retornar.</p>
<p>Eu sei que parece complicado mas realmente não é. Se você já teve que usar jQuery pra manipular nós de HTML do DOM num navegadow, é basicamente o mesmo conceito. Quando a gente tem um HTML como &ldquo;<ul><li>1</li><li>2</li></ul>&rdquo; que é uma lista, o contexto começa com o nó &ldquo;ul&rdquo;, ele vai ter 2 children que são nós &ldquo;li&rdquo;, o primeiro &ldquo;li&rdquo; vai ter valor 1, o segundo &ldquo;li&rdquo; vai ter valor 2. E se tivermos isso aberto no DOM que podemos manipular com javascript, podemos transformar isso em dois parágrafos, por exemplo. E é algo semelhante que fui fazendo nesse Listener, convertendo nós de SQL em string Javascript, pra todos os comandos que eu quis suportar no banco stupid.</p>
<p>Esse projetinho ainda tá incompleto, como falei antes, coisas como inner join, outer join, sub-selects e coisas assim eu não implementei. Fica de exercício pra vocês se quiserem. Mas vamos recapitular: até aqui fiz um banco de dados em memória bem stupid, que são só arrays de objetos. Fiz funções javascript com nomes que se assemelham aos comandos SQL e chamei isso de Fake SQL. E de bônus usei o Antlr4 pra pegar a gramática do Sqlite3, gerar lexer e parser em javascript e preencher o listener com código pra transpilar SQL de volta em javascript que chama minhas funções de Fake SQL.</p>
<p>Mas até aqui só expliquei como que de comandos SQL dá pra executar alguma coisa por baixo. Mas pra ser um banco de dados de verdade, ainda faltam funcionalidades que vou explicar só em conceito. O mais importante é vocês entenderem índices. Todo iniciante esquece que existem índices porque não entendem a importância. Alguns sabem que é pra aumentar a performance, mas como em máquina de desenvolvimento, que você dá insert em poucas linhas na tabela, não faz quase nenhuma diferença, todo mundo esquece de usar. Então deixa eu explicar o problema.</p>
<p>Pra ilustrar criei esse script de benchmark chamado <code>bench_index.mjs</code>. Agora, esquece o parser de SQL, vamos usar só o fake sql. Notem que lá em cima eu carrego uma nova biblioteca chamada <code>fake-sql-index.mjs</code>. Pula por um segundo. A primeira função aqui é um timer genérico, passo um comando e ele grava a hora logo antes, a hora logo depois, subtrai os dois pra saber quantos milissegundos levou pra processar. Depois faço uma rotina de criação de índices. Vou explicar o que isso significa já já, mas na prática estou criando um índice pra cada coluna da nossa tabelinha.</p>
<p>Em seguida crio uma nova função de insert decorada em cima do insert anterior. Ignorem por enquanto, já explico. Agora vem a parte da carga, vou duplicar aquela tabela de 37 usuários 18 vezes. Só que é acumulativo, então da primeira vez, 37 vira 74. Daí duplica de novo e vira 148, daí duplica de novo e vira 296 e faz isso 18 vezes, aumentando a quantidade de objetos exponencialmente até dar quase 10 milhões de objetos em memória.</p>
<p>Agora olhem o primeiro teste do benchmark, é usando minha função <code>from</code> lembra? Que vai fazer um eval chamando a função <code>.filter</code> desse nosso array de quase 10 milhões de objetos e checando um a um se a propriedade <code>city</code> é igual a &ldquo;Wakanda&rdquo;. Ele vai checar isso quase 10 milhões de vezes, objeto a objeto. Porque não tem outra forma nessa versão simples e no nosso teste vai levar algo na faixa de 150 milissegundos, o que é bem rápido porque já tá tudo na memória e fuçar RAM é muito rápido. Se fosse pesquisar num HD seria 100 ou 200 vezes mais devagar que isso, levando vários segundos inteiros pra fazer a mesma pesquisa.</p>
<p>O segundo teste é diferente, ele usa outras funções que ainda não mostrei e que está naquele arquivo &ldquo;fake-sql-index.mjs&rdquo;. Logo no começo chamei a função <code>createIndex</code> dessa biblioteca. Vamos ver o que ela faz. Olha só, já tinha uma lista chamada <code>databases</code> pra representar o banco de dados e agora criei outra estrutura global chamada <code>indexes</code> pra representar índices. Daí crio um índice pra cada chave da tabela. Então a chave pra idade da tabela users seria <code>indexes['users']['age']</code> e cada uma delas vai ter uma instância desse tal de <code>AVLTree</code>.</p>
<p>AVLTree é parecida com uma Red-Black Tree. Expliquei isso no vídeo de Árvores, lembra? Deixa recapitular o conceito rapidinho aqui. Eu tenho aquela lista de usuários com nome, idade e cidade, certo? Se eu quiser achar todo mundo da cidade de Wakanda como faz? Do jeito que tá agora preciso olhar o 1o elemento e vai ser o Peter Parker em Nova Iorque, não é. Pula pro próximo, vai ser Clark Kent, em Metropolis. Só quando chegar na linha 12 que vamos achar o T&rsquo;Challa em Wakanda. Mas eu não sei se é só ele, vamos checando um a um de novo até chegar na linha 25 e achamos a Okoye. Mas ainda não sabemos se acabou, então tem que ir até a última linha 37 que é a Jubilee em Los Angeles e agora sabemos que só tem 2 usuários de Wakanda, mas pra saber isso tivemos que checar 37 vezes. Tem como melhorar isso? Tem.</p>
<p>O jeito mais ingênuo é jogar numa árvore de procura binária. O processo é simples. Primeiro de tudo ordemos a lista de cidades. Vou simular aqui. Depois, não começamos do começo, da linha 0. Começamos do meio. Metade de 36 é 18. E na linha 18 temos New York. Agora, Wakanda é maior ou menor que New York? Por ordem alfabética é maior, então ignoramos tudo abaixo da linha 18 e vamos pra metade entre 18 e 36, que é a linha 27. E 27 continua sendo outro New York. Wakanda ainda é maior, então pegamos o meio entre 27 e 36 que é 31. De novo Wakanta é maior. O meio entre 31 e 36 é 33. E nessa linha temos San Francisco. Ainda Wakanda é maior. Meio de 33 e 36 é 34. Agora é Themyscira. Wakanda ainda é maior. E finalmente, entre 34 e 36 temos 35, onde encontramos o primeiro Wakanda.</p>
<p>Esse foi por acaso, o pior caso, onde procurei Wakanda que fica no final da lista. Mas olhem só, no pior caso anterior tinha que vascular 37 vezes. Mas desse jeito binário de ir jogando metade fora a cada checagem, tivemos que fazer só 6 comparações. Ou seja, 6 vezes menos comparações no pior caso. Se em vez de eu procurar na tabela principal, procurar no índice ordenado de cidade, vou descobrir as duas últimas linhas depois de só 7 comparações e isso me resulta nos ids 12 e 25. Agora pego esses ids na tabela principal e acho o T&rsquo;Challa e a Okoye, totalizando 7 comparações e 2 operações pra pegar os objetos na tabela de usuários. 9 operações, que é quase 4 vezes menos operações.</p>
<p>Vamos ver o benchmark das 10 milhões de linhas. Vasculhando, que em mundo de banco de dados é o que chamamos de fazer um &ldquo;table scan&rdquo; porque estamos escaneando linha a linha da tabela inteira, 10 milhões de vezes, levou na casa de 150 milissegundos. Mas agora buscando no índice, que é o que faz essa função getFromIndex, vai custar na faixa de 10 milissegundos. 15 vezes mais rápido. Mas aí vai levar uns 35 milissegundos pra pegar os ids e buscar os objetos na tabela, que é o que faz essa função <code>getFromIds</code>, um total de uns 40 milissegundos, que continua sendo pelo menos 3 vezes mais rápido do que um table scan. Lembrando que esses tempos só são em millisegundos porque estamos em RAM, se fosse em disco seria muito mais lento.</p>
<p>No caso, por causa do javascript, essa função <code>getFromIds</code> tá prejudicando nosso benchmark. Mas se fosse em C estaríamos falando de uma diferença ainda maior, na casa de tudo 10 vezes mais rápido mais ou menos. Antes que alguém comece a chiar nos comentários, Javascript não é sempre X vezes mais lento que C, mas nesse trecho específico sim. Enfim, uma AVLTree e uma Red-Black Tree, diferente de uma árvore de procura binária simples, são árvores balanceadas. Eu mostro no outro video a diferença de forma animada, mas em resumo, significa que o processo de ordenar e colocar na árvore exige uma operação de rotação pra fazer o balanceamento, então a inserção na árvore é um pouquinho mais lenta do que uma árvore binária simples, mas na hora de procurar vai exigir menos comparações então a pesquisa é mais rápida.</p>
<p>Pra todas as pesquisas serem mais rápidas, o certo é ter índices pra cada expressão que queira procurar. Se quisesse fazer uma pesquisa por idade e cidade ao mesmo tempo, eu deveria ter um índice que contenha esses dois valores na mesma árvore. Se quiser procurar também separado por idade e em outras pesquisas procurar por cidade, daí preciso ter outro indice separado pra cidade e outro pra idade. Mas aí você pensa, porque diabos tenho que ficar criando índices manualmente, se tudo fica mais rápido com índices, o banco já não cria índices pra todas as colunas automaticamente como eu fiz no meu banco estúpido aqui nesse trecho?</p>
<p>Agora é hora de falar daquela função nova de insert decorada. Eu usei um decorator só pra não ter que mexer na função de insert original e só plugar a funcionalidade de índice. Vamos ver o que faz esse <code>decorateInsert</code>. Abrindo a biblioteca &ldquo;fake-sql-index&rdquo;, vemos que ela recebe a função <code>insert</code> antiga e devolve outra função. Dentro ela chama o insert original, mas na sequência insere também os valores nos índices de cada coluna. Ou seja, se estivermos inserindo um novo objeto com os campos name, age, city e id, são 4 novos inserts. O que antes era só 1 operação de insert no array de users, pra atualizar todos os índices, virou um total de 5 inserts. A operação de insert ficou pelo menos 5 vezes mais lenta.</p>
<p>Imagina uma tabela com 50 colunas. Se todas tiverem índices, cada insert vai exigir 51 operações de insert. Um pra linha na tabela principal e 50 inserts pros índices de cada coluna. Então o insert ficaria 50 vezes mais lento. E não é só isso. Concorda que toda vez que fizer update também precisa atualizar todos os índices? E se fizer delete também tem que deletar de todos os índices? Portanto insert, update e delete todos ficam lentos. E é por isso que não se faz índices pra todas as colunas.</p>
<p>Índices é um trade-off de deixar operações de pesquisa muito mais rápidas ao custo de deixar operações de escrita muito mais lentos. E nós programadores é que temos que avaliar, testar e achar formas de criar o mínimo de índices possível pra ter a maioria das pesquisas rápidas sem prejudicar demais as operações de escrita. Da mesma forma, não ter índice nenhum significa que as pesquisas podem ser 10 vezes mais lentas, porque vai cair num table-scan. Na prática, todo select complexo vai ser uma combinação de pesquisa em índices e table-scan pras coisas que não tem índice. E é por isso que existe o comando EXPLAIN na maioria dos bancos de dados pra explicar qual estratégia que o banco vai usar pra executar sua pesquisa.</p>
<p>Se quiser ficar bom em fazer queries que tiram o máximo dos seus índices, estudem como funciona o comando EXPLAIN. Internamente um banco de dados como Postgres pode pegar seu SELECT com condições WHERE complicadas e dividir o trabalho, por exemplo, pra rodar em paralelo em múltiplos núcleos do CPU. Ele vai tentar sempre otimizar a query pra rodar no menor tempo possível, usando a menor quantidade recursos que puder. É bem mais complicado do que eu dar um &ldquo;filter&rdquo; num array, obviamente. Mas já deve dar pra dar uma idéia.</p>
<p>De curiosidade, além dos comandos básicos de SQL como select, insert, ainda podemos programar stored procedures, que são programinhas em SQL, com condicionais como IF, variáveis e tudo mais. No caso de Postgres, a linguagem mais comum é o PL/pgSQL, num Oracle é o PL/SQL, num SQL Server é o T-SQL. Um postgres vai passar o código por um parser mais complicado do que eu mostrei e vai usar LLVM pra fazer o just in time compile desses programinhas, incluindo a fase de transformation passes pra otimizar o código antes de tentar executar. Então existe um JIT até no seu banco de dados hoje em dia. Todo banco de dados tem um interpretador embutido.</p>
<p>Seguindo no nosso banco estúpido ainda temos um probleminha. Notem que estamos usando o console do Repl do banco stupid pra inserir, apagar linhas, mas quando saímos e entramos de novo, os dados que mudamos não estão mais lá porque sempre carregamos do zero, do mesmo arquivo &lsquo;users-database.mjs&rsquo;, mas nunca salvamos em cima desse arquivo. Portanto os dados são fixos. E se a gente quisesse gravar as mudanças em disco pra persistir? Ah, isso é fácil, alguns poderiam dizer. Basta fazer o que todo tutorial de javascript já explicou: transformar a lista de objetos num arquivo JSON e gravar. Certo?</p>
<p>Totalmente errado. E nem meu banco é stupid o suficiente pra fazer isso. Na realidade vou quebrar o problema em dois. Um banco de dados de verdade tem no mínimo dois mecanismos, pra tanto persistir dados, quanto garantir a integridade desses dados, em particular num banco SQL. Se você estudou o básico de SQL já deve ter ouvido falar que bancos relacionais tem a característica de ser ACID, certo? Atomicidade, Consistência, Isolamento e Durabilidade. Se nunca ouviram falar, é obrigatório estudar isso. Hoje vou falar só de um pequeno aspecto de consistência e durabilidade.</p>
<p>Em particular, tudo que um banco responde &ldquo;gravei&rdquo; depois que você mandou inserir, tem que estar garantidamente gravado de verdade quando pesquisar. Todo conjunto de operações que eu mandar numa transação, ou todas executam ou nenhuma executa. Não pode ter operações feitas pela metade. Tudo isso porque uma característica importante é integridade. Tudo que o banco me confirmar eu preciso acreditar que fez realmente tudo certo, sem exceções. Essa é a característica de todo banco de dados relacional. O mesmo não se aplica a bancos NoSQL como Redis ou Dynamo, eles fazem trade-offs diferentes, mas outro dia falo disso.</p>
<p>Um dos mecanismos pra ter algumas dessas garantias é o transaction log ou journal. Um sistema de arquivos modernos como NTFS, ext4 ou APFS também tem esse mecanismo, como já expliquei no video de sistemas de arquivos. Como o nome diz é basicamente um arquivo de log, cuja característica principal é ser um arquivo append-only, ou seja, que nunca muda o que já foi escrito e só dá pra ir apendando, só adicionando na frente. O conceito é que antes de executar uma operação você primeiro grava que operação vai fazer nesse log e depois executa. Caso dê alguma catástrofe, tipo acabar a luz e o computador desligar, da próxima vez que reiniciar ele consegue dar replay nesse log e refazer as operações pra trazer os dados de volta num estado consistente.</p>
<p>Quero simular isso rapidinho pra vocês verem. Pra isso criei um outro módulo chamado &ldquo;fake-sql-transact&rdquo;. Abrindo o código, as duas primeiras funções são pra abrir o arquivo de log em modo append  - pra isso que serve esse “a” aqui -, e a segunda é pra apendar alguma coisa nesse arquivo. Em seguida, tenho decorators pras três operações principais que modificam os dados, um pra insert, outro pra delete e outro pra update. Repetindo, um decorator recebe como argumento o nome da função original e devolve outra função como retorno. Dentro dessa nova função vou salvar o comando no log de transações e só depois executo de fato o insert, delete ou update.</p>
<p>Pra testar dupliquei o script de repl que estava usando até agora e fiz um <code>replTransact.mjs</code> pra ter essa funcionalidade. Olha o que faço. Primeiro importo as funções insert, deleteFrom e updateFrom do módulo de fake-sql com outros nomes. Daí o resto importa igual. Agora chega nesse trecho onde começo abrindo o arquivo de log de transações que chamei de <code>transact.log</code>. A primeira coisa que faço é checar se tem algum coisa nesse arquivo e, se tiver, essa função de replay vai repetir todas as operações lá dentro. Como toda vez eu carrego sempre os mesmos dados do arquivo de <code>users-database</code>, eu sempre perdia tudo que fazia nela. Agora isso vai recuperar o que fiz baseado no log.</p>
<p>Em seguida abro o arquivo em modo append e recrio as funções de insert, deleteFrom e updateFrom com versões decoradas que recebem o objeto do arquivo de log aberto. A partir de agora quando chamar um insert, por exemplo, ele vai salvar a operação no log e depois fazer as alterações no array de objetos. O resto do script é igualzinho, então vamos sair, ir pro terminal, e carregar esse novo repl. A primeira coisa que vou fazer é dar insert em mais dois usuários. Vamos inserir os desconhecidos de Paradis. Insert do Levi Ackerman, 37 anos, depois deixa eu inserir uma parente distante dele, Mikasa Ackerman, 20 anos.</p>
<p>Pronto, agora posso fazer <code>select name from users where users.city = 'Paradis'</code> e estão lá direitinho. Vamos dar Ctrl+C pra sair e abrir o Repl de novo. E olha só, ele executa a função de replay e reinsere os dois. Se eu fizer o mesmo select, eles estão lá direitinho. E com isso eu implementei um tipo estúpido de persistência desses dados. Imagina que isso fosse um servidor de banco normal, como um Posgres, e bem na hora que tá salvando essas linhas em disco, dá uma pane, acaba a energia ou o sistema crasheia. O banco ia ficar num estado inconsistente, mas como antes de tentar gravar na tabela ele salvou as operações num transaction log, no próximo restart, vai conseguir fazer um replay, como o que simulei nessa versão simples.</p>
<p>Todo banco de dados e todo sistema de arquivos moderno tem um transaction log ou o outro nome que se usa pra isso, que é um journal, que é inglês pra diário. Óbvio que diferente da minha versão estúpida, ele não vai dar replay de todo o arquivo de transactions toda vez que restarta, isso seria estúpido. Ele só dá replay nas últimas transações que não concluíram até o fim. E isso leva à última característica de um banco de dados que quero discutir hoje, que é: como se salva esses dados numa tabela de verdade num HD?</p>
<p>Bom, já mostrei que meu banco estúpido sempre carrega os dados de um arquivo como o &ldquo;users-database&rdquo;. E nesse caso estou persistindo os dados em formato texto, em javascript. Acho que todo mundo aqui já salvou dados assim de outra forma, como um arquivo JSON. Bastaria chamar a função <code>JSON.stringify</code> e salvar a string num arquivo, certo? Por que a gente não faz só isso? Toda vez que insere ou modifica uma linha naquele array de usuários, basta chamar o <code>JSON.stringify</code> e salvar tudo no arquivo. Fácil, não?</p>
<p>E isso estaria errado. Vamos entender o raciocínio pra vocês nunca cometerem esse erro. O que faz uma função como <code>JSON.stringify</code>? Ele executa uma operação que chamamos de serialização ou marshalling. Daí quando lemos de um JSON e recriamos o array de objetos em memória, chamamos de desserialização ou unmarshalling. Esse é o tipo de operação que tem literalmente em toda biblioteca de APIs que você já tenha usado, como APIs pra integrar com meios de pagamento, ou APIs pra fazer bots de instagram ou twitter. Toda API te devolve um XML ou um JSON e você converte pra objetos da sua linguagem pra poder manipular os dados. Daí prepara um payload qualquer e serializa de volta em XML ou JSON pra mandar pra API.</p>
<p>Essa operação necessariamente duplica os dados em memória, pra começar. Você já tem o array de objetos em memória, e gera uma versão em JSON, em texto, que também tá em memória. Ambos vão ocupar o dobro ou mais de espaço. Os dados no array estão em binário e toda vez que transforma binário numa representação legível em texto, isso ocupa mais memória. Vamos ver quanto. Eu fiz um scriptzinho besta que usa a biblioteca &ldquo;object-sizeof&rdquo; que instalei via npm. E lá embaixo faço uma versão serializada do array de users usando o <code>JSON.stringify</code>.</p>
<p>Olha só. A string com a conversão em JSON do array tá ocupando 4.3 kilobytes na memória. Já a versão original binária em memória do array de objetos de users ocupa uns 3 kilobytes. Portanto a versão JSON é mais de 40% maior. Imagina se fosse um array gigante, com 1 gigabyte. Significa que o JSON ocuparia algo na ordem de 1.4 gigabytes. 400 Mega a mais por causa da conversão em texto e do formato JSON. Representações em texto de estruturas de dados fazem o trade-off de ocupar mais espaço pra gerar um arquivo que é mais fácil de um ser humano como nós, programadores, conseguirmos manipular num editor de textos. Por isso é um bom formato pra arquivos curtos, que não vão crescer o tempo todo. Mas em termos puramente de performance, trafegar JSON é um desperdício de espaço, banda e processamento.</p>
<p>Exatamente por isso que empresas como o Google preferem usar outro protocolo pra fazer comunicação entre serviços, como o ProtoBuf, que é um protocolo binário de formato de dados. Nenhum game que você joga online trafega JSON pra atualizar a posição do seu personagem no jogo, sempre é um protocolo binário. Mas pra APIs de coisas como mandar mensagens automatizadas no Whatsapp ou Twitter, eles oferecem um protocolo baseado em JSON, porque a maioria das pessoas nunca ia conseguir montar um pacote binário. E também porque costuma ser poucos dados trafegados. Num jogo precisa estar trafegando dezenas ou centenas de pacotes de dados por segundo, por isso todo byte conta. Numa API de mensagens ou pagamentos, você manda poucos pacotes de dados, por isso, mesmo que seja 40% mais pesado, não vai fazer tanta diferença assim pra você.</p>
<p>Da mesma forma, num banco de dados, a gente primeiro não quer perder 40% de espaço gravando num formato ineficiente como JSON ou XML ou qualquer outro protocolo baseado em texto. Mesmo desconsiderando isso, ainda tem outro problema. Uma função como JSON.stringify, converte a estrutura de dados do array inteira de uma só vez pra texto. Depois não tem como inserir ou modificar só uma linha. Como que adiciona um novo elemento no JSON? Não tem como de maneira rápida. E um banco de dados vai o tempo todo aumentando de tamanho e não dá pra ficar convertendo de binário pra texto, tabelas inteiras toda vez. É inviável. Tem que ter um jeito de ir adicionando só um insert ou update de cada vez, sem mexer no que já tava gravado, sem criar outro arquivo e sobrescrever por cima.</p>
<p>Pra gravar em disco queremos gravar em formato binário, algo próximo da tal árvore ordenada que mencionei antes e que simulei com índices usando uma árvore AVL. Você poderia pensar, ok, todo Nó de uma árvore contém a referência ou ponteiro na memória pra outros Nós. Basta gravar o nó em disco e mudar de ponteiro em memória pra endereço no disco, daí consigo navegar pelos nós no disco e consigo inserir novos nós fácil, certo?</p>
<p>Nos videos sobre sistemas de arquivos eu explico como que funciona um sistema de partição, endereçamento em sistemas de arquivos e tudo mais. Recomendo que dêem uma olhada. Mas pros nossos propósitos basta saber que na prática o sistema operacional enxerga um disco como se fosse um array gigante de dados, começando com a posição zero e incrementando pra 1, 2, 3 etc até acabar o espaço de 64-bits, que é endereços pra caramba, terabytes e terabytes. Então beleza, é parecido como memória funciona, que pra uma linguagem é como se fosse um arrayzão gigante que começa em 0 e vai endereçando até acabar a RAM, também com endereços de 64-bits. Então tá fácil, dá pra gravar um binário em RAM pra um binário em disco só convertendo as posições de cada nó quando passa de um arrayzão em memória pro arrayzão em disco.</p>
<p>Mas discos de HD tem uma diferença importante. Vamos pensar a árvore AVL. Deixa eu mostrar uma representação gráfica de como seria a árvore de índice de idades. Vou inserir aqui todas as idades de todo mundo que tá no array de usuários. Ela vai ficar assim. Conseguem enxergar que essa árvore é uma lista ordenada? Começa lendo da esquerda pra direita. Olha só. 22, 25, 27, 30, 30, 30, 30, 32, 33, 33, 35, 35, 35, 35 etc.</p>
<p>E se eu quiser achar todo mundo com idade maior que 40? Começa descendo tudo pra esquerda. 22 é menor que 40, vai pra direita, 27 é menor que 40, sobe, 25 ainda é menor, 30 é menor, sobe, 30 ainda é menor, 33 é menor, sobe 30 é menor, 35 é menor, sobe, 35 é menor. sobe, 40 é menor. Desce tudo até 40, é menor, opa 41. sobe, 42. opa. Daqui pra direita é tudo maior que 40. Então eu tenho no final 10 endereços pras posições na memória onde estão esses nós.</p>
<p>Essas posições em memória não são necessariamente consecutivas. Eu peço um espaço pro gerenciador de memória, tipo malloc, e ele me dá algum endereço que eu não me preocupo. Expliquei isso no episódio de gerenciamento de memória, dêem uma olhada depois. Mas enfim, saiba que toda vez que você inicia uma nova variável na sua linguagem, por baixo a variável é só um nome pra alguma posição na memória, um endereço, que você não sabe. Uma das utilidades de uma linguagem de programação é esconder esses endereços de você pra não ficar complicado. Em Assembly você teria que lidar direto com o endereço. Em disco é a mesma coisa, só que em disco vai ser a localização em coordenadas tridimensionais de qual dos discos, em qual trilha e em qual posição da trilha.</p>
<p>Tão vendo esse CD aqui, um HD é como se fossem vários CDs um em cima do outro. Em cima e embaixo de cada disco desse tem um cabeçote com um leitor, que vai navegar nos diversos cilindros ou trilhas desse disco. Agora imagina que aqueles 10 endereços que retornou pras idades maior que 40 seja cada um num disco diferente, em trilhas diferentes, em posições aleatórias. Concorda que pra ler tudo, o HD vai ter um trabalhão? Especialmente porque os discos não estão parados, eles ficam girando sem parar dentro do HD e o cabeçote tem que ir lendo até bater no endereço que foi pedido, daí ele muda de cilindro, e procura outro endereço. É muito trabalhoso pegar endereços aleatórios num disco, é muito lento. HDs funcionam melhor quando queremos pegar pedaços mais longos de dados de uma só vez numa única passada do cabeçote na trilha.</p>
<p>E essa nossa árvore AVL é super curtinha. Imagina um banco de dados de milhares ou milhões de nós. Seu HD ia fritar pra ficar procurando nós individualmente e aleatoriamente. Mas tem jeito melhor? Tem sim, na verdade tem dois jeitos, as árvores B-Tree e B+Tree que eu cheguei a mencionar no episódio de árvores e agora espero que faça mais sentido pra que eles servem. Deixa eu mostrar como ficam as mesmas idades ordenadas organizadas numa árvore B-tree.</p>
<p>Nem vou inserir todo mundo, só as idades dos 10 primeiros usuários. Olha o formato que essa árvore tá ficando. Entenderam? Cada nó pode ter mais de um valor. Em particular se eu pesquisar só quem tem mais de 40 anos, aí vou ter só esses dois grandes nós aqui na direita. E eles vão ser lidos do disco em no máximo 2 operações, em vez de ter que pesquisar e ler de 10 endereços aleatórios como no exemplo da árvore AVL. A grande sacada é justamente isso de acumular vários valores um atrás do outro num único nó grandão, num bloco ou cluster. Isso porque um HD não lê byte a byte, ele lê em setores ou blocos de uma só vez. Todo HD vai sempre ler pelo menos um bloco de 512 bytes ou até 4 kilobytes de uma só vez e colocar num buffer.</p>
<p>Se não ficou claro, lembra quando se formata um HD novo? Na tela de formatação ele pergunta sobre tamanho da unidade de alocação. Por padrão costuma ser 4 kilobytes que é o tamanho do setor. É a menor unidade que ele lê ou grava de uma só vez. Quanto maior for, melhor pra ler depois, mas desperdiça mais espaço, porque quando precisar gravar menos de 4 kilobytes, o que faltar, não pode reusar mais. Mas enfim, em um único setor do disco cabe nossa estrutura de usuários inteira, que a gente viu que ocupava menos de 4 kilobytes lembra? Então num HD nossa tabelinha de usuários cabe inteiro num único setor e consegue ser lido do HD de uma só vez.</p>
<p>O trabalho pra ler um único byte não compensa pro HD, como o disco tá girando sem parar, na hora que a cabeça começa a ler, ele já lê de uma só vez vários bytes. Ler um byte ou ler 4 kilobytes, é quase o mesmo trabalho. Então se eu organizar a árvore pra já conseguir organizar em clusters, fica mais fácil de ler depois. Na hora que o HD for ler esse valor 41 do exemplo, já vai ler de uma vez o 42, 43 e 47. Como já organizei na árvore pra serem valores consecutivos, ou seja, um atrás do outro sem intervalo, ele vai ler os 4 valores corretos numa única passagem.</p>
<p>Nesse exemplo eu fiz nós que vão ter no máximo uns 7 valores, mas numa árvore B-tree de verdade vai ter dezenas ou centenas de valores um atrás do outro em cada nó. E é assim que o HD vai gravar e vai ler de uma só vez. A diferença de uma árvore B-tree e B+tree é que no B+tree só os últimos nós da árvore que vão conter dados, os nós acima só vão conter metados, os ponteiros pros nós de baixo.</p>
<p>Atualizar a árvore é significativamente mais complicado do que a rotação que já mostrei de uma árvore simples Red Black ou AVL, mas o layout pro HD é muito mais amigável. Recomendo depois procurar num livro de algoritmos, como o do Cormen, os detalhes sobre uma B+Tree. No Cormen inclusive ele explica exatamente sobre essa vantagem e no meu repositório no GitHub desse episódio adicionei outro repositório como sub módulo que encontrei alguém que implementou em C a B+Tree do Cormen pra gravar a árvore em disco, pra quem tiver curiosidade.</p>
<p>E de fato, se eu procurar por &ldquo;btree&rdquo; no repositório do código fonte do banco sqlite3, vamos encontrar várias menções a árvore B-tree que ele usa pra gravar as tabelas, os índices, e tudo mais. E se sair procurando em outros bancos como MySQL, Postgres, até Redis e outros, você vai encontrar uma variação dessa árvore sendo usada.</p>
<p>Em resumo o importante é entender que um bom banco de dados precisa levar em consideração a estratégia e custos de ler e gravar dados em disco, em blocos, vários bytes de uma só vez. Um programador iniciante que só lida com variáveis em memória, costuma pensar só um byte de cada vez, mas é bom saber que dependendo do que tá fazendo, é mais eficiente pegar mais dados do que precisa e descartar o que não precisa. Não é intuitivo mas é justamente por isso que ninguém deve sair fazendo um banco de dados do zero sem ter conhecimento pra isso. Existem vários papers e anos de pesquisa pra cada aspecto de um banco de dados modernos. Lembrem-se que a gente vem fazendo isso faz mais de 60 anos.</p>
<p>A intenção do episódio de hoje não foi incentivar ninguém a escrever o próprio banco, mas sim entender alguns dos principais componentes que um banco de dados de verdade precisa ter. Muita gente tenta racionalizar um banco com variáveis e arrays em memória, e espero que tenha ficado mais claro como é bem mais do que isso. E também porque chamei o episódio onde explico árvores de &ldquo;O Começo de Tudo&rdquo;. Compiladores precisam de árvores. Bancos de dados precisam de árvores. Tudo precisa de árvores, e se você não consegue enxergar listas ordenadas como árvores, está perdendo o entendimento de como tudo funciona em computação.</p>
<p>Aliás, como último exercício de tudo que fiz até agora no meu banco estúpido, faltou mostrar mais uma coisa. Como transformar esse meu console de linha de comando de SQL em um sistema cliente/servidor, como um banco de verdade. E esse nem vou explicar o código, porque é um servidor simples de TCP. Pra quem aprendeu redes na faculdade isso aqui é o primeiro dia de aula. Mas olha só. Num terminal vou executar o servidor com <code>./stupid-server.mjs</code> e pronto, isso vai pendurar o meu servidor na porta 4000 da minha máquina.</p>
<p>Em outro terminal, vou executar <code>./stupid-client.mjs</code> que vai tentar se conectar na porta 4000. E no terminal do servidor, mostra que alguém se conectou. Esse cliente não tem nenhuma lógica do banco, não importa nenhum código do interpretador de SQL nem o array de users, mas eu posso mandar comandos em SQL. Esses comandos vão trafegar pela rede e chegar no servidor. O código do servidor é parecido com o REPL, e no fim devolve o resultado de volta pro cliente.</p>
<p>E pra provar que é um servidor mesmo, posso abrir outro Terminal separado e abrir mais um &ldquo;stupid-client&rdquo;. O servidor mostra que mais alguém se conectou. Posso mandar mais SQL por aqui, e ele vai responder. E esse é o esqueleto básico de como um servidor de banco de dados funciona. Depois estudem o código no repositório do GitHub pra ver como eu fiz isso, mas se entenderam até aqui, essa foi a parte mais fácil, eu literalmente fiz esse cliente e servidor em uns 15 minutos.</p>
<p>Fazer um banco de dados de verdade exige conhecimento bem mais avançado do que isso em ciências da computação e vai exigir centenas, milhares de horas homem de desenvolvimento, testes e ajustes. Essa minha versão estúpida eu justamente chamo de estúpido porque fiz só as coisas mais fáceis, o suficiente pra fazer esse episódio. Eu basicamente peguei o feriado e pensei &ldquo;hum, que tipo de banco SQL será que consigo fazer em uns 2 ou 3 dias de trabalho&rdquo;, e foi isso aqui que mostrei pra vocês. Em particular, o interpretador de SQL, eu nunca tinha usado Antlr4 antes, então foi a parte que gastei mais tempo pra fazer, devo ter gasto pouco mais de 1 dia pra fazer o parser e interpretador. Portanto, tendo conhecimentos básicos de algoritmos e as coisas que já vinha explicando no canal, dá pra fazer esse exercício do zero em uns 3 dias.</p>
<p>Eu me coloquei um limite de tempo porque não dava pra ficar eternamento mexendo no código, uma hora eu precisava gravar este episódio. Mas se alguém se interessar puramente pelo exercício, o próximo passo que não fiz foi justamente mover o array de objetos do &ldquo;users-database&rdquo; pra uma árvore B+tree e gravar em disco, incluindo os índices. Só que aí precisaria também implementar um cache, pra não precisar ir toda hora no disco, e precisaria implementar um sisteminha simples de lock, caso na versão cliente e servidor, vários clientes tentem modificar os dados ao mesmo tempo.</p>
<p>Além disso era bom fazer um limite de conexões. Javascript não tem multi-thread real, então teriamos um limite do que é possível fazer. E nesse caso sintam-se a vontade de reimplementar tudo numa linguagem diferente como Java ou C# ou qualquer outra coisa assim. Só que se fizerem numa linguagem que não é dinâmica, aí vão ter mais trabalho por não terem a trapaça que eu fiz usando &ldquo;eval&rdquo; pra executar as condições de where. Enfim, dá pra ficar brincando bastante em cima disso ainda.</p>
<p>Pra só usar um banco de dados precisa saber em detalhes como ele foi feito? Claro que não. Justamente o que mostrei aqui já é um modelo mínimo suficiente pra ter na cabeça pra não parecer que é uma caixa preta mágica. Usem isso pra ajudar a explicar os principais conceitos técnicos de como um banco de dados funciona pra colegas e amigos iniciantes. Quero saber se isso ajudou a mudar um pouco o que vocês imaginavam que era um banco de dados. Num outro episódio vou falar mais especificamente de bancos de verdade como um Postgres, Redis ou Dynamo. Mas por hoje acho que já ficou comprido o suficiente. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e compartilhem o video pros seus amigos. A gente se vê, até mais.</p>
]]></content:encoded><category>bancos-de-dados</category><category>algoritmos-e-estruturas-de-dados</category><category>javascript</category><category>akitando</category></item><item><title>[Akitando] #116 - De 5 Tera a 25 Giga | Compressão de Dados e Multimídia</title><link>https://akitaonrails.github.io/2022/03/23/akitando-116-de-5-tera-a-25-giga-compressao-de-dados-e-multimidia/</link><guid isPermaLink="true">https://akitaonrails.github.io/2022/03/23/akitando-116-de-5-tera-a-25-giga-compressao-de-dados-e-multimidia/</guid><pubDate>Wed, 23 Mar 2022 14:28:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/j4a1SgUWh1c"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;p&gt;Huffman, Lempel-Ziv, LZ77: o que esses nomes tem a ver com o video que você tá assistindo aqui no YouTube e Netflix da vida? Vamos ver de uma forma prática sobre compressão de dados e como eles são aplicados no seu dia a dia. E de quebra vai aprender uma pequena introdução a ciência de cores e processamento de imagens e videos.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/j4a1SgUWh1c"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<p>Huffman, Lempel-Ziv, LZ77: o que esses nomes tem a ver com o video que você tá assistindo aqui no YouTube e Netflix da vida? Vamos ver de uma forma prática sobre compressão de dados e como eles são aplicados no seu dia a dia. E de quebra vai aprender uma pequena introdução a ciência de cores e processamento de imagens e videos.</p>
<h2>Conteúdo<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>00:00:00 - O problema: Como faz streaming de 5TB em 2h??</li>
<li>00:03:52 - Cores não é só RGB: entendendo YUV</li>
<li>00:08:51 - Chrome Subsampling</li>
<li>00:13:52 - Contínuo pra Discreto: Fourier</li>
<li>00:15:22 - Resolução de Áudio</li>
<li>00:18:50 - Transformada Discreta de Fourier</li>
<li>00:20:11 - Transformada Discreta de Cosseno</li>
<li>00:24:14 - Quantização</li>
<li>00:27:37 - Run-Length Encoding</li>
<li>00:29:51 - Huffman Encoding</li>
<li>00:36:45 - Recapitulando até aqui</li>
<li>00:38:06 - JPEG</li>
<li>00:40:12 - GIF</li>
<li>00:42:44 - Lempel-Ziv</li>
<li>00:44:50 - Sliding Window</li>
<li>00:46:13 - Legado do PKZIP</li>
<li>00:49:26 - A treta da Unisys e GIF: PNG</li>
<li>00:53:33 - Aplicando tudo isso em video</li>
<li>00:55:00 - Codecs de video: AVC, HEVC</li>
<li>00:57:17 - Interframes e Intraframes</li>
<li>00:58:58 - Problema resolvido: bitrates</li>
<li>01:01:27 - codecs profissionais vs de consumo</li>
<li>01:04:40 - Resumo e Conclusão</li>
</ul>
<h2>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><ul>
<li>But what is the Fourier Transform? A visual introduction. (<a href="https://www.youtube.com/watch?v=spUNpyF58BY"target="_blank" rel="noopener">https://www.youtube.com/watch?v=spUNpyF58BY</a>)</li>
<li>How are Images Compressed? [46MB ↘↘ 4.07MB] (<a href="https://www.youtube.com/watch?v=Kv1Hiv3ox8I"target="_blank" rel="noopener">https://www.youtube.com/watch?v=Kv1Hiv3ox8I</a>)</li>
<li>Original CompuServe announcement about GIF patent (Original CompuServe announcement about GIF patent)</li>
<li>Thomas Boutell (<a href="https://twitter.com/boutell/status/1357707533612441604?s=20&amp;t=A2zgWT-iY2CiZrNvMI4lBw"target="_blank" rel="noopener">https://twitter.com/boutell/status/1357707533612441604?s=20&t=A2zgWT-iY2CiZrNvMI4lBw</a>)</li>
<li>Huffman Visualization (<a href="https://cmps-people.ok.ubc.ca/ylucet/DS/Huffman.html"target="_blank" rel="noopener">https://cmps-people.ok.ubc.ca/ylucet/DS/Huffman.html</a>)</li>
<li>Chroma Subsampling – 4:4:4 vs 4:2:2 vs 4:2:0 (<a href="https://www.displayninja.com/chroma-subsampling/"target="_blank" rel="noopener">https://www.displayninja.com/chroma-subsampling/</a>)</li>
<li>How JPEG works (<a href="https://cgjennings.ca/articles/jpeg-compression/"target="_blank" rel="noopener">https://cgjennings.ca/articles/jpeg-compression/</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Todo mundo já zipou alguma coisa. Até quem não é programador sabe que existe isso de &ldquo;zipar&rdquo; as coisas. Um programador tem que ir um passo além de só acreditar cegamente que as coisas funcionam como mágica. Como funciona compressão de dados? Já pararam pra pensar nisso? Melhor ainda, estamos assistindo um video aqui no YouTube. Como funciona a compressão de video pra possibilitar assistir isso aqui em Full HD ou 4K? Quem estudou ciência da computação provavelmente aprendeu o básico. A idéia do episódio de hoje não é falar de tudo que existe no assunto, mas dar mais noção pra você parar de ficar no escuro e aumentar sua curiosidade pra continuar estudando.</p>
<p>(&hellip;)</p>
<p>Vamos fazer uma conta de padeiro. Digamos que você tem grana sobrando, comprou uma TV nova 4K, e quer assistir filmes em 4K, com 8-bits de cor, em pelo menos 30 quadros por segundo. Um monte de jargão, vamos traduzir. Dizer 4K significa dizer 4 vezes a densidade de pixels de Full HD, High Definition. Full HD é a mesma coisa que 1080p, ou seja 1920 pixels na vertical e 1080 pixels na horizontal. O &ldquo;p&rdquo; é de progressivo, antigamente a gente tinha “i” de interlaced ou entrelaçado que ninguém usa mais então nem vou falar disso. Então, 4K é o dobro de resolução na vertical, que dá 3840 pixels e o dobro de resolução na horizontal, que dá 2160 pixels. O agora famoso 2160p. E cuidado que 8K tá chegando.</p>
<p>Tá claro que dobrando altura e comprimento, quadruplica a quantidade de pixels né? Geometria do primário aqui. Agora, se você é programador front-end, tá acostumado a cores de 24-bits, onde cada componente de RGB tem 8-bits cada, que são 256 tons pra cada uma das 3 cores primárias, vermelho, verde e azul. Normalmente a gente codifica em hexadecimal, então vai de 00 até FF. Preto é todas as cores zeradas então 000000. Branco é todas as cores no máximo, então FFFFFF. Esse é o básico de CSS que se aprende no dia 1, então vou assumir que todo mundo aqui tá confortável com isso.</p>
<p>Significa que cada cor precisa de 8-bits ou 1 byte, total de 3 bytes pra guardar uma cor por pixel. Repetindo, 24-bits é a mesma coisa que 3 bytes. Uma imagem 4K então vai ter 3840 colunas vezes 2160 linhas que dá um total de 8 milhões, 294 mil e 400 pixels. Tudo isso vezes 24 bits por pixel, que vai dar 24 milhões, 883 mil e 200 bytes, ou quase 24 megabytes por imagem.</p>
<p>Mas um video são 24 ou 30 desses quadros por segundo. A cada segundo precisaria trafegar 24 megabytes vezes 30 quadros que dá mais de 711 megabytes por segundo! De curiosidade, quanto precisaria ter de SSD aí no seu computador pra guardar um filme de 2 horas então? Só multiplicar por 7200 segundos, que é quantos segundos tem em 2 horas. Então 711 megabytes por segundo vezes 7200 segundos, vai dar a quantidade absurda de 5 milhões de megabytes, ou seja, nada menos que 5 terabytes.</p>
<p>Pensa um segundo, se isso fosse verdade, eu tenho certeza que o seu SSD aí na sua máquina não seria capaz de armazenar nem um único filme. Mas eu também sei que você baixa um monte de filmes via Torrent e deve ter uma dúzia nesse instante aí na sua pasta de downloads. Você é um programador, sua função é escovar bits. Como que você escovaria esses 42 trilhões de bits pra caber no seu HD?</p>
<p>O campo de compressão de video em particular é um corpo de conhecimento gigantesco, tem centenas de pesquisas e implementações e formas diferentes de resolver o problema. O que eu vou fazer a partir daqui é uma simplificação super grosseira pra vocês entenderem o básico, ter um modelo na cabeça pra ficar mais fácil quando quiserem ler sobre os detalhes avançados depois. Pra quem já é profissional da área, perdoem a simplificação mas é por uma boa causa. Vamos continuar.</p>
<p>Eu venho falando faz vários videos que tudo no final vira um linguição de bits. Um computador só entende uma fita de bits, nada mais. E uma fita de bits é nada mais, nada menos que um numerozão gigante de milhões de bits. Veja meus videos sobre a máquina de Turing e a introdução mais hardcore de computadores que soltei alguns meses atrás. Mas entenda que um video é também é só isso, um linguição de bits. Nosso trabalho, como programadores, é representar o mundo em forma de bits e processar esses bits pra tranformar em outros bits mais úteis. Um programa é só uma função, que nem uma equação, que recebe números e cospe números, no caso números em binário, bits.</p>
<p>E pra isso você tem que parar de assumir que as coisas são regras escritas em pedra. Por exemplo, se você é só programador de front-end e seu dia a dia é justamente escrever cores em hexadecimal pra RGB, seu mundo é estático em pensar que a única forma de representar cores é juntando 1 byte pra vermelho, 1 byte pra verde e 1 byte pra azul, e acabou. Isso não é verdade. Existem diversas formas de representar cores e RGB é a mais fácil de entender, porque todo mundo misturou tintas na escola em algum momento, mas não é a forma mais eficiente. Aqui começa aquele troço que todo iniciante não entende chamado “abstrações”. Vamos ver na prática o que isso significa.</p>
<p>E se eu quiser gastar menos bits por cor? Podemos diminuir de 24-bits pra digamos, 12-bits. Daí em vez de gastar 3 bytes no total, vamos gastar só 1 byte e meio, metade do espaço. Só que as possibilidades de cores não cai pela metade. Lembra, são potências de 2. Ou seja, caimos de um total de mais de 16 milhões de cores pra só umas 4 mil cores. Pra economizar metade do espaço de bytes, perdemos milhões de possibilidades de cores. É parecido quando se converte uma foto pra um GIF, que vou explicar sobre mais pro fim. Mas 4 mil cores é muito pouco hoje em dia. Pra fazer jogo de Nintendinho em pixel art tá mais que bom. Mas pra editar fotos, é impossível. É muito pouca informação de cor. Até um amador consegue ver uma foto com paleta de 4 mil cores e dizer que a qualidade é inaceitável.</p>
<p>Então não dá pra cortar bits do RGB, cada bit que eu corto, divido o total de cores por 8. Fodeu então? Não. No caso de video não se usa RGB e sim outra representação chamada YUV, alguns já devem ter visto esse nome. Mas simplificando a idéia é dividir a cor em 2 grandes componentes, a luminância que é o brilho ou luma e o chroma, que é representado por diferença de azul e diferença de vermelho. Quando se fala em color science, é aqui que isso começa. Em vez de pensar em cores como mistura de tintas, imaginamos cores como a distância pro ponto mais branco num espaço tridimensional.</p>
<p>Se você é fotógrafo ou algo assim já deve ter ouvido falar em coisas como sRGB, Adobe RGB ou DCI-P3 que é color gamut ou gama de cores, que é um subconjunto do total teórico de cores possíveis de existir no espaço de cores. YUV representa numericamente todas as cores teóricas e color gamut como Adobe RGB é o que realmente é possível de mostrar num determinado monitor. Essa parte é bem longa, tem que passar por coisas como correção de gama, mas na prática se você não sabe a importância de DCI-P3 contra sRGB, é porque pra você isso não faz diferença. Só fotógrafos e coloristas profissionais que precisam saber os detalhes disso.</p>
<p>YUV vem da era de TV preto e branco e como fazer broadcast de video, daí já existia o conceito de luma, já que não tinha cores, ou chroma, ainda. Em YUV o Y é o luma, U é a projeção de azul e V é a projeção de vermelho. Em resumo, converter de RGB pra YUV é multiplicar o RGB por esta matriz aqui. Eu sei, assusta pra quem nunca viu Álgebra Linear. E converter de volta pra RGB é só inverter e multiplicar YUV por esta outra matriz. É uma conversão linear, um pra um, sem perder informação nenhuma. Se nunca pensou nisso, RGB é uma coordenada em 3 eixos de cor num espaço tridimensional, um vetor.</p>
<p>Pra visualizar, vejam esta imagem, um quadro de um video. Se separar em RGB, teríamos esse quadro representando só o componente vermelho, este só o verde e este outro só o azul. É como você pensa em canais no Photoshop, cores primárias sendo misturadas. Mas depois de converter pra YUV, agora este é o Y ou luma, que seria a imagem preto e branco e tons de cinza. Este é o quadro de projeção de azul e este outro o de projeção de vermelho, que é o componente UV ou chroma. Junte os 3 e temos a imagem original de volta.</p>
<p>Lembram DVD player antigo ou video game antigo? Antes de existir HDMI como que vocês ligavam os aparelhos na TV? Quando era modelo barato era só um cabo com conector RCA, o cabo composite ou composto. Mas tinha modelos melhores, com 2 ou 3 cabos RCA separados. Alguns poderiam chutar que era RGB, sinal de uma das 3 cores primárias em cada cabo. Mas não, é um cabo pra luma, e os outros dois pra chroma. Por isso algumas TVs podiam até ter escrito atrás YCrCr que é a mesma coisa que YUV.</p>
<p>Sem entrar em detalhes sobre correção de gama, color space e outros detalhes, o que ganhamos fazendo isso? Beleza, convertemos um número de RGB em outro número em YUV, mas continua usando os mesmos 24-bits. “Tomeito, tomáto”. Não mudou nada. Ah, mas agora vem o pulo do gato. Na ciência de cores, pesquisas mostraram que o olho humano é mais sensível a mudanças de luma do que de chroma. Ou seja, o componente Y é muito mais importante que U e V. Em RGB a informação de brilho vem embutido em cada cor primária, e quando convertemos em YUV o que fizemos foi retirar essa informação pra um número separado de luma.</p>
<p>Vejam de novo a separação de canais, a imagem preto e branco é super nítida, conseguimos saber o que é. Mas agora olha os componentes de chroma, difícil de discernir o que estamos vendo né? Principalmente os detalhes. O mesmo não acontece se vermos os canais separados de RGB, porque a informação de luma está distribuido nas 3 cores primárias. Tão começando a ver a vantagem de representar a mesma informação de jeitos diferentes? RGB é mais fácil de descrever mas YUV vai ser mais fácil de manipular como vamos ver já já.</p>
<p>Já tentou ligar seu computador no HDMI da sua TV velha, que não tem um modo game? Já notou que assistir videos é normal, fica bonito. Mas quando liga o computador pra digitar um email as letras ficam todas meio borradas? Isso acontece porque sua TV é YUV 4:2:0. E o que significa isso? Pra entender vamos voltar pra RGB. Quando falamos em RGB estamos falando de 8-bits cor primária. Quando falamos de YUV estamos falando de samples ou grupos de 4 pixels organizados em duas linhas. Nesta primeira imagem temos o formato não comprimido, logo depois da conversão de RGB, que chamamos de 4:4:4, toda a informação de luma e de chroma preservados.</p>
<p>Agora, já disse que as pesquisas mostraram que nossos olhos são mais sensíveis a mudanças de luma do que de chroma. E se mantivermos a mesma quantidade de bits pra guardar luma então, garantindo a definição da imagem, e diminuirmos a quantidade de bits pra guardar chroma? Daí podemos pular pro esquema 4:2:2 que nem podemos ver aqui. Notem o importante, a informação de luma continua a mesma, mas cortamos chroma pela metade nesse sample, nesse grupo de pixels. Isso economiza um terço de bytes e caímos dos 48 bytes pra 36 bytes. Mas dá pra melhorar. Veja que perdemos muito menos informação do que quando tentamos cortar bits de RGB.</p>
<p>O que as pesquisas mostraram é que podemos cortar ainda mais no chroma e com isso chegamos ao formato YUV 4:2:0 que toda TV usa, que cai dos 48 bytes originais pra 30 bytes. Eu não tô muito certo dessa conta, mas é nessa faixa. Outra coisa é que o 2 e zero no 4:2:0 não se refere ao total de pixels. É como se fosse uma proporção. O primeiro 4 se refere a todos os 4 sample de pixels originais do canal de luma. O segundo 2 é o número de samples de chroma na primeira linha. O terceiro 0 é o número de mudanças dos samples de chroma entre as duas linhas, no caso 0 mudança no formato 4:2:0.</p>
<p>Se pareceu complicadinho é porque é mesmo. O que chamamos de YUV na realidade é o padrão YCbCr às vezes chamado de YCC e é tudo a mesma coisa. Mas não confundir com YPbPr que era o padrão mais antigo. Além disso eu falei que tem vários detalhes como correção de gamma e color space, que define padrões como o Rec. 709 que é o que todo mundo usa pra videos normalmente, como aqui no YouTube. Na prática, convertendo de RGB pra YUV 4:4:4 é um pra um, visualmente e em quantidade de bytes, não muda nada.</p>
<p>Mas mantendo o luma em 4 e descendo chroma pra 2:0, economizamos uns 40% de dados sem mudanças perceptíveis pra um ser humano, especialmente num video em movimento. Por isso a maioria das TVs é 4:2:0, por isso que video de DVD, Blu-Ray, streaming, incluindo esse aqui do YouTube é encodado em YUV 4:2:0, segundo o padrão Rec. 709. E por isso eu falei que quando se liga um computador numa TV que não suporta modo de game, PC, ou mais corretamente, YUV 4:4:4, as letras e detalhes precisos vão ficar meio borrados. Como 4:2:0 precisa de menos dados pra representar, é ideal pra fazer broadcast ou streaming ou guardar arquivos de video, porque precisa de menos banda e menos espaço sem quase perder qualidade do video, com exceção de detalhes pequenos, como letras e fontes pequenas.</p>
<p>Cada um dos componentes de YUV é representado por samples, e de 4:4:4 pra 4:2:0 estamos diminuindo samples do chroma então chamamos esse processo de Chroma Subsampling. Repetindo, estamos diminuindo bits de cor e mantendo bits de brilho pra ter praticamente a mesma imagem aos nossos olhos usando menos bits. Tudo que você achava que era RGB na verdade é YUV 4:2:0. Quando converte de 4:2:0 de volta pra RGB pra editar, ele completa os bits dos samples que faltam, duplicando dos que sobraram, então só aumenta o tamanho de bits mas não recupera o chroma perdido, lógico. Guarde essa informação.</p>
<p>Agora pára e pensa, um video é basicamente um conjunto de imagens. No nosso exemplo, imagens 4K. E precisa de 30 dessas imagens todo segundo pra termos algo em movimento suave como vemos na TV. Se a gente consegue reduzir 40% de todas as imagens, o video inteiro também fica 40% menor, portanto aqueles 5 terabytes pode cair pra até 3 terabytes, olha só que sensacional. Só que 3 tera ainda é absurdo pra 2 horas de video, continua não cabendo aí no seu computador. Como que faz então?</p>
<p>Antes de continuar deixa eu introduzir mais um jargão pra vocês. A compressão de cores ou downsampling, que é descer o números de samples de chroma, é só uma técnica pra economizar bits jogando fora informação que nossos olhos não são bons de detectar. Isso significa que uma vez feito o downsampling, não tem mais como voltar pra imagem original, porque os detalhes originais foram simplificados. Isso é uma compressão que chamamos de lossly, ou seja, que perde informação.</p>
<p>Mas podemos ir além de chroma downsampling. Uma das principais funções de computadores é representar o mundo natural, analógico, contínuo, pro mundo digital, que é discreto e exato. Quando falamos em pixels, estamos arbitrariamente pegando a luz que chega de algum objeto e dividindo pacotes de luz em pixels, que é uma medida exata e discreta. Temos uma representação digital perto o suficiente pra ser convincente pros nossos olhos. Quanto mais pixels ou samples tivermos, quanto mais resolução, mais enxergamos a imagem como realista.</p>
<p>O mesmo vale pra outras coisas da natureza que queremos digitalizar como música e sons em geral. Se aprendeu física acústica no colegial vai lembrar que sons ou ondas representamos com um gráfico de curvas, senos, cossenos, radianos. Se já abriu qualquer editor de áudio como um Audacity ou Adobe Audition, já viu essa representação de ondas. E qualquer engenheiro de áudio sabe o que precisa fazer quando recebe onda analógica pra processamento digital: transformada de Fourier.</p>
<p>Eu mesmo não lembro mais quase nada de Fourier que aprendi na faculdade, mas acho que o principal é o seguinte. Da mesma forma que uma imagem não é formada de pixels discretos, uma onda de som também é contínua, mas num computador precisamos representar de forma discreta. Por isso que uma curva bonitinha como dessa imagem, uma linha contínua, pro computador fica tipo uma escadinha, que é um intervalo de valores discreto que mais ou menos representa essa curva original. Lembra quando eu falo que precisa pelo menos ter uma intuição matemática? Se gastou tempo estudando coisas como cálculo, enxergar isso deveria ser trivial.</p>
<p>Assim como temos samples pra representar som, samples de pixels por segundo representa video. Em video também representamos um movimento contínuo em quadros por segundo discretos. Quanto mais quadros por segundo pudermos ter, mais o movimento fica suave. Mas convencionamos uns 30 quadros por segundo que já é suficiente pra gente conseguir assistir um filme sem achar estranho. Quadros por segundo é uma medida de frequência. 30 quadros por segundo seriam 30hz. Um game precisa de no mínimo uns 60 quadros por segundo pra ficar bom de jogar, por isso compramos monitores de 60hz ou mais.</p>
<p>Quanto mais samples puder ter, maior a resolução, mais preciso vai ser o video ou áudio. A convenção da qualidade de CD de música é de 44.1khz, com samples de 16-bits, ou seja 44 mil e 100 samples por segundo, ordens de grandeza mais samples por segundo que video, mas cada sample ocupa bem menos espaço. Pelo jeito precisamos de bastante resolução pra enganar nossos ouvidos. Música HiFi que é o equivalente 4K da música, são pelo menos 192 khz com samples de 24-bits.</p>
<p>Voltando pra CD, 16-bits vezes 44 mil e 100 dá 86 kilobytes por segundo. Um álbum de 24 músicas de 5 minutos cada, ou seja, 7200 segundos, vezes os 86 kilobytes por segundo vai dar aproximadamente 604 megabytes, que se você cresceu nos anos 90 e começo dos 2000 vai lembrar que um CD cabia 650 megabytes, por isso só isso de música já enche um CD. Era até engraçado porque antigamente tinha consoles como o Sega CD ou 3DO. Esses consoles não tinham potência pra ler arquivo de áudio comprimido tipo os mp3 de hoje e descomprimir em tempo real, por isso o áudio era sempre crú, descomprimido.</p>
<p>Se tivesse as 24 faixas na trilha sonora, ocupando 600 mega, sobrava menos de 50 mega pro jogo em si. Por isso que cabia, porque um cartucho de megadrive não era muito mais que uns 2 megabytes, daí cabia fácil num CD de Sega CD. Mas eu tô fugindo do assunto. Mencionei áudio pra explicar que tudo analógico, imagem, video e áudio migram do mundo contínuo pro mundo discreto e no caso de áudio, todo engenheiro sabe analizar ondas usando transformada de Fourier, mais especificamente transformada discreta de Fourier pra representar um período de samples de onda como equações lineares.</p>
<p>Eu não tenho a matemática pra conseguir explicar isso fácil mas entenda que é uma conversão de sinais analógicos contínuos num domínio de frequências discretas, ou seja, dividir uma onda contínua de som em conjuntos de bits discretos. Pra quem estudou matemática, não dá pra representar todos os infinitos pontos de uma curva em conjuntos de números discretos, mas podemos achar as equações que geram esses pontos em determinado momento do tempo.</p>
<p>O problema de transformada discreta de Fourier é que o cálculo disso é lento. Consoles e micros antigos dos anos 80 não tinham como calcular em tempo real. Mas nos anos 60 descobriram uma aproximação que é ordens de grandeza mais fácil de calcular, pulando da complexidade quadrática da transformada discreta de Fourier pra complexidade logaritmica, N.log N que é a transformada rápida de Fourier ou Fast Fourier, que é considerado o algoritmo numérico mais importante já criado.</p>
<p>Quando falam se é importante entender matemática e tals, esse é &ldquo;o&rdquo; caso mais importante em processamento digital e se tiver interesse em pelo menos ter uma intuição visual de como enxergar uma transformada de Fourier eu recomendo muito os videos do Grant do canal 3Blue1Brown. Ele é o melhor professor de matemática que já vi no YouTube e em particular sobre Fourier tem uma forma visual animada que vai te dar uma imagem mental muito melhor do que eu jamais conseguiria.</p>
<p>Muito bem, vamos voltar. Porque falei todo esse mambo jambo de matemática? Porque grupos de pixels em imagens podemos tratar como séries de frequências também. Falando em resumo, bem resumido, estamos com nossa imagem gigante de 4K depois de fazer color downsampling pra YUV 4:2:0. O que podemos fazer agora?</p>
<p>Transformada Discreta e Transformada Rápida de Fourier transformam ondas contínuas em uma representação de uma série discreta. Quando falamos em ondas, falamos de senos e cossenos. E nessas equações estamos considerando duas dimensões, por isso são equações de números complexos. Lembra número complexo, que tem um componente que é um número real e outro que é um número imaginário.</p>
<p>Quando vamos mexer com imagens só precisamos de uma dimensão de números reais e por isso em vez de DFT ou FFT podemos usar DCT que é transformada discreta de cosseno. Em áudio usamos FFT não só pra representar o áudio analógico de forma digital mas também pra fazer DSP ou processamento digital de sinais, por exemplo, decompondo uma onda em ondas mais simples usando senos e cossenos. E de novo, eu to simplificando grosseiramente, mas é assim que fazemos coisas como denoising que é redução de ruído, equalização e tudo mais.</p>
<p>Pois bem, com DCT que é a transformada discreta de cosseno vamos decompor o luma e chroma de cada imagem em uma soma de funções de cosseno de diferentes frequências. Fodeu né. Calma que é complicado mesmo, mas pra visualizar não é tanto assim. Como uma imagem pode ter qualquer dimensão, o algoritmo começa dividindo a imagem em blocos de 8 por 8 pixels. Não importa se é uma imagem pequena do tamanho de um ícone ou gigante do tamanho de uma TV 8K. Vamos sempre trabalhar em blocos de 64 pixels de cada vez. Em computação a gente sempre usa a estratégia de dividir e conquistar. Nenhum trabalho é tão grande que não possa ser quebrado e atacado um pedaço de cada vez.</p>
<p>Agora o trabalho da fase de DCT é quebrar os canais de luma, chroma azul, chroma vermelho em uma soma de funções de cosseno. Simplificando, essas funções de cosseno podemos ver visualmente nesta imagem também de 8 por 8 imagens. É uma tabela pré-calculada, não se preocupem agora como ela foi criada, só aceitem que existe. Cada quadrado representa uma onda diferente, a tal função de cosseno, em duas dimensões. Lá no canto superior esquerdo é todo branco, normalmente o componente mais usado. E lá embaixo é uma pattern que parece tabuleiro de xadrez ou damas, um checkerboard.</p>
<p>Temos que processar os 3 canais de YUV, mas vamos pegar primeiro um bloco de 8 por 8 pixels só do canal luma, que é a camada preto e branco. E vamos representar cada pixel pela intensidade de preto até branco, indo de 0 até 255. Na real acho que é de 16 até 235 ou algo assim por conta de correção de gama mas não interessa agora. Funções de cosseno, você lembra que é em radianos no eixo X, por exemplo Pi que é 180 graus e 2 Pi que é 360 graus. Mas no eixo Y vai de -1 até 1 positivo.</p>
<p>Então a primeira transformação que precisamos fazer pra cada pixel no nosso bloco é substrair por 128 pra dar números que vão de -128 até 127. Agora usando DCT, e aqui é a parte que vai ser uma caixinha preta que vocês vão ter que acreditar que funciona, vamos criar uma nova matriz de 8 por 8 com os pesos pra cada uma das imagens de frequência de base. Cada uma das imagens dessa tabela contribui um pouco pra reconstruir a imagem original. Se não ficou claro, veja esta imagem de uma pequena letra “A”. Pegando cada uma daquelas imagens bizarras de cosseno que eu falei e aplicando os pesos ou coeficientes e somando um em cima do outro, no final eu reconstruo o “A” original, sacaram?</p>
<p>Se você é de CSS, de novo, é como se esses coeficientes fossem o tanto de RGB, o tanto de vermelho, o tanto de verde e o tanto de azul que precisa misturar pra fazer, por exemplo, marrom escuro. É o peso de cada cor. A diferença é que em vez de misturar, ou seja, somar componentes de cor, estamos somando tipo funções de cosseno, usando esses pesos pra dizer quanto que cada função contribui pra reconstruir a imagem original.</p>
<p>A matriz de coeficiente resultante é interessante pela forma como essa base de funções de frequência é construído, blocos mais lá pro canto superior esquerdo tentem a contribuir mais pra reconstruir nosso bloco original de 8 por 8, por isso usamos coeficientes maiores. Mas os blocos mais lá pro canto inferior direito, contribuem pouco, são funções de alta frequência. E faz sentido, essa tabela base foi construída de tal forma a separar componentes de baixa frequência e de alta frequência.</p>
<p>Mesmo se você não for de computação já deve ter ouvido que animais, como cachorros, conseguem ouvir sons de alta frequência que nosso ouvidos humanos não conseguem ouvir. Mesma coisa pra luz, a luz visível é só uma faixa pequena de todas as frequências de cores. E sim, lembra que luz é uma onda, assim como som é uma onda. Ondas de baixa frequência começam lá atrás na faixa dos raios gama, depois raios x, depois ultravioleta, aí chegamos na minúscula faixa que é a luz que nós conseguimos enxergar, mas daí vai pro infravermelho até ondas de rádio.</p>
<p>E mesmo na faixa visível, nas bordas com o ultravioleta e infravermelho é difícil de distinguir um limite. Essas faixas não são discretas, tipo “a partir exatamente deste ponto que a gente não consegue ver”. Vai variar. E essa tabela base é feita pra quando calcularmos a tabela de coeficientes, os números que se aproximam do canto inferior direito sejam cores de alta frequência que a gente não consegue distinguir direito, mais uma vez, depois de anos de pesquisa, sabemos o que não conseguimos enxergar. Assim como na fase de color downsampling já reduzimos o chroma, agora vamos reduzir mais ainda nos 3 canais.</p>
<p>E isso foi pra um bloco de 8 por 8 pixels da imagem. Agora precisa repetir a mesma coisa pra todos os outros blocos que formam a imagem, em todos os 3 canais de luma e chroma. Fazendo tudo isso, até agora não comprimimos nada, só processamos os números que representam cada pixel de cada canal em outro número processando com a transformada discreta de cosseno. Mas o pulo do gato vem agora. Lembra quando você vai salvar uma imagem em JPEG e o Photoshop ou outro programa pede pra escolher se quer salvar em alta qualidade ou baixa qualidade, e tem um slider pra escolher algum valor no meio? E normalmente você escolhe lá pelos 80% porque você acha que isso vai salvar 80% dos detalhes do original?</p>
<p>Pois é, essa é a fase chamada de quantização. Na prática é uma divisão de matriz. Um exemplo é essa tabela aqui. Notem que os números mais perto do canto superior esquerdo são menores e os próximos do canto inferior direito são maiores. Lembra as tabelas resultantes de coeficientes pra cada bloco de 8 por 8 pixels? Pois bem, vamos dividir cada número por cada número correpondente nessa tabela de quantização. Cada nível de qualidade, tipo os 80% que você escolheu pra salvar no Photoshop é uma tabela diferente. Quanto menor a qualidade, maiores os números dessas tabelas de divisão.</p>
<p>Dividindo um número por outro grandão o resultado vai ser um número bem pequeno. Se der quebrado, arredonda pro inteiro mais próximo. Isso significa que os coeficientes mais próximos do canto interior direito vão ficar pequenos, ou melhor ainda, zerados. É aqui que começa a fase real de compressão. Olha só o resultado da divisão, uma boa parte dos números fica zerado e não tem problema porque elas contribuem pouco na reconstrução da imagem, no sentido que os detalhes que acrescentam, nossos olhos não distinguem tão bem. A tal da região da alta frequência.</p>
<p>Vamos fazer essa divisão pra todos os blocos que compõe a imagem original. E como último truque, vamos rearranjar esses pixels de lugar. Lembra que a imagem toda é só um linguição de bits? Onde cada grupo de uns 16 bits representa um pixel em YUV? A gente só tá mostrando elas quebradas em linha, formando um retângulo, pra ficar fácil de ver mas na realidade é uma linha atrás da outra no binário. E cada bloco de 8 por 8 pixels representa um pedaço de cada linha. Em vez de escrever uma linha de cada vez, vamos reescrever esse bloco em zigue zague, começando do canto superior esquerdo e descendo até o canto inferior direito. Isso pros zeros ficarem um atrás do outro.</p>
<p>Na transformação de cosseno a gente fez de propósito os coeficientes que virariam zero na divisão ficarem mais acumulados num canto. A ordem que se escreve os bits não é importante contanto que na hora de ler de volta seja na mesma ordem, claro. E de novo, até este ponto ainda não comprimimos nada, só fizemos preparativos. Lembra que partimos de um bloco de 8 por 8 pixels e chegamos no mesmo bloco de 8 por 8 pixels. Nada mudou ainda.</p>
<p>Depois de fazer o zigue zague olha o que aconteceu, temos um array onde os números mais pro fim tem sequências de zeros. Agora sim, vamos pra compressão. O primeiro algoritmo é mais intuitivo. Vamos pegar um outro exemplo com uma string aleatória com sequências repetidas, tipo um &ldquo;AABCDDDDEFFFFFGGGH&rdquo; que tá aqui do lado com 18 caracteres. Concorda que poderíamos reescrever por exemplo como &ldquo;A2B1D4E1F5G3H1&rdquo;. Ou seja, em vez de repetir F, 5 vezes, escrevemos F5 pra indicar que é pra repetir 5 vezes. Descemos de 18 caracteres e comprimimos pra 14, um ganho de 22%, mas isso porque essa string é curtinha, quanto maior for e quanto mais sequência repetidas tiver, mais comprimida fica.</p>
<p>Esse é um dos algoritmos mais simples e ingênuos de compressão lossless, ou seja, que não perde nenhuma informação, diferente de chroma subsampling que fizemos lá no começo. Da string comprimida podemos voltar pra original sem muito trabalho. Voltando pro nosso bloco de coeficientes divididos depois da quantização, aquelas sequências de zeros repetidos podemos fazer a mesma coisa e isso já vai nos dar um pequeno ganho.</p>
<p>Esse algoritmo se chama Run Length Encoding ou codificação de corrida de comprimento. O pior caso desse algoritmo obviamente é se a string original tiver alta entropia e quase nenhuma sequência repetida. Mas o próximo algoritmo é mais interessante e se aprende na ciência da computação. E começa criando uma tabela de frequência. No mesmo exemplo da string, vamos contando letra a letra e ordenando pela frequência vamos ter B 1, C 1, E 1, H 1, A 2, G 3, D 4, F 5</p>
<p>Agora vamos montar uma árvore binária de frequências. Começa pegando do começo dessa lista, O B e o C que só aparecem 1 vez cada e coloca embaixo de um nó que é a soma das frequências, no caso 2. Agora pegar o E e H que também só aparecem 1 vez e colocar embaixo de outro nó 2. Agora tem vários com frequência 2, meio que tanto faz, mas na ordem pegamos o nó 2 do E e H que acabamos de criar, o A que aparece 2 vezes no texto e colocamos embaixo de um nó 4 que é a soma das frequências.</p>
<p>Continuando, pegamos o nó 2 do B e C e a próxima frequência 3 da letra G e colocamos embaixo de um nó que a soma dá 5. Temos 2 nós 4, do A, E e H e do D, colocamos embaixo de um nó com soma 8. Depois pegamos a sub-árvore do B, C e G e o F que tem frequência 5 também e colocamos embaixo de outro nó com soma 10. E finalmente pegamos os nós resultantes 8 e 10 e colocamos embaixo da raíz 18, e formamos uma árvore binária de frequências. Agora pra que tivemos esse trabalhão todo?</p>
<p>Como que o computador enxerga aquela nossa string original &ldquo;AABCDDDD&rdquo; etc? Digamos que está representada como caracteres UTF-8. Lembra Unicode? Computadores não entendem letras, só números. Nós, programadores, que precisamos enxergar uma sequência de números e pedir pra ele desenhar ou processar alguma coisa. Sendo simplista, por convenção usamos uma tabelona gigante que é a UTF-16, que associa um número com até 2 elevado a 16 possíveis glifos, mais de 65 mil possíveis glifos.</p>
<p>Um glifo é basicamente o desenho de um caracter. UTF é uma tabela arbitrária que usamos por convenção e todo programa e sistema operacional traduz da mesma forma. O glifo da letra A maiúsculo em inglês é o ponto de código 0041 em hexadecimal. O glifo da letra B maiúscula é 0042 e assim por diante. Então, em hexadecimal o string AABC etc fica 4141 4243 e assim por diante. Mas lá embaixo, no nível do computador mesmo, ele enxerga em binário, zeros e uns, então fica um linguição tipo  0100 0001 0100 0001 0100 0010 e assim por diante.</p>
<p>No mínimo, o código que representa cada letra, vai ocupar 8 bits se o encoding for em UTF-8. Cada letra ocupa 1 byte portanto a string inteira ocupa 18 bytes. Mas e se eu esquecer UTF-8 e usar outra tabela com códigos que precisam de menos bits? É pra isso que criamos essa árvore binária de frequência. Ela que vai servir pra codificar o A, B, C etc com outro código diferente da UTF-8. Vamos ver como.</p>
<p>Começamos da raíz da árvore que é o nó 18 lá em cima e vamos descendo. Cada galho vai representar zero ou um. Se eu for pra esquerda vai ser zero, pra direita vai ser um. Então vamos lá, qual vai ser o código binário pro A? Em UTF-8 é 0100 0001, 8 bits. Vamos lá começando do nó 18, um galho pra esquerda, então 0. Mais um pra esquerda, 0 de novo. Esquerda, 0. Esquerda 0. Então o código pro A vai ser 0000. Logo de cara já economizamos metade do espaço.</p>
<p>Daí tem A de novo, então 0000. Agora o B, da raíz vai pra direita, então 1. Daí tem mais 3 galhos pra baixo pra esquerda, então 3 zeros e o código pro B vai ser 1000. O C mesma coisa, começa pra direita então 1, desce dois galhos pra esquerda, então 0 e 0, e vira pra direita no final então 1. Portanto o código do C vai ser 1001. Mas começa a ficar divertido agora, porque temos uma sequência de 4 Ds. O código do D é começando pra esquerda 0 e indo pra direita que é 1, portanto o código do D é 01. Repetido 4 vezes fica 01010101. 8 bits, pra 4 letras. Agora começaram a entender?</p>
<p>Antes esse D seria o hexadecimal UTF-8 0044 que em binário é 0100 0100, 8 bits. Como são 4 Ds tó esse trecho ocuparia 4 bytes. Mas nesse sistema de encoding da árvore de frequência codificamos o D somente com 2 bits. Todas os 4 Ds vão ocupar um único byte, então esse trecho ficou 4 vezes menor! E pra não ficar tedioso vou parar por aqui mas é pra fazer a mesma coisa letra a letra, usando a árvore como o sistema de codificação.</p>
<p>No final vai ficar esse binariozão. Olha como o F que repete 5 vezes também ficou curtíssimo com 2 bits cada letra. E não é por acaso que o D e o F, que são as letras que mais repetem, sejam representados só com 2 bits enquanto letras que não repetem tanto como o A ou C precisam de 4 bits. Porque a árvore foi ordenada e montada de tal forma que as letras mais comuns fiquem mais próximos da raíz da árvore, precisando de menos galhos pra chegar neles. O string original tinha 18 letras, portanto 18 bytes. Agora o binário comprimido ficou só com 60 bits que é 12 bytes, economizamos 6 bytes nessa brincadeira que é 2/3 do tamanho original.</p>
<p>0000 0000 1000 1001 01 01 01 01 0010 11 11 11 11 11 101 0011</p>
<p>Pra strings pequenas assim esse sistema não compensa porque pra conseguir descomprimir precisa também salvar a estrutura da árvore junto, e vai dar mais que os 18 bytes originais. Mas em textos mais longos isso pode diminuir tudo em até umas 5 vezes. Pensa um arquivo de código fonte, ou um JSON, que só de identação tem um monte de espaço em branco. Espaço em branco, tabulação, tudo é um caracter e tudo ocupa espaço, mesmo o que você não enxerga.</p>
<p>Com uma combinação de Run Length e esse sistema de árvore de frequência que se chama codificação de Huffman, dá pra comprimir bastante. Depende do texto, claro, alguns dá pra comprimir mais, outros menos. Huffman é um bom exemplo de porque todo programador precisa saber o que é uma árvore. E por isso eu fiz um vídeo só falando de árvores algum tempo atrás. Todo mundo pensa em tabelas como uma matriz, um array de array, mas uma tabela ordenada, de chaves e valores, costuma ser implementada como uma árvore.</p>
<p>Se estão curiosos em como descomprime, é fácil. Vamos lá, 0000 significa começar da raíz e descer pra esquerda 4 vezes, aí esbarra no A, então escreve A e volta pra raíz. Vem 0000 de novo, vai chegar no A de novo, concatena com o A anterior, volta pra raíz. Agora é 1000, direita e esquerda 3 vezes, esbarra no B, concatena. Volta pra raíz, 1001, direita, esquerda, esquerda, direita, esbarra no C, concatena. Volta pra raíz, 01, esquerda e direita e já esbarra no D, concatena. Volta pra raíz 01 01 01, esbarra no D 3 vezes. E vai fazendo assim até ter o texto original de volta. Viram como é super simples e fácil?</p>
<p>Se lembram porque estou falando de Run Length e Huffman? Porque voltando na explicação de imagem, fizemos a compressão de cores com chroma subsampling pra converter de RGB pra YUV 4:2:0, daí dividimos o linguição de bytes de cada canal em blocos de 8 por 8 bits e aplicamos o processamento de transformada discreta de cosseno ou DCT, depois a quantização pra simplificar os coeficientes.</p>
<p>Daí reescrevemos bloco a bloco em zigue zague pra forçar zeros repetidos mais próximos no fim de cada bloco. E agora? Uma sequência de zeros repetidos, é só aplicar Run Length pra diminuir o espaço que ocupam. Agora podemos aplicar Huffman. Criar a árvore de frequência dos números que mais se repetem perto da raíz e codificar como no exemplo da string.</p>
<p>Recapitulando, uma imagem de video de 4K seria 3240 vezes 2160 pixels, com cada pixel ocupando 3 bytes em RGB. Isso daria uma imagem crua, descomprimida, de uns 20 megabytes. Aaplicando chroma subsampling, transformada discreta de cosseno, quantização de qualidade 8, run-length e Huffman, podemos chegar à mesma imagem ocupando nada menos que 900 kilobytes, menos de 1 mega. Uma compressão de mais de 20 pra 1, 20 vezes menor que o original e que pra 99% das pessoas vai ser difícil de distinguir com o original. Nada mau né?</p>
<p>Esse processo todo está por trás do formato de imagem mais difundido do mundo que todo mundo, até quem não é programador usa todo dia, vocês acabaram de aprender como funciona o famoso JPEG, que é um sistema de compressão lossy, que perde informações do original, mas que em quantização lá perto de 8 pelo menos, a gente não vai perceber no dia a dia a menos que precise abrir no Photoshop pra editar. E agora você talvez comece a entender as razões das limitações do formato.</p>
<p>Como um JPEG vai cortando informação ao longo de todo esse processo, é impossível recuperar os detalhes do original. É um processo “lossy”. E cada vez que edita um JPEG e salva num novo, vai continuar perdendo mais informação. Por isso que não se deve editar um JPEG diversas vezes. Toda vez vai perdendo mais e mais definição. Por todo esse processo que imagens que tem traços muito bem definidos, como desenhos, quadrinhos, anime e coisas assim ficam meio soft e perdem a nitidez nas bordas. Esse sistema é melhor pra fotos de natureza, pessoas, que tem coisas como céu com muitos tons de azul. Mesmo desconstruindo o tanto que fizemos, é difícil alguém não treinado notar que mudou alguma coisa.</p>
<p>E por isso JPEGs de baixa qualidade, que o original já não era grande coisa e você ainda vai e manda salvar em qualidade baixa, tipo quantização 1, fica artefatos visíveis em formato de quadradinhos, como num grid, porque a gente divide a imagem em bloquinhos quadrados e processa um a um e depois concatena o resultado. Esse sistema dá resultados impressionantes de mais de 20 ou 30 vezes de compressão, por outro lado, quanto mais nítido, ou no outro espectro, quando mais borrado o original for, pior vai ser o resultado, especialmente em quantizações mais agressivas. Em resumo, JPEG é muito bom pra comprimir uma foto e guardar ou transmir. Mas é uma bosta pra editar. Por isso que fotógrafo profissional não tira fotos em JPEG e sim em RAW, que fica muito maior que os 20 mega que eu falei, porque guarda bem mais informação de cores e tudo mais pra possibilitar manipular e ajustar num Photoshop ou Lightroom depois.</p>
<p>O formato BMP que o Paint ou Painbrush do Windows originalmente salvava sempre gerava arquivos grandões, porque não comprime nada. Mais parecido com o formato RAW de câmeras, era o antigo formato TIFF. Ambos são parecidos com BMP ou bitmap no sentido que não joga informação fora, mas eles aplicam um mínimo de compressão lossless, ou seja, que não perde nenhuma informação. Já vou explicar compressão lossless.</p>
<p>Mas tem um formato que surgiu da CompuServe, um dos primeiros provedores de internet dos anos 90, mesma época da America Onine, ou AOL. Naquela época estávamos engatinhando, era final dos anos 80. E começou a ficar óbvio que queríamos trafegar imagens, seja em BBS ou nesse troço novo chamado de Web. Mas não dava pra ser em Bitmap descomprimido. Mesmo TIFF era grandão. Daí eles inventaram o famigerado GIF ou JIF, seja lá como prefere chamar. A essa altura já nem me importo mais qual é o certo. É aquele formato de imagem de memes animados que todo mundo manda no zap.</p>
<p>O GIF é um patinho feio que continuamos usando até hoje por causa de funcionalidades que poucos tem. Em particular animações bem leves. Pra começar ele limita a quantidade de cores na imagem. Em vez de cada pixel ser representado por 24 bits de cor, limitaram a no máximo 8 bits, o que diminui o espaço de cores de mais de 16 milhões pra meras 256 cores. Sim, só 256. Cada componente do RGB tem 8 bits, mas no GIF tem que dar um jeito e enfiar tudo em só 8 bits no total. E obviamente não dá, então tem que jogar fora quase todas as cores e se virar com quase nada. Eu lembro que parte do trabalho em agência naquela época era otimizar as cores dos GIFs.</p>
<p>O processo não é muito diferente de fazer pixel art. Dá pra automatizar o processo de jogar cores fora mas às vezes o algoritmo escolhe um tom que não fica muito bom, daí temos que ajustar manualmente. Em cima disso o GIF ainda tinha duas funcionalidades que são os diferenciais até hoje. A primeira era poder escolher uma cor dessa paleta pra ser transparente. Como não era uma canal alpha de verdade, a transparência era bem abrupta e feia, só em cima de uma única cor. Mas pra memes tá mais que bom.</p>
<p>E a segunda funcionalidade era permitir ter mais de uma imagem dentro do mesmo arquivo, com a possibilidade de tocar animado, que é a coisa mais importante em todo meme de hoje em dia. O problema disso era a paleta de cores. Lembra que pode ter no máximo 256 cores? Pois é, e as cores valem pra todas as imagens da animação, por isso é inteligente não fazer animações que tenham quadros muito diferentes um do outro.</p>
<p>Depois de comprimir jogando milhões de cores fora, o segundo passo era comprimir reduzindo sequências de bytes repetidos, meio parecido com a idéia do Run-Length que falei antes, mas bem mais agressivo. Pra isso GIF usa um algoritmo de compressão chamado LZW ou Lempel-Ziv-Weich. Tecnicamente o algoritmo original foi criado por Abraham Lempel e Jacob Ziv coincidentemente no ano que eu nasci, em 1977. Por isso que o algoritmo original se chama LZ77 que você vai esbarrar bastante se começar a pesquisar sobre compressão. Também vai esbarrar no LZ78 que é uma otimização da fase de descompressão. O LZW é uma variação desse algoritmo criado por Terry Weich em 1983 mas já volto a falar dele. Vamos focar só no LZ77.</p>
<p>Vale eu explicar a idéia geral com um exemplo. Vamos pegar outro string, como esse &ldquo;um tigre, dois tigres, três tigres&rdquo;. Sem descer em detalhes temos repetido 3 vezes o trecho &ldquo;tigre&rdquo;. E se a gente substituir o 2o e 3o tigre com um ponteiro dizendo quantas letras pra trás precisa ir pra achar o 1o tigre e quantas letras queremos pegar? Então ficaria algo como &ldquo;um tigre, dois &lt;12,5&gt;, três &lt;25,5&gt;&rdquo;. Esse 12,5 quer dizer, ande 12 letras pra trás e leia 5 letras. E 25,5 mesma coisa, ande 25 letras pra trás e leia 5 letras, ambos vão cair na primeira palavra “tigre” e com isso dá pra remontar a frase. Se eu codificar esses ponteiros com 4 bits cada valor, em 1 byte dá pra guardar.</p>
<p>O string original tem 33 letras, mas substituindo onde repete por esses ponteiros daria pra reduzir de cara pra 23 bytes, mais 2 bytes pra cada ponteiro, ou seja, 25 bytes. Mesmo nesse esquema simplificado já comprimimos em quase 25%, 1 quarto de economia. Imagine que num texto muito maior vai ter muito mais repetições e por isso dá pra comprimir bem. Se só o esquema de Huffman comprime umas 5 vezes, eu diria que o mesmo texto nesse esquema de ponteiros daria pra comprimir na faixa de 10 vezes. No geral acho que a eficiência é pelo menos 2 vezes maior que Huffman. Por isso vale a pena entender mais.</p>
<p>Imagina num texto longo fazer esse esquema de ponteiros. Pensa um livro por exemplo, centenas de páginas de texto. Se estivermos lá na última página e precisar fazer um ponteiro pra uma palavra na primeira página. Pra poder apontar tão longe significa que precisaríamos manter o livro inteiro em memória, megabytes de dados ou mais. Seria um puta desperdício de memória. E quando o algoritmo foi criado, no fim dos anos 70, sabemos que memória era hiper cara. Pensa que os melhores microcomputadores do começo dos anos 80 male male tinham 32 kilobytes de RAM. Então eu só posso manter pedaços do texto em memória enquanto vou descomprimindo, e por isso esses ponteiros não podem apontar pra tão longe no começo do texto.</p>
<p>Naquela época se reservava tipo 2 kilobytes, talvez 4. Esse bloco de buffer é o que se chama de sliding window ou janela deslizante, porque é como só desse pra ler o trecho do texto onde está essa janela. E os ponteiros só podiam referenciar dentro dessa janela, daí dá pra manter ponteiros usando poucos bits também. Quanto maior o sliding window, maiores as possibilidades de comprimir mais, mas mais RAM vai precisar. Então é um trade off. Hoje em dia RAM é barata, então dá pra ter sliding windows na faixa de megabytes. Porém quanto maior for o sliding window, maior tem que ser os ponteiros, daí ocupa mais espaço no total e comprime um pouco menos. É um balanço.</p>
<p>O primeiro grande sucesso que me lembro desse algoritmo surgiu na era dos BBS nos anos 80 pelo Phil Katz que inventou o formato ZIP, que usa esse algoritmo LZ77. Foi o famoso PKZIP que tinha os utilitário pkzip pra comprimir e pkunzip pra descomprimir. Quem daquela época não usou? Com a adoção pela Microsoft já naquela época o formato zip se popularizou, e embora outras coisas melhores tenham surgido, meio que virou o padrão até hoje. Com o tempo ganhou funcionalidades como encriptação, os famosos zips com senha que todo mundo que manja tem medo quando baixa, porque pode ser a pornografia que queria ou um malware.</p>
<p>Não confundir porque existem diversos formatos de compressão como zip, arj, rar, 7zip e outros que usam variações do algoritmo Lempel-Ziv. Uma coisa é o formato de arquivo, a estrutura de dados e metadados, outra coisa são os algoritmos que usam por dentro. E cada um usa estratégias diferentes pra tentar ultrapassar os concorrentes. Nos anos 80 mesmo surgiram diversas melhorias em cima do LZ77 como o LZSS ou Lempel-Ziv-Storer-Szymanski de 1982 criado por James Storer e Thomas Szymanski usado no bom e velho RAR. Lembram? WinRAR? O shareware que ninguém nunca pagou. Quem nunca baixou pirataria quebrada em vários arquivos de RAR?</p>
<p>No mundo Windows mais recente se popularizou o formato e ferramenta 7-zip, que é versátil e consegue lidar com outros formatos como o RAR ou ZIP originais, mas tem um formato próprio que usa outras variações do LZ77 como o Lempel-Ziv-Markov chain ou LZMA e LZMA2 criado pelo russo Igor Pavlov no fim dos anos 90. O Rar foi criado por outro russo, Eugene Roschal no começo dos anos 90. Naquela época pelo menos a gente falava que não podia subestimar um russo quando a questão era compressão de dados, porque entre RAR e 7zip eles contribuíram com alguns dos melhores formatos de compressão de arquivos.</p>
<p>Muitos desses formatos usam uma mistura de Huffman pra substituir letras ou palavras mais frequentemente usados por símbolos guardados num dicionário, aquela árvore binária de frequências e uma variação de Lempel-Ziv como LZSS ou LZMA2 pra substituir repetições por ponteiros dentro de um sliding window. E como cada um ajusta essas diversas combinações de dicionários, sliding windows, tamanho de ponteiros e tudo mais é que varia tanto quanto conseguem comprimir, quanto de CPU e RAM vão gastar no processo. Mas em geral, quanto menor consegue fazer o arquivo ficar, mais processamento e memória vai gastar.</p>
<p>Desenvolvedores web devem estão mais acostumados com o formato gzip, que é do começo dos anos 90 e é o mais usado em servidores web. Caso não saibam, toda página web que vocês navegam provavelmente vem compactada em gzip. Se não vem, deveria. O gzip usa o algoritmo deflate, que foi criado pelo Phil Katz pro PKZIP original. A patente dele já expirou e por isso podemos usar hoje de graça. Deflate e gzip são muito comuns no mundo Linux e é uma combinação do LZ77 e Huffman como expliquei. Aliás, toda linguagem de programação ou já tem ou tem bibliotecas pra todos esses algoritmos, deflate, huffman e tudo mais. Pesquisem.</p>
<p>Huffman, Lempel e Ziv são as fundações pra tudo que é compressão lossless, ou seja, que não jogamos fora nada dos dados originais e quando descomprime volta exatamente como era antes, diferente do esquema de JPEG em imagens. E falando em JPEG, o objetivo dessa longa tangente de história foi só pra poder voltar a falar do seu GIF de memes. Como falei antes, criado pela Compuserve em cima de outra variação do LZ77, o LZW que é Lempel-Ziv-Weich, do Terry Weich. Estamos falando de uma era antes da idéia de software livre. E naquela época patentear software era mais importante ainda, ninguém sabia como as coisas iam evoluir.</p>
<p>O erro foi que a Compuserve criou o GIF usando o LZW do Weich e deu certo, o GIF começou a se popularizar, especialmente quando a Web comercial começou a crescer e surgiram navegadores como o antigo Netscape, que adotou o GIF com um dos formatos de imagem suportado. Antes disso, no meio dos anos 80 o Weich vendeu as patentes pra Sperry Corporation, que fez merge com a antiga Burroughs, famosa por mainframes e pelo UNIVAC e que depois se tornou a Unisys, que existe até hoje. Pois bem, a Unisys ficou sabendo desse tal de GIF usando as patentes deles sem pagar nada e em 1987 começou a negociar com a Compuserve.</p>
<p>Ninguém tava muito ciente disso e todo mundo continuou usando. Aí em 1994 eles anunciam que esperam que todo mundo que usasse e distribuisse GIFs precisaria pagar licenças. Imagina se tivesse Twitter em 1994, todo mundo ia ficar putaço e ia cair matando cancelando a Unisys. Como assim querem taxar meus memes?? Aí em 1995 o Thomas Boutell anunciou na usenet, que era tipo nosso Reddit da época, uma proposta pra resolver isso. Que seria todo mundo largar saporra de GIF e trabalhar num formato melhor e mais moderno. E daí nasceria o PNG ou Ping.</p>
<p>Deu tão certo que já em 1996 a W3C, que é quem define os padrões da Web, adotou e oficializou o PNG. Mas então significa que todo mundo distribuindo GIFs de memes hoje tá infringindo patente da Unisys? Felizmente não, porque a patente já expirou lá por 2004 e a gente voltou a usar GIFs porque apesar do PNG ser infinitamente superior, ele não suportava animações. A única função que GIF tem é pra memes animados. Até existe um formato de PNG animado que é o APNG mas demorou muito pros navegadores adotarem. Ele já foi criado tarde, pela Mozilla, em 2008. Mas Chromium só adotou em 2017 e por tabela navegadores baseados em Chromium como Microsoft Edge só vieram a adotar depois. Você sabia que tinha APNG? Pois é, nem eu.</p>
<p>PNG é bem versátil, suporta imagens de poucas cores como GIF, só com 8-bits de cor, mas dá pra subir pra imagens pra 24 bits ou 32 bits. Também suporta um canal de alpha pra transparência de qualidade infinitamente superior ao GIF e por isso é muito adequado pra desenvolvimento Web. E ele usa um sistema de otimização da paleta de cores pra facilitar ter sequências de repetições, é tipo filtros que não degradam a qualidade da imagem mas facilita depois usar o algoritmo Deflate. Lembra? Baseado em Huffman e LZ77, que o gzip usa? PNG é comprimido com Deflate, portanto, diferente de JPEG, ele é lossless.</p>
<p>Depois do PNG surgiram outros formatos como o WebP do Google. Mas nem vou falar do WebP, WebM, codecs como VP8 ou VP9 porque apesar de bons não se tornaram populares. Aliás, toda vez que esbarro numa porcaria de WebP quero xingar porque a adoção é tão baixa que pro Photoshop abrir eu preciso instalar um plugin por fora. Um JPEG faz compressão na casa de 20 pra 1. Um PNG faz compressão na casa de 4 pra um. São ordens de grandeza menos. Mas um WebP é só uns 25% melhor em compressão que um PNG. Eu sei que tem outras vantagens, mas a diferença é muito pequena pra trocar tudo de PNG pra WebP e por isso continuo usando PNG pra tudo. Se eu quero uma foto altamente comprimida, vou usar JPEG. Se quero uma foto razoavelmente comprimida mas mantendo os detalhes do original, vou usar PNG e ponto.</p>
<p>Vamos voltar pro assunto principal. Lembram o objetivo? Eu quero assistir um filme de 2h em 4K na minha TV nova. Mas o arquivo não comprimido de video, a gente fez as contas, e ia precisar de uns 5 teras. O máximo que conseguimos fazer foi converter de RGB pra YUV 4:2:0, que joga fora informações do canal de chroma que a gente não percebe, que é o chroma subsampling. Mas mesmo assim o tamanho do arquivo cai só pra 3 teras, que continua sendo coisa pra caramba.</p>
<p>O que é um video. É basicamente uma sequência de imagens. Por que não comprimir cada imagem em PNG então? Já cairia o tamanho de tudo 4 vezes. Não muda muita coisa porque isso só diminui o video de 5 pra 1 terabyte. 1 tera é o tamanho total dos HDs de metade de vocês aí assistindo. Eu chuto que a maioria deve estar com HDs de no máximo uns 512 giga. Ou seja, tá chegando perto, mas ainda não dá. Então a próxima escolha óbvia é comprimir cada quadro do frame em JPEG, e aí vai ficar 20 vezes menor né? Sim, isso diminui os 5 teras originais pra 256 gigabytes!</p>
<p>Caraca olha só! Significa que se eu comprimir em JPEG já cabe 2 filmes no HD de vocês. Sucesso. Fim do episódio, deixem seu like e assinem o canal! Tô zoando. 256 gigas é um puta avanço, mas ainda não dá. Com conexão de 50 megabits que é o que a maioria de vocês aí deve ter, ia levar nada menos que 12 horas pra baixar. Não rola fazer streaming disso porque assistir um filme de 2 horas precisando de 12 horas pra baixar, só se você assistisse beeeeem em slow motion.</p>
<p>Falando em velocidade de internet, esse é o requisito pra streaming. 50 megabits é por volta de 6 megabytes por segundo, no máximo. Pra eu conseguir assistir um filme de 2h nessa velocidade, considerando uma conexão perfeita, que não engasga nem desconecta, ou seja, sem considerar buffer, significa que o tamanho máximo do arquivo do filme teria que ser menos 6 megabytes por segundo vezes 7200 segundos, ou seja, 42 gigas. Esse é o máximo, na realidade tem que ser menos que isso. Então precisamos dar um jeito de pegar o video compactado com JPEG que deu 256 gigas e comprimir 6 vezes ainda.</p>
<p>Aliás, realmente existe formato de video que comprime os quadros em JPEG. O Motion JPEG ou M-JPEG. É um dos componentes que depois se torna o que você conhece como MPEG1 que se usava nos antigos Video-CD, ou MPEG2 que se usava nos antigos DVDs. Ou o MPEG4 também chamado de AVC, Advanced Video Coding, que você conhece como H.264 que é o codec que a maioria dos videos aqui do YouTube, Netflix e outros usam. Na verdade a gente tá migrando pro H.265 mas já falo dele. Aliás, nomenclatura de tecnologias de imagens, áudio, video, é uma bosta. Tem três nomes diferentes pra mesma coisa, com um monte de versões diferentes, diferentes empresas usando nomes diferentes. É uma confusão mesmo. Se esses nomes te confundem, você não tá sozinho. Mas vamos nos ater ao básico.</p>
<p>Não necessariamente a compressão é o JPEG igualzinho das suas fotos mas sim um algoritmo de compressão baseado em DCT, transformada discreta de cosseno, então seria tipo variações das técnicas usadas pelo JPEG. Mas como já vimos, precisamos de mais que isso. Como estamos chegando no final, não vou entediar demais com todos os detalhes, mas o pulo do gato é simples. Presta atenção aqui em mim. Como são meus videos? É esse meu cenário aqui atrás que fica imóvel e não muda absolutamente nada do começo ao fim do video, e sou eu bobão aqui na frente, falando e se mexendo que nem um idiota. Concorda que precisa só de um quadro com a imagem inteira e depois pode recortar só eu aqui na frente pro resto do video?</p>
<p>O que é um video? É um conjunto de imagens. A grosso modo uma câmera de video que nem essa aqui na minha frente tá tirando fotos de mim, 60 vezes por segundo. Num video de 1 hora tem 216 mil fotos. Toda a parte da imagem que é o cenário tá repetido em todas essas fotos, 216 mil vezes. Se eu arrancar fora esse cenário de todas as fotos, só de bater o olho você pode chutar que daria pra jogar fora pelo menos metade da imagem, concorda? Lembra como é mais fácil comprimir sequências repetidas de zeros? Olha quantos zeros tem aqui ao redor de mim agora.</p>
<p>É exatamente isso que um codec como o H.264 vai fazer. Ele vai primeiro gravar um interframe ou keyframe que é uma foto completa, como essa. Daí os próximos frames vai gravando o que se chama de intraframes, que são deltas. Sabe commit de Git, que é só o trecho que você mudou no código e não o arquivo original inteiro? Mesmo conceito. Então vai ter um interframe e vários intraframes na sequência com deltas. O correto da imagem inteira é falar interframe, mas vou falar keyframe pra ficar mais fácil de distinguir de intraframe.</p>
<p>Eu não lembro se a quantidade de intraframes era fixo ou se o codec detecta quando teve tanta mudança que é melhor pegar um keyframe inteiro de novo. Dependendo do codec ele pode escolher ser conservador e pelo menos o keyframe comprimir usando um algoritmo lossless como equivalente de PNG pra aumentar a qualidade e os intraframes usando um derivado de DCT, como um JPEG. Indo direto aos finalmentes, quanto que aqueles 5 terabytes consegue diminuir somando um esquema de JPEG com um esquema de inter e intrarames?</p>
<p>Normalmente um filme por volta de 2h, em H.264 que é o codec mais usado atualmente, numa qualidade boa, próxima de Blu-Ray UHD que é a versão 4K, seria por volta de 20 a 25 gigabytes. Naquela mesma conexão de 6 mega por segundo daria pra baixar esse filme em aproximadamente 1 hora e 18 minutos. Ou seja, dá pra baixar todos os bits do video em menos tempo que a duração do video, que é o pré-requisito pra ser possível fazer streaming, que é poder começar a assistir enquanto o video vai baixando no fundo. Ou seja, atingimos nosso objetivo de conseguir assistir o filme via streaming na TV nova 4K!</p>
<p>E é exatamente isso que acontece no YouTube ou Netflix. Os videos em 4K de 30 quadros por segundo são encodados em H.264 com um bitrate de 35 a 45 megabits por segundo, dependendo do video. Lembrando que o tamanho final de um video não é linear com a duração do video. Depende do tipo de conteúdo. Desenho animado, por exemplo, vai ser mais fácil de compactar do que um filme de ação com câmera que balança o tempo todo, famoso shaky cam, como nos filmes do Jason Bourne, porque fica bem mais difícil fazer delta intraframes. A qualidade da imagem também importa porque senão fica qualidade ruim quando faz color downsampling ou DCT. Por isso que vários filmes diferentes de 2h vão dar tamanhos de arquivos bem diferentes um do outro.</p>
<p>Tem bem mais otimizações que codecs modernos fazem. Eles avaliam movimentos rápidos entre frames pra tentar normalizar, diferente de cenas que são só pessoas paradas conversando por exemplo. Tem técnicas como compensação de movimento, que existe desde o MPEG2 dos DVDs. O H.264 ou AVC que é o codec de video mais difundido pra consumo foi lançado oficialmente por volta de 2003 e levou desde 1998 até chegar no formato final. E mesmo depois saíram revisões do formato.</p>
<p>E já em 2004 começou os estudos pro sucessor que hoje conhecemos como H.265 ou HEVC de High Efficiency Video Coding que foi lançado por volta de 2013 e só em anos recentes a adoção ficou significativa. Na prática, se você edita videos, exporte em HEVC que os arquivos tendem a ficar quase 50% menores e com qualidade pelo menos similar ao H.264, mas pra isso ele gasta bem mais processamento do que o H.264. Lembram o que eu expliquei antes que podemos ajustar o algoritmo pra criar arquivos menores ao custo de mais processamento e mais memória?</p>
<p>Falando em quem edita videos, H.264 e H.265 são o que se chama de codecs de consumo. Assim como eu falei que JPEG é ótimo pra guardar suas fotos mas não pra editar, esses codecs de consumo é mesma coisa, ótimo pra poder baixar rápido e assistir, mas péssimos pra editar. Primeiro porque assim como JPEG eles fazem chroma downsampling, porque pra TV YUV 4:2:0 é mais que suficiente e você não vai notar que jogamos fora metade da informação de chroma. Mas pra editar faz diferença, porque agora não temos cores suficientes pra trabalhar.</p>
<p>Fotógrafos profissionais devem tirar fotos no formato RAW que usa compressão lossless, por causa do algoritmo deflate. Daí edita no Lightroom da vida e no final exporta em JPEG pra postar no Instagram. Mesma coisa video, o formato correto pra gravar nunca deve ser H.264 ou H.265 e sim Apple ProRes ou DNxHR ou CineForm ou outros formatos proprietários como o REDCODE, se usa câmeras da RED. Todos esses formatos são similares a zip, 7zip, rar, compressão lossless, que não joga informação fora. Eles tem diferentes taxas de compressão caso queira sacrificar qualidade de video por espaço de armazenamento.</p>
<p>Por exemplo, eu gravo meus videos aqui em DNxHR. E gravo no perfil que comprime mais e mesmo assim dá 1 gigabyte por minuto de video. Então se eu gravar 2 horas de video vai dar pelo menos 120 gigabytes. E se gravar com menos compressão pode chegar lá no 1 terabyte fácil. Quem edita videos profissionalmente precisa disso. Num H.264 final vai estar em YUV 4:2:0, mas no meu video gravado em DNxHR vai estar em YUV 4:2:2, ou seja, mais informação de cor. E pra ser agressivo era só gravar em ProRes 4:4:4 que é zero perda de informação de chroma. Agora você sabe o que significa ProRes 4444 versus ProRes 422.</p>
<p>Além disso H.264 usa o esquema de inter e intraframes. Acho que todo mundo já viu como se edita videos? Num programa como Adobe Premiere ou DaVinci Resolve. São programas de uma categoria que se chama NLE ou Non Linear Editing, edição não linear, ou seja, que eu posso editar qualquer ponto do video que quiser. Só que toda vez que eu peço um frame pra editar, só vai ter o intraframe, que é o delta, a imagem incompleta. Então precisa voltar alguns frames pra trás e localizar o keyframe completo, mesclar os dois e aí eu tenho a imagem inteira. Imagina navegar num clip de video pra frente e pra trás e toda horas precisar processar frames assim.</p>
<p>Com um codec profissional como o Apple ProRes ou DNxHR ele vai ter só keyframes. Então esses codecs são mais parecidos com o M-JPEG original, o Motion JPEG onde todo frame é um JPEG completo, sem deltas. Por isso, apesar do tamanho do arquivo de video ser ordens de grandeza maior, pra editar é muito mais performático, porque não precisa reconstruir os frames o tempo todo, eles já estão inteiros, é só ler. Um H.264 atrapalha pra editar e exige mais processamento e mais memória da máquina, por isso não compensa. Espaço em disco é barato, melhor plugar um SSD externo via Thunderbolt. Lembra dos meus videos explicando NAS e armazenamento?</p>
<p>Além de tudo isso que expliquei, nem toquei no assunto de áudio direito, mas assim como imagem crua de alta qualidade de câmera se usa o formato RAW, que comprime de forma lossless com alguma variação de LZ77, assim como video tem formato lossless como Apple ProRES, áudio também tem formato crú comprimido lossless como o FLAC ou os antigos WAV e AIFF. Mas também assim como imagem tem formato lossy de consumo como JPEG, video tem formato de consumo H.265, áudio tem alta compressão lossy com MP3 ou AAC, Advanced Audio Coding, que é o que costuma ir com video H.265 ou Blu-Ray.</p>
<p>No final os conceitos básicos são os mesmos: não tem como guardar a informação analógica original 100%, por isso pegamos samples, como pixels no caso de imagens. Quadros por segundo no caso de video. E sample por segundo no caso de áudio. Ajustamos resolução, frequência, e fazemos transformações pra simplificar os dados quando queremos alta compressão. Ou só tentamos reduzir repetições se não queremos perder informação.</p>
<p>Tem várias outras coisas sobre compressão que nem mencionei, por exemplo, sistemas de arquivo que suportam compressão. No Windows com NTFS é possível ligar compressão e todos os arquivos no seu sistema vão ser comprimidos. Significa que toda vez que salvar vai levar um tempo a mais pra comprimir, e toda vez que tentar ler um arquivo vai levar outro tempinho pra descomprimir, e isso vai ser o tempo todo. Não sei quanto de overhead isso adiciona, até imagino que é rápido o suficiente pra não se notar na maior parte do tempo, mas considerando que espaço em disco hoje é super barato, não acho que compensa fazer isso. É muito mais inteligente comprar um HD externo USB barato e jogar o que não precisa pra lá em vez de deixar seu sistema inteiro lento o tempo todo por conta de poucos megabytes economizados. E ele provavelmente vai usar o equivalente de um zip na menor compressão pra não demorar demais, então não comprime nem de perto tanto quanto um zip de verdade.</p>
<p>Enfim, o objetivo do video de hoje era usar a desculpa de video pra explicar o básico de tudo que existe sobre compressão de dados, incluindo as peças básicas mais fáceis que você pode treinar em qualquer linguagem de programação como Run-Length, Huffman, Lempel-Ziv, pra comprimir textos e depois ficar curioso pra aprender mais sobre séries de Fourier e transformada discreta de cossenos que, como vimos, você usa diariamente e nem sabia disso. Agora que sabe, se é de ciências da computação, não custa nada ir perguntar pro seu professor de algoritmos ou pesquisar por conta própria. Espero que isso tenha aumentado sua percepção do que acontece por trás dos panos das atividades mais simples que fazemos no dia a dia, como assistir este video no YouTube. Se ficaram com dúvidas mandem nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e compartilhem o video pra ajudar o canal. A gente se vê, até mais.</p>
]]></content:encoded><category>audio-e-video</category><category>algoritmos-e-estruturas-de-dados</category><category>akitando</category></item><item><title>[Akitando] #102 - Entendendo Sistemas de Arquivos: FAT</title><link>https://akitaonrails.github.io/2021/07/20/akitando-102-entendendo-sistemas-de-arquivos-fat/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/07/20/akitando-102-entendendo-sistemas-de-arquivos-fat/</guid><pubDate>Tue, 20 Jul 2021 11:37:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/6YQve3KUqJs"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Como seus programas sabem onde encontrar arquivos no HD? Como diretórios e arquivos são organizados?&lt;/p&gt;
&lt;h3&gt;Conteúdo&lt;span class="hx:absolute hx:-mt-20" id="conteúdo"&gt;&lt;/span&gt;
&lt;a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;00:47 - Revisando sobre Cartuchos&lt;/li&gt;
&lt;li&gt;03:46 - Sprites em Arquivos?&lt;/li&gt;
&lt;li&gt;06:24 - Primeiros Sistemas de Arquivos&lt;/li&gt;
&lt;li&gt;09:12 - Anos 80: Tudo incompatível&lt;/li&gt;
&lt;li&gt;09:34 - Revisando MBR&lt;/li&gt;
&lt;li&gt;12:55 - FAT12&lt;/li&gt;
&lt;li&gt;15:42 - FAT16&lt;/li&gt;
&lt;li&gt;16:37 - Windows 95 - Nomes Longos&lt;/li&gt;
&lt;li&gt;17:48 - Introdução a Bit Vector&lt;/li&gt;
&lt;li&gt;19:28 - VFAT&lt;/li&gt;
&lt;li&gt;21:09 - FAT32&lt;/li&gt;
&lt;li&gt;22:03 - Microsoft e IBM&lt;/li&gt;
&lt;li&gt;23:23 - IDE, SATA, AHCI, etc&lt;/li&gt;
&lt;li&gt;25:15 - HPFS ao NTFS e HFS&lt;/li&gt;
&lt;li&gt;25:53 - B+Tree&lt;/li&gt;
&lt;li&gt;27:28 - Resource Fork e Alternate Streams&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Links&lt;span class="hx:absolute hx:-mt-20" id="links"&gt;&lt;/span&gt;
&lt;a href="#links" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Design of the FAT file system (&lt;a href="https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system"target="_blank" rel="noopener"&gt;https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;A Disk Editor View of the NTFS Boot Sector and &amp;ldquo;Bootstrap Code&amp;rdquo; for Windows™ 2000 and XP (&lt;a href="https://thestarman.pcministry.com/asm/mbr/NTFSbrHexEd.htm"target="_blank" rel="noopener"&gt;https://thestarman.pcministry.com/asm/mbr/NTFSbrHexEd.htm&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;FAT (&lt;a href="https://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html"target="_blank" rel="noopener"&gt;https://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;FAT (&lt;a href="https://wiki.osdev.org/FAT"target="_blank" rel="noopener"&gt;https://wiki.osdev.org/FAT&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SCRIPT&lt;span class="hx:absolute hx:-mt-20" id="script"&gt;&lt;/span&gt;
&lt;a href="#script" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Olá pessoal Fabio Akita&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/6YQve3KUqJs"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Como seus programas sabem onde encontrar arquivos no HD? Como diretórios e arquivos são organizados?</p>
<h3>Conteúdo<span class="hx:absolute hx:-mt-20" id="conteúdo"></span>
    <a href="#conte%c3%bado" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>00:00 - Intro</li>
<li>00:47 - Revisando sobre Cartuchos</li>
<li>03:46 - Sprites em Arquivos?</li>
<li>06:24 - Primeiros Sistemas de Arquivos</li>
<li>09:12 - Anos 80: Tudo incompatível</li>
<li>09:34 - Revisando MBR</li>
<li>12:55 - FAT12</li>
<li>15:42 - FAT16</li>
<li>16:37 - Windows 95 - Nomes Longos</li>
<li>17:48 - Introdução a Bit Vector</li>
<li>19:28 - VFAT</li>
<li>21:09 - FAT32</li>
<li>22:03 - Microsoft e IBM</li>
<li>23:23 - IDE, SATA, AHCI, etc</li>
<li>25:15 - HPFS ao NTFS e HFS</li>
<li>25:53 - B+Tree</li>
<li>27:28 - Resource Fork e Alternate Streams</li>
</ul>
<h3>Links<span class="hx:absolute hx:-mt-20" id="links"></span>
    <a href="#links" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><ul>
<li>Design of the FAT file system (<a href="https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system"target="_blank" rel="noopener">https://en.wikipedia.org/wiki/Design_of_the_FAT_file_system</a>)</li>
<li>A Disk Editor View of the NTFS Boot Sector and &ldquo;Bootstrap Code&rdquo; for Windows™ 2000 and XP (<a href="https://thestarman.pcministry.com/asm/mbr/NTFSbrHexEd.htm"target="_blank" rel="noopener">https://thestarman.pcministry.com/asm/mbr/NTFSbrHexEd.htm</a>)</li>
<li>FAT (<a href="https://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html"target="_blank" rel="noopener">https://www.win.tue.nl/~aeb/linux/fs/fat/fat-1.html</a>)</li>
<li>FAT (<a href="https://wiki.osdev.org/FAT"target="_blank" rel="noopener">https://wiki.osdev.org/FAT</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal Fabio Akita</p>
<p>Depois dos últimos episódios espero que vocês tenham entendido mais sobre como HDs são organizados em partições e volumes e como seu computador boota e acha o sistema operacional instalado no HD. Hoje quero explicar o que tem dentro dos volumes do HD: os sistemas de arquivo ou filesystems.</p>
<p>Vou começar contando um pouco o que me lembro. De como a vida era difícil nos PC dos anos 80 e 90 com o maldito filesystem FAT, que corrompia fácil. Apesar de muita gente não gostar, o NTFS do Windows NT foi um adianto de vida enorme. No Linux a gente tinha a série ext de filesystems. Nos Macs tinha HFS. Mas qual a diferença?</p>
<p>(&hellip;)</p>
<p>Antes de começar o assunto eu queria voltar de novo alguns episódios pra trás. Vocês já devem ter notado que fico insistindo nos videos de introdução a computação com videogames e mencionar cartuchos. Quando falei de cartuchos pela primeira vez foi só pra chegar nessa introdução de hoje.</p>
<p>Vocês lembram que nos chips da ROM a gente guarda as instruções do jogo em si mas numa região especial tem todos os assets do jogo, as imagens ou tiles, que formam os sprites dos personagens, por exemplo. O intervalo de endereços que definem a área CHR, que provavelmente quer dizer characters. Sem mappers só tem um intervalo pequeno de 8 kilobytes do endereço 0000 até 1FFF.</p>
<p>Sprites, como do Super Mario, são formados por tiles ou azulejos. Quatro tiles formam o Super Mario. Cada tile com 8 por 8 pixels. Então o Mario inteiro são 16 por 16 pixels. Vamos fazer umas continhas de padeiro. Sem nenhum mapper, pode ter no máximo 8 kilobytes ou 8 mil cento e noventa e dois bytes. Acabei de falar que cada tile tem 8 por 8 pixels. No Super Mario conseguiram entuchar quinhentos e doze tiles.</p>
<p>Portanto, cada pixel só pode ser representado por 2  bits de cor. E pra um NES é mais que suficiente. Hoje em dia, numa imagem normal, se representa cada pixel com pelo menos 24-bits, 8-bits pra cada cor. Se você já mexeu com HTML e CSS, já deve ter escrito uma cor em 24-bits.</p>
<p>Por exemplo FFFFFF é branco. O primeiro FF é o hexadecimal de 256, e representa vermelho. O segundo FF é pra verde e o terceiro é pra azul. Os três no valor máximo de 8-bits que é FFFFFF, vai dar branco. FF0000 significa vermelho no máximo e zero pra verde e azul.</p>
<p>Hoje em dia  tem bem mais cores que isso, em vez de 24-bits podemos usar 30 bits, onde cada uma das 3 cores básicas pode ser representada com 10-bits. Por isso, quando você vê um monitor ou TV moderna dizendo HDR-10 é High Dynamic Range com cores de 10-bits. Mas cores é assunto pra outro dia. O problema é o seguinte: se um tile de nintendinho fosse representado com esse tanto de cor, 30-bits, um tile de 8 por 8 pixels seria 8 vezes 8 vezes 30, ou seja mil noventos e vinte bits. Divide por 8 pra ter em bytes e isso seria duzentos e quarenta bytes.</p>
<p>Só que aí, nos nossos pobres 8 kilobytes de ROM no cartucho, só caberia trinta e quatro tiles. Pra fazer caber quinhentos e doze, o máximo que podemos usar pra cores são 2 bits, meio byte. E por isso em videogame antigo tinha uma outra estrutura de dados que são as paletas de cores. Note no debugger do emulador Mesen que embaixo do zoom do tile tem uma estrutura de 4 cores, essa é a paleta atual.</p>
<p>A gente pode mudar a paleta e as cores dos tiles vão mudando. É assim que se consegue reusar os tiles que formam a nuvem e tem cores como azul e branco e pra fazer o arbusto, basta trocar a paleta pra verde. Olhem aqui no nosso debuger como esse tile tá verde, mas quando aparece a nuvem na tela, muda pra branco. É o mesmo tile, só muda a paleta.</p>
<p>Sem detalhar porque, o nintendinho tinha uma paleta total de 54 cores mas cada tile só podia escolher entre uma paleta que tem no máximo 3 cores, mais o preto, então 4 cores, representado por 2-bits. Isso faz cada tile de 8 por 8 pixels ocupar 32 bytes e num cartucho simples, sem mapper, do endereço 0000 até 1FFF, cabe no máximo quinhentos e doze tiles.</p>
<p>Se a gente fosse fazer esse joguinho do super mario num computador moderno, como você faria? Um jeito ingênuo, já que não usamos mais cartuchos, seria gravar cada tile em um arquivo, tipo tile1.png, tile2.png, etc. Cada arquivo contendo exatamente os 32 bytes de cada tile. A parte divertida é que o arquivo na verdade vai consumir mais que 16 bytes. Por que?</p>
<p>Porque só o nome do arquivo &ldquo;tile1.png&rdquo; tem 18 bytes (em double byte, letras em UNICODE, lembra?). Só o nome já aumenta quase 50% o tanto de bytes. E não acaba aí. Em qualquer gerenciador de arquivos você consegue ver coisas como a data de modificação do arquivo. Precisa guardar esses bits, e digamos que ocupe 1 byte ou mais. E tem que guardar em que diretório esse arquivo tá, então no mínimo mais um ponteiro de endereço pra estrutura de diretório.</p>
<p>E na realidade tem mais metadados que isso, então cada arquivinho de 32 bytes vai ter quase o triplo de bytes pros metadados. Num Linux, na realidade, é mais que isso. Cada arquivo tem 2 kilobytes de metadados e estruturas. Os metadados são maiores que esse conteúdo. Por isso que você não deveria tentar guardar uma coisa tão pequena num arquivo. Claro que ninguém faz isso, mas só pra explicar caso você nunca tenha pensado nisso: metadados custam bytes e não são poucos. O tamanho de um arquivo não é só o tamanho do conteúdo.</p>
<p>Se o cartucho de Nintendo usasse um sistema de arquivos como um ext4 de Linux moderno, a gente não ia conseguir colocar quase nenhum tile nele. Só de metadados inúteis já ia encher. Aliás, a configuração do ext4 não ia caber no cartucho. E por isso que um cartucho não usa sistema de arquivo. Nenhum tile tem nome nem nada, só direto o endereço. Sistema de arquivos substitui a necessidade de lidar com endereços, ao custo de adicionar peso pra dar nome e organização pras coisas.</p>
<p>Se eu fosse simular um cartucho numa linguagem moderna, os bytes de tiles, guardaria num array. E se fosse gravar num dispositivo, gravaria todos os bytes crús juntos num arquivão só. Aliás, é isso que é uma imagem de ROM de cartucho que você roda num emulador: todos os bytes crus do cartucho. Mas aí, como você ia carregar só o tile correto? Igual se fazia antigamente: indo na posição desse arrayzão onde começa o tile e mandando ler só 32 bytes.</p>
<p>Quando a gente espeta um cartucho num videogame ou num microcomputador antigo como um Commodore, é o equivalente a espetar mais memória já pré-carregada. Quando executa um jogo num PC que tá gravado num disquete ou mesmo HD, primeiro ele carrega o jogo na memória, então tem um passo a mais e gasta a RAM. Sistema de arquivos aparece quando a gente passa a ter um dispositivo de armazenamento, de onde vamos carregar dados pra memória pra depois usar. Não usamos os bytes direto do dispositivo porque é lento demais.</p>
<p>É a transição da fase que computador só tinha memória e expandia com cartuchos. Daí passamos a usar gravador de fita cassette pra guardar os bytes do programa, literalmente uma fita de bytes. Um Commodore 64 usava um sistema de arquivos bem rudimentar, basicamente uma lista ligada com a coordenada de trilha e setor, 2 bytes pro próximo bloco e duzentos e cinquenta e quatro bytes de dados. Acho que era uma tabela de nomes de arquivos e cada arquivo sendo uma lista ligada assim.</p>
<p>O problema é que só dava pra escrever sequencialmente, e o próximo arquivo começava a partir do último endereço do último arquivo mais um byte e seguia assim. Se apagasse um arquivo, aquele espaço no meio não dava pra ser reusado automaticamente sem usar um comando como MOVE pra literalmente mover os arquivos que tavam depois pra ocupar os bytes antes.</p>
<p>Quando pensamos em dados, seja um único arquivo ou vários arquivos, na nossa cabeça a gente pensa como num cartucho: tudo empacotado um atrás do outro bonitinho. E no fim dos anos 70, no mundo doméstico, muitos computadores eram usados assim: você plugava um cartucho de jogo, jogava e desligava a máquina. Fazia uma conta de calculadora, e desligava a máquina.</p>
<p>Carregava um jogo de uma fita cassette, jogava, e desligava a máquina. E toda vez que desligava, tudo que tava na memória RAM apagava e era isso aí. Videogames até a era de um super nintendo, não gravavam nada em lugar nenhum. Com exceção de cartuchos que dava pra salvar o progresso do jogo, que era basicamente um chip de RAM ligado numa bateria daquelas de relógio dentro do cartucho.</p>
<p>Sistema de arquivos era mais comum num computador comercial, um mainframe ou minicomputador, rodando UNIX, gravando as coisas em rolos de fita gigantes ou nos primeiros HDs, que também eram gigantes, suportavam só alguns kilobytes e custavam uma fortuna. Mas foi só no começo dos anos 80 que coisas como disquetes e fitas cassete começaram a aparecer.</p>
<p>Daí surgiram os primeiros programas de produtividade, como eu já tinha mencionado em outros episódios, editores de texto como Wordstar ou Wordperfect, planilhas como Lotus 1-2-3 ou Visicalc, bancos de dados como dBase. Você ficou o dia todo criando uma planilha de custos, precisava gravar esses dados em algum lugar. Não podia ser como antes que ia desligar a máquina, perder tudo e ter que redigitar tudo no dia seguinte. Daí surge a necessidade de sistemas de arquivos que começaram bem rudimentar em micros como MSX ou Commodore.</p>
<p>Nos anos 80, cada microcomputador como Apple II, Commodore e tudo mais usava um sistema de arquivos proprietário. Trocar arquivos entre micros era um saco. Além do sistema de arquivos, no caso de arquivos texto, nem todo mundo usava ASCII, então mesmo conseguindo copiar pra outra máquina, quando abrisse pra editar vinha caracteres estranhos porque cada um usava códigos diferentes pra símbolos diferentes.</p>
<p>Com a dominância dos clones de IBM PC e todo mundo rodando ou MS-DOS ou um clone de MS-DOS, meio que estabilizou que o sistema de arquivos mais comum seria FAT e a tabela de caracteres seria ASCII ou uma variante. FAT é sigla pra tabela de alocação de arquivos. Assim como expliquei sobre MBR ou GPT dois episódios atrás, FAT é uma estrutura de dados que define como que arquivos e diretórios são gravados e organizados. O MBR ocupa o primeiro setor da primeira trilha do primeiro cilindro, logo na sequência os próximos setores vão guardar a FAT.</p>
<p>Naquela época a maioria das pessoas não tinha HDs, no máximo floppy ou disquetes. Acho que os primeiros tipos, que gravava só de um lado não era mais que 160 kilobytes no total. A gente grava informações sobre o disco e a tabela de arquivos, ou mais corretamente, tabela de alocação de arquivos que é File Allocation Table ou FAT, nos primeiros setores da primeira trilha do disco, lembra? Se as estruturas de dados pra guardar esses dados fosse muito grande, ia usar parte significativa desses 160 kilobytes.</p>
<p>Pra ter uma imagem na cabeça pense no seu disco sendo um linguição de bytes, que a gente pode desenhar como sendo várias linhas uma embaixo da outra. Você já sabe que HDs lêem bytes em blocos. O HD reconhece setores e vai lendo de 512 em 512 bytes. O primeiro setor do disco, lá no endereço 0 é o master boot record, que é uma estrutura de dados que vai configurar as partições. Sendo 512 bytes vai do endereço 0 até 01FF.</p>
<p>Existem várias versões de MBR, que foi evoluindo até ser substituido por GPT como você já sabe. Mas na primeira versão, a partir do endereço 01BE tinha entradas de partição, que é outra estrutura de 16 bytes. No MBR original eram 4 espaços de 16 bytes reservados pras 4 partições possíveis. Era só isso até criarem o conceito de partições extendidas, mas nem vou perder tempo detalhando porque hoje a gente usa GPT, que permite muito mais partições, mas também ocupa mais que um setor no começo do disco.</p>
<p>Independente se é MBR ou GPT, só entender que o primeiro setor do disco é reservado pra ter esse índice. E cada entrada de partição que você configurar com um fdisk da vida vai ter a coordenada em CHS no caso de MBR ou LBA no caso de GPT. Enfim, o endereço onde começa e outro endereço pra dizer onde termina. Daí a BIOS ou UEFI, depois de achar a primeira partição, vai continuar o bootstrapping, carregando o bootloader do sistema operacional, que finalmente vai carregar a kernel, drivers e terminar de bootar a máquina.</p>
<p>Agora você entende quando a gente abre um aplicativo como o Disk Management do Windows e vê essas partições. Elas ficam gravadas nessa área de GPT, e olha só como tem uma partição de 184 mega que é o EFI System Partition, que é pra onde a UEFI vai apontar no boot pra carregar a kernel do Windows. Tem outras partições maiores, de quase 500 mega, que provavelmente é a partição de recuperação. Quando você tá com problemas no boot, vai ter um sistema de emergência pra diagnosticar. Hoje que temos espaço sobrando, deixar reservado meio giga ou um giga, não é uma perda grande se for facilitar nossa vida caso algo dê pau.</p>
<p>Hoje é comum ter pelo menos 128 giga num HD barato. Mas no começo dos anos 80 o comum era no máximo ter 40 kilobytes num cartucho ou 160 kilobytes num disquete. A gente não tinha espaço pra ficar perdendo. Então imagine que os primeiros sistemas de arquivo tinham que ser super simples. Daria pra fazer vários episódios falando de cada um mas vou resumir.</p>
<p>O mais comum no começo foi o FAT12, que tinha endereços de 12 bits. Diferente do que fizemos com memória onde cada endereço aponta pra um byte na memória, endereços de HDs apontam pra clusters. Neste ponto vocês já devem estar começando a se acostumar com o conceito de uma estrutura de dados, tipo uma struct de C mesmo, onde cada campo tem um tamanho fixo em bytes. Sabendo o tamanho de cada campo podemos ir achando os campos no linguição de bytes. Foi assim quando expliquei sobre arquivos executáveis como ELF de Linux, ou o próprio MBR que é tipo uma estrutura de cabeçalho que define o drive.</p>
<p>Do byte 0 até 2 vai ter uma instrução de JUMP de assembly pra um endereço. O bytecode dessa instrução e o endereço vai depender da versão do MS-DOS ou Windows NT, mas é aqui que a BIOS passa o controle pro sistema operacional. Nos bytes seguintes vai ter outras informações como o nome do fabricante, daí vem as informações do layout do drive.</p>
<p>Nos bytes 11 e 12 tem o número de bytes por setor, que costuma ser 512 mas pode ir até 4 kilobytes que é 4096. No byte 13 tem o número de setores por cluster. Eu falei que o sistema de arquivos vai lidar com clusters e clusters pode ser um ou mais setores. É aqui que vai definir isso. Podemos colocar quanto de espaço queremos ter no drive.</p>
<p>Aqui que vem o 12-bits no FAT12. A tabela FAT12 consegue indexar até 2 elevado a 12 endereços pra clusters. Até 4 mil e noventa e seis clusters. Se o cluster tiver 2 setores de 512 bytes cada, o tamanho máximo que dá pra ter na partição vai ser 4 mil e noventa e seis vezes 2 setores, vezes 512 bytes, ou 4 megabytes. O que seria muito pouco. Mas se aumentar o tamanho dos setores de 512 bytes pra 4 kilobytes, esse valor aumenta pra até 16 megabytes.</p>
<p>Qual o problema de fazer isso? Lembra que expliquei no episódio de HDs que um arquivo vai ser gravado um setor de cada vez. Na realidade, vai ser gravado cluster a cluster porque é isso que o FAT reconhece. Então se cada cluster tiver 2 setores e cada setor tiver 4 kilobytes, se pegarmos um arquivo de 40 kilobytes pra gravar, precisamos reservar 2 clusters inteiros na FAT pros primeiros 32 kilobytes. Mas os 8 kilobytes restantes vai ocupar o próximo cluster inteiro. Ou seja, vai desperdiçar espaço no fim desse cluster. O arquivo de 40 kilobytes na realidade vai ocupar 48 kilobytes reais no disco. É um desperdício de 20% do disco.</p>
<p>Se você escolher um setor de 512 bytes como antes, cada cluster só vai ter 1 kilobyte. Mas o FAT só consegue indexar os mesmos 12-bits, 4 mil e noventa e seis clusters. Aí só dá pra ter uma partição de 4 megabytes em vez de 16. Naquela época isso não era ruim, porque a gente lidava mais com disquetes. No começo dos anos 80 o mais comum acho que era o disquete de 5 e 1/4 polegadas de 360 kilobytes. Então setores de 512 bytes tava de boa.</p>
<p>O problema foi do meio pro fim dos anos 80, quando passamos a ter HDs ou winchesters. Em 1990 eu tinha um winchester de 10 megabytes. Agora o FAT12 sozinho não ia dar conta. Por isso foi atualizado pra FAT16. Agora a tabela podia ter 2 elevado a 16, mais de 65 mil clusters. Se manter 2 setores por clusters e setores de 512 bytes mesmo, ainda assim já daria pra ter uma partição de até 64 megabytes. Então tava seguro. Até o meio dos anos 90 pouca gente tinha um HD tão grande assim.</p>
<p>Se você não tá acostumado com megabytes. Seu HD no seu computador aí deve ser de 128 giga ou mais. Se for um computador bem barato talvez seja de 64 gigas. Espero que não, porque o diretório C:\Windows sozinho  deve estar ocupando mais de 20 gigas. 1 giga é mil e vinte quatro vezes 1 mega. Ou seja, um HD de 64 giga é mil vezes maior que um HD de quase 30 anos atrás.</p>
<p>Mas aí, no meio dos anos 90, chegou a hora de aparecer o Windows 95, que ia tentar tirar o atraso nas interfaces gráficas que sistemas como Amiga e Mac já tinham faz anos. Mas construir uma interface gráfica em cima do FAT16 tinha um grande problema: a estrutura de dados que se chama &ldquo;directory entry&rdquo; era gravado assim no disco:</p>
<p>Os primeiros 11 bytes era o nome do arquivo de 8 bytes e uma extensão de 3 bytes. Só quem usou micros antes de 95 lembra disso. Era horroroso ficar criando vários documentos, planilhas, código fonte de programa e estar limitado a nomear arquivos só com 8 letras. A gente tinha que ser criativo. Mas era assim porque espaço de memória ou disco até aquele ponto custava muito caro. Lembra que eu falei que nome de arquivo ocupa espaço?</p>
<p>Eu lembro quando programava Clipper que a gente seguia essa convenção no nome dos campos em tabela de banco de dados. Era muito comum ter campos como CODCLI pra código de cliente, ou NOMCLI pra nome de cliente, DATVAL pra data de validade e assim por diante. Aliás, era pra economizar memória que datas eram gravadas no formato YYMMDD que é dois digitos pra dia, pra mês e pra ano, considerando que se YY fosse 80 então era 1980.</p>
<p>De qualquer forma, precisava resolver isso. Nessa entrada de diretório, que usava pra diretório e pra arquivo, o byte 11 depois da extensão era um vetor de bits. Outro dia vou falar das vantagens de um bitvector e bitmasks, mas deixa eu resumir.</p>
<p>Um byte tem 8 bits. Você pode pensar em cada bit como se fosse um interruptor de luz. 1 é ligado e 0 é desligado. E o campo inicializa com tudo zerado. No caso do FAT, se eu ligar o 1o bit, o bit 0, significa que é um arquivo ou diretório só de leitura. Se ligar o bit 1, é um arquivo ou diretório escondido. No caso de um Linux, ser escondido é ter o primeiro byte do nome sendo um ponto, os dotfiles. Mas no FAT é só 1 bit, então pra indicar se um arquivo é escondido em FAT usamos 7 bits a menos que no Linux.</p>
<p>Continuando, se ligar o bit 2 vai indicar que é um arquivo especial do sistema. Se ligar o bit 3 é a etiqueta ou nome do volume. Se ligar o bit 4 finalmente indica que essa estrutura tá definindo um subdiretório. Por outro, lado se ligar o bit 5 é um arquivo. Imagino que os bits 4 e 5, só um deles pode ser 1 e o outro 0. Já os bits 6 e 7 não significam nada e são ignorados.</p>
<p>Ou seja, em um único byte a gente conseguiu enfiar 6 informações diferentes. Se você fizesse do jeito ingênuo como uma struct com 6 campos int, ia gastar 6 bytes inteiros, 5 bytes a mais do que usando um único campo de 1 byte. Isso é muito comum pra guardar informações que é só verdadeiro ou falso, 1 ou 0. Você só precisa de 1 bit pra representar essa informação. Essa técnica aumenta a densidade de informações por byte.</p>
<p>E eu me dei ao trabalho de explicar isso porque a forma de permitir nomes longos a partir do Windows 95, foi atualizar o FAT16 pra VFAT. E a principal diferença é que toda vez que cria um arquivo vai criar duas entradas de diretório. Um igualzinho o que acabei de explicar, começando com 11 bytes pro nome no formato antigo de 8 letras e extensão de 3 letras. Mas a segunda estrutura ia entuchar mais bytes no meio pra caracteres, agora em formato UNICODE em vez de ASCII, já que a ambição do Windows 95 era expandir pelo mundo em diversas línguas.</p>
<p>Pra programas de DOS antigos não se confundirem, ainda ia ter a estrutura antiga, que ia ignorar a estrutura nova. Pensa que na mesma lista você tem as duas estruturas. Mas aí o DOS antigo ia ler o byte 11 que é o bitvector de atributos e encontrar 0xF, tudo ligado 11111111, que é um atributo inválido, então um DOS antigo ia ignorar.</p>
<p>Na prática, você podia digitar um documento no Word do Windows 95 e salvar como hoje, com um nome como &ldquo;Escopo do Projeto Secreto.doc&rdquo;. Vai gerar uma entrada de diretório com caracteres UNICODE normalmente e junto gerar uma segunda entrada com uma versão no formato antigo de 11 bytes. Provavelmente vai ser &ldquo;ESCOPO<del>1.DOC&rdquo;. Se gravar outro arquivo como &ldquo;Escopo do Projeto do Ano que vem.doc&rdquo;, vai gerar uma versão de DOS chamado &ldquo;ESCOPO</del>2.DOC&rdquo;.</p>
<p>Esse esquema dura até hoje no NTFS do Windows 10. Se não acredita abre um command prompt e digita &ldquo;cd c:\progra<del>1&rdquo; e ele cai no &ldquo;Program Files&rdquo;. Se abrir o Explorer do Windows e digitar &ldquo;C:\progra</del>1&rdquo;, ele vai te mandar certinho pro diretório &ldquo;Program Files&rdquo;. O DNA do DOS ainda existe.</p>
<p>O VFAT foi uma versão feita pro lançamento do Windows 95, mas logo no segundo DLC que foi o Windows 95 OSR2, a versão que funcionava de verdade, o VFAT foi substituído pelo FAT32. Você já imagina que a tabela subiu pra endereçar 2 elevado a 32 cluster em vez de 16, até 4 milhões de clusters. Com setores de 512 bytes isso daria uma partição de tamanho máximo teórico de 4 terabytes.</p>
<p>Se o cluster só tiver 1 setor, então ia até 8 terabytes, mas cada arquivo dentro só poderia ter 4 gigabytes. É 32-bits, então só dá pra ler e gravar 2 elevado a 32 bytes de dados crús, o que dá o famoso limite de 4 giga por arquivo. Mas a gente já viu no outro episódio que era a BIOS e o MBR que não permitiam ter partições maiores que 2 terabytes. Finalmente o FAT32 seria o filesystem que ia durar a virada do século.</p>
<p>Em paralelo a isso a Microsoft tentou muitas coisas dos anos 80 até começo dos 90 pra abrir mais pro mercado de workstations, onde tava a Silicon Graphics ou a Sun. Mas MS-DOS e Windows 3 não era suficiente. Por isso teve as iniciativas como o XENIX que foi um UNIX da Microsoft que não vingou e a parceria com a IBM que deu origem ao OS/2. Tem muito sistema como máquinas antigas de sacar dinheiro, as ATMs, sistema de bilhete de metrô e coisas assim que ainda rodam em OS/2.</p>
<p>Um dia eu tento falar do OS/2 mas ele era mais sofisticado que o Windows 95, que não era muito mais que um sistema parasita que espera o MS-DOS bootar em modo real pra depois ligar o modo protegido e dar controle pro Windows. Era uma gambiarra de coisas antigas de 16-bits misturado com coisas novas de 32-bits. A série do Windows 95, 98 até o famigerado Millenium Edition no final dos anos 90 foi uma bela de uma zona.</p>
<p>Já o OS/2 foi projetado pra ser um sistema mais completo de 32-bits. Acho que a primeira versão era 16-bits mas mudaram rápido. Eu cheguei a ver as primeiras versões do OS/2 que tinha a cara do Windows 3.1. O OS/2 mais famoso acho que o foi a versão 3 Warp e a que perdura até hoje em muitos lugares é a versão 4. E existe uma versão open source que foi feita via engenharia reversa, porque a IBM nunca abriu o código fonte do OS/2.</p>
<p>É quase impossível rodar um MS-DOS ou OS/2 dos anos 90 em máquinas modernas sem modificação. Pra começar, o processo de boot mudou de BIOS e MBR pra UEFI e GPT além disso o hardware de hoje tem mais integrações como ACPI e AHCI que é o Advanced Configuration and Power Interface e Advanced Host Controller Interface. O primeiro pra facilitar o sistema operacional encontrar hardware, configurar e gerenciar energia e a segunda pra falar com os controladores de SATA que fala com drives.</p>
<p>Seu HD provavelmente é SATA e não mais IDE ou SCSI. Sem entender AHCI, a gente nem encontra o HD. Quando você configura uma máquina virtual num VMWare ou Virtualbox da vida, vai notar que eles configuram um HD SCSI e não SATA. SCSI era a melhor interface da época, usado em workstations e Macs. PCs mais baratos usavam IDE mesmo.</p>
<p>Antigamente a gente tinha porta serial, porta paralela e barramento IDE que falava ATA. Ligava impressora na porta paralela, modem na serial. Era um saco configurar alguma coisa nessas portas e barramentos antigos. Ainda não tinha protocolos pros dispositivos se identificarem automaticamente pro sistema operacional. Pensa que isso exige mais bytes e um protocolo mais gordo e a velocidade de transferência era bem lenta.</p>
<p>Hoje em dia a gente tem conectores USB como Type C em vez do antigo SCSI, protocolos como USB 3.2 Gen 2 ou Thunderbolt 3 em vez do antigo ATA. Aliás, depois do ATA veio o Serial ATA ou SATA. E esses conectores se ligam em barramentos internos que falam direto com a CPU como PCI Express ou PCIe, que substituiu o antigo PCI e AGP. Essa é uma parte da história que tenho zero saudades: o tanto de problema que era conectar e configurar dispositivo. Nunca funcionava de primeira. Muito diferente de conectar um HD externo num Thunderbolt hoje, que é plug and play de verdade.</p>
<p>Mas vamos deixar dispositivos de lado por hoje. A parte importante é que da parceria da Microsoft com a IBM, no OS/2, surgiu o sistema de arquivos HPFS ou High Performance File System. E ele serviu de inspiração pra outro filesystem, o New Technology File System ou NTFS do Windows NT.</p>
<p>Em paralelo a essa história de FAT. No mundo dos Macs, tinha o Macintosh com o Macintosh File System, que era um pouco melhor que um FAT12 da vida. Mas com a Apple querendo lançar HDs nos Macintoshs eles evoluíram pra uma nova versão de filesystem chamada Hierarchical File System ou HFS.</p>
<p>Assim como o HPFS da IBM pro OS/2 e diferente do FAT, o HFS seria implementado usando B+Trees. Você já entendeu um pouco sobre estruturas de árvores alguns episódios atrás. E eu expliquei rapidamente sobre Balanced Trees ou B-Trees e derivações como B+Trees e B* Trees. Vocês vão ver que essas estruturas estão por baixo de quase tudo que você usa num computador.</p>
<p>Em resumo, são árvores balanceadas de baixa profundidado onde cada nó tem vários filhos de uma só vez. Além disso os filhos nos galhos internos só tem metadados e chaves, onde ficam os inodes. E só as folhas da árvore, os últimos nós externos lá embaixo, é que apontam pros dados em si. No caso, pra clusters ou blocos.</p>
<p>Tem a vantagem de ter tempo de procura razoavelmente bom, especialmente se comparar com uma mera tabela. E diferente de uma árvore binária, a profundidade vai ser baixa porque cada nó vai ter vários filhos em sequência. E como tem vários filhos por nó, nem toda operação de inserção ou deleção vai precisar promover um rebalanceamento, como eu mostrei que acontece numa árvore como red black trees.</p>
<p>Em discos, a vantagem é que isso ajuda a diminuir a fragmentação e aumenta a performance. Os dados são lidos e escritos em conjuntos grandes de blocos de uma só vez. Provavelmente vai desperdiçar um pouco de espaço. Mas isso é outro trade off, a gente troca um pouco de espaço em disco pra evitar fragmentação e aumentar a performance de leitura e escrita. Provavelmente tirando o antigo FAT, todos os file systems usam B+Trees. Mesmo o HFS da Apple em 1985 já era implementado com B+Tree.</p>
<p>Num Mac você sabe que dá pra abrir as propriedades de um arquivo e atribuir uma cor pra aparecer destacado no Finder. E dá pra selecionar o ícone, copiar, e colar em outro arquivo. Sem ter um arquivo de icone separado. E muito mais, mas você já pensou onde que se armazena essas coisas?</p>
<p>A cor do arquivo até pode ir nos metadados no nó da árvore mas e um ícone que tem múltiplos bytes? Ele vai num resource fork. Na realidade o nó da árvore pode apontar pra dois galhos de dados, um é o data fork que aponta pro primeiro byte do conteúdo do arquivo propriamente dito. O outro galho é o resource fork que aponta pro primeiro byte de um outro conjunto de dados escondido.</p>
<p>Por isso que quando você faz um arquivo zip comprimido num Mac e descompata num PC, vem um diretório estranho chamado &ldquo;underline&rdquo; MACOSX. Digamos que você tava compactando um arquivo chamado &ldquo;retrato.jpg&rdquo; então nesse diretório estranho vai ter um arquivo &ldquo;._ retrato.jpg&rdquo; que são os bytes que ficavam no resource fork.</p>
<p>Como nem todo filesystem tem suporte a resource forks, em particular FAT não tem nada nem perto disso, então o Mac é obrigado a criar arquivos de suporte pra não descartar informações. Se você copia de um Mac pra outro Mac, isso é transparente. Mas se usa um filesystem intermediário, ou um formato de arquivos como Zip, ele é obrigado a dar um jeito. O NTFS tem suporte a forks, que chama de ADS ou alternate data streams.</p>
<p>Se você abrir um command prompt num Windows 10 de hoje, sabe que dá pra criar arquivos texto como num shell de Linux. Basta fazer <code>echo &quot;hello world&quot; &gt; hello.txt</code> e tá criado. Se usar o comando <code>more hello.txt</code>  vai listar de volta o conteúdo. Trivial, todo mundo sabe isso. Mas o que muitos de você não sabem é que posso escrever bytes nesse alternate data stream do arquivo. Tente fazer <code>echo &quot;isto é um segredo&quot; &gt; hello.txt:segredo</code>. Pronto. Se usar o comando <code>more</code> de novo, não vai enxergar nada, só o texto original. O que aconteceu?</p>
<p>Agora posso fazer <code>more &lt; hello.txt:segredo</code> e olha só, o texto secreto apareceu! Se abrir num editor de textos normal, nunca vai ver esses bytes. Praticamente ninguém suporta alternate streams nos programas. Mas esse arquivo agora carrega esse segredo escondido. Pena que é um recurso que acaba sendo desperdiçado. Esse &lsquo;segredo&rsquo; é o nome do stream de bytes e a gente pode criar vários streams diferentes.</p>
<p>E é mais chato ainda porque se a gente tentar copiar esse arquivo pra algum pendrive em FAT32 da vida, o Explorer vai avisar que o arquivo tem propriedades que não vão ser copiadas e parar por aí. Daí perdemos aquele segredo porque, diferente do Mac, ele nem tenta criar um arquivo separado. Como as ferramentas que gerenciam arquivos do próprio Windows male male reconhecem esses forks, eles acabam sendo inúteis pra gente.</p>
<p>E por hoje vou pausar essa história porque no próximo episódio vou começar a falar sobre os outros filesystems e a nova geração que usamos hoje em dia e como esse estudo em estruturas de dados foi modificando a forma como pensamos em arquivos. Se ficaram com dúvida mandem nos comentários abaixo, assinem o canal e não deixem de clicar no sininho pra não perder a continuação. Compartilhem o video pra ajudar o canal. A gente se vê, até mais.</p>
]]></content:encoded><category>armazenamento-e-backup</category><category>algoritmos-e-estruturas-de-dados</category><category>ciencia-da-computacao</category><category>akitando</category></item><item><title>[Akitando] #95 - Árvores: O Começo de TUDO | Estruturas de Dados e Algoritmos</title><link>https://akitaonrails.github.io/2021/04/06/akitando-95-arvores-o-comeco-de-tudo/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/04/06/akitando-95-arvores-o-comeco-de-tudo/</guid><pubDate>Tue, 06 Apr 2021 10:30:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/9GdesxWtOgs"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Este é o final da minha Trilogia de Estruturas de Dados e Algoritmos e finalmente vou conseguir falar sobre o mais importante na matéria: árvores! Vamos de BSTs a AVLs, passando por Red Black Trees e muito mais! Se você nunca estudou sobre isso, ou não tinha entendido quando estudou, esta é sua chance!&lt;/p&gt;
&lt;p&gt;Conteúdo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;02:27 - Distribuição Gaussiana&lt;/li&gt;
&lt;li&gt;05:53 - Hashing&lt;/li&gt;
&lt;li&gt;09:43 - Todo mundo erra&lt;/li&gt;
&lt;li&gt;10:18 - Recapitulando&lt;/li&gt;
&lt;li&gt;12:34 - Pilhas e Filas&lt;/li&gt;
&lt;li&gt;12:57 - Grafos&lt;/li&gt;
&lt;li&gt;13:25 - Entendendo Redes&lt;/li&gt;
&lt;li&gt;15:05 - Entendendo Árvores&lt;/li&gt;
&lt;li&gt;16:34 - Heap/Memória&lt;/li&gt;
&lt;li&gt;18:21 - Construindo Árvores&lt;/li&gt;
&lt;li&gt;19:31 - Vantagem da Partição (log)&lt;/li&gt;
&lt;li&gt;20:18 - Árvore de Procura Binária&lt;/li&gt;
&lt;li&gt;22:24 - Visualizando Inserção na Árvore&lt;/li&gt;
&lt;li&gt;26:19 - Imprimindo a Árvore Ordenada&lt;/li&gt;
&lt;li&gt;30:24 - Procura Binária&lt;/li&gt;
&lt;li&gt;32:01 - Complexidade de Recursos&lt;/li&gt;
&lt;li&gt;34:52 - Pior Caso&lt;/li&gt;
&lt;li&gt;35:47 - Red Black Trees&lt;/li&gt;
&lt;li&gt;38:33 - Cor e Rotação&lt;/li&gt;
&lt;li&gt;42:52 - AVL Trees&lt;/li&gt;
&lt;li&gt;44:51 - Red Black no Mundo Real&lt;/li&gt;
&lt;li&gt;47:06 - B-Tree e B+ Tree&lt;/li&gt;
&lt;li&gt;48:47 - Bancos de Dados&lt;/li&gt;
&lt;li&gt;52:46 - Intuição em Algoritmos&lt;/li&gt;
&lt;li&gt;54:13 - Por que estudar tudo isso?&lt;/li&gt;
&lt;li&gt;57:04 - Conclusão&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Meus Vídeos Mencionados:&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/9GdesxWtOgs"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Este é o final da minha Trilogia de Estruturas de Dados e Algoritmos e finalmente vou conseguir falar sobre o mais importante na matéria: árvores! Vamos de BSTs a AVLs, passando por Red Black Trees e muito mais! Se você nunca estudou sobre isso, ou não tinha entendido quando estudou, esta é sua chance!</p>
<p>Conteúdo:</p>
<ul>
<li>00:00 - Intro</li>
<li>02:27 - Distribuição Gaussiana</li>
<li>05:53 - Hashing</li>
<li>09:43 - Todo mundo erra</li>
<li>10:18 - Recapitulando</li>
<li>12:34 - Pilhas e Filas</li>
<li>12:57 - Grafos</li>
<li>13:25 - Entendendo Redes</li>
<li>15:05 - Entendendo Árvores</li>
<li>16:34 - Heap/Memória</li>
<li>18:21 - Construindo Árvores</li>
<li>19:31 - Vantagem da Partição (log)</li>
<li>20:18 - Árvore de Procura Binária</li>
<li>22:24 - Visualizando Inserção na Árvore</li>
<li>26:19 - Imprimindo a Árvore Ordenada</li>
<li>30:24 - Procura Binária</li>
<li>32:01 - Complexidade de Recursos</li>
<li>34:52 - Pior Caso</li>
<li>35:47 - Red Black Trees</li>
<li>38:33 - Cor e Rotação</li>
<li>42:52 - AVL Trees</li>
<li>44:51 - Red Black no Mundo Real</li>
<li>47:06 - B-Tree e B+ Tree</li>
<li>48:47 - Bancos de Dados</li>
<li>52:46 - Intuição em Algoritmos</li>
<li>54:13 - Por que estudar tudo isso?</li>
<li>57:04 - Conclusão</li>
</ul>
<p>Meus Vídeos Mencionados:</p>
<ul>
<li>Monetizar? Bloquear ADs? O que Fazer? (<a href="https://www.youtube.com/watch?v=dOe9X6Q_-nU"target="_blank" rel="noopener">https://www.youtube.com/watch?v=dOe9X6Q_-nU</a>)</li>
<li>Entendendo Conceitos Básicos de CRIPTOGRAFIA | Parte 1/2 (<a href="https://www.youtube.com/watch?v=CcU5Kc_FN_4"target="_blank" rel="noopener">https://www.youtube.com/watch?v=CcU5Kc_FN_4</a>)</li>
<li>O Guia +Hardcore de Introdução à COMPUTAÇÃO (<a href="https://www.youtube.com/watch?v=8G80nuEyDN4"target="_blank" rel="noopener">https://www.youtube.com/watch?v=8G80nuEyDN4</a>)</li>
<li>O Computador de Turing e Von Neumann | Por que calculadoras não são computadores? (<a href="https://www.youtube.com/watch?v=G4MvFT8TGII"target="_blank" rel="noopener">https://www.youtube.com/watch?v=G4MvFT8TGII</a>)</li>
<li>Turing Complete, Emuladores e o Chip ARM M1 (<a href="https://www.youtube.com/watch?v=kz3649U2sJY"target="_blank" rel="noopener">https://www.youtube.com/watch?v=kz3649U2sJY</a>)</li>
<li>Qual a REAL diferença entre Arquivos Binário e Texto?? 🤔 (<a href="https://www.youtube.com/watch?v=oSCVb4Ts-G4"target="_blank" rel="noopener">https://www.youtube.com/watch?v=oSCVb4Ts-G4</a>)</li>
<li>Hello World Como Você Nunca Viu! | Entendendo C (<a href="https://www.youtube.com/watch?v=Gp2m8ZuXoPg"target="_blank" rel="noopener">https://www.youtube.com/watch?v=Gp2m8ZuXoPg</a>)</li>
<li>O que vem DEPOIS do Hello World | Consertando meu C (<a href="https://www.youtube.com/watch?v=YyWMN_0g3BQ"target="_blank" rel="noopener">https://www.youtube.com/watch?v=YyWMN_0g3BQ</a>)</li>
</ul>
<p>Links:</p>
<ul>
<li>Red-black Trees (rbtree) in Linux (<a href="https://www.kernel.org/doc/Documentation/rbtree.txt"target="_blank" rel="noopener">https://www.kernel.org/doc/Documentation/rbtree.txt</a>)</li>
<li>Red-black Trees Animated (<a href="https://www.cs.usfca.edu/~galles/visualization/RedBlack.html"target="_blank" rel="noopener">https://www.cs.usfca.edu/~galles/visualization/RedBlack.html</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>No final eu acabei me empolgando no assunto de algoritmos e estruturas de dados. Eu não queria continuar porque dá bastante trabalho fazer esse tipo de episódio, mas já que cheguei até aqui, me senti na obrigação falar da estrutura de dados que eu considero a mais importante da matéria: árvores ou trees.</p>
<p>Os vídeos acabaram trazendo muita gente nova que acho que não entendeu a proposta do canal. Eu não tô fazendo um substituto pra cursos oficiais de faculdades, e eu nem tenho intenções de parecer que seja. Só tô discutindo assuntos que acho que são importantes que iniciantes procurem estudar por conta própria. E pra isso eles precisam pelo menos saber que esses assuntos existem e porque são importantes.</p>
<p>Pra quem não sabe, o canal não tem fins lucrativos, eu não tenho patreons, nem patrocínios de nenhum tipo, nem vendo nada. E tudo que é arrecadado com ads eu faço doação de 200%, quer dizer, eu pego o que arrecado e dobro do meu bolso pra outros projetos de educação sem fins lucrativos. Vejam meu video acima explicando e não esqueçam de mandar mais sugestões de projetos pro e-mail contato @ codeminer42.com.</p>
<p>Como eu faço os vídeos nas minhas horas vagas e como desde o fim do ano passado meu volume de trabalho aumentou drasticamente, tem sobrado bem menos tempo pra me dedicar. Infelizmente não é porque tô jogando muito videogame. Meu pobre PS5 tá pegando pó desde que comprei. E eu sou meio retardado das idéias e decidi justo fazer videos de 1 hora de duração, trabalhosos de editar, e obviamente cometi erros pra lá e pra cá. Desculpem por isso, mas lembrem que eu relato os principais nas erratas na descrição e até mesmo no video seguinte. Por outro lado acho bacana que vocês estão prestando atenção e notando esses erros.</p>
<p>E só pra variar, o comecinho do episódio de hoje vai ser pra eu falar das erratas do episódio anterior. Apesar de pra alguns parecer coisa pequena, é importante eu explicar o correto, porque se você entender completamente esta minissérie de 3 episódios, talvez comece a enxergar programação e código de uma forma diferente. E mais do que isso: que cometer erros faz parte do processo. Então vamos lá!</p>
<p>(&hellip;)</p>
<p>Um erro besta, mas importante, é quando eu falo sobre a função de hashing pro nosso exemplo de Hashtable. Sou o primeiro a dizer que as pessoas não devem achar que tudo é uma distribuição gaussiana ou normal no mundo, porque não são. E eu mesmo cometi o erro de fazer isso. Vamos entender rapidamente porque isso é importante e aproveitar pra fazer umas tangentes.</p>
<p>Eu não vou explicar estatística porque eu não me acho a pessoa mais adequada. Mas todo mundo já deve ter visto a distribuição gaussiana ou normal. Todo tipo de evento independente pode ser gaussiano, por exemplo a altura média da população. Faz de conta que se medirmos a altura de 100 pessoas, 80 vão estar nos 1 metro e sessenta, 12 vão estar perto de 1 metro e meio ou 1 metro e oitenta, e 8 entre 1 metro e quarenta e 1 metro e noventa. Se colocar isso num gráfico, mesmo medindo poucas pessoas, a gente começa a ver o tal formato de sino ou bell shape, que caracteriza uma distribuição normal.</p>
<p>A média é onde costuma estar a maioria das medições, no meio. E quanto mais pra direita ou pra esquerda vai indo, as medições caem rápido. Essas distâncias a gente chama de sigmas. Se não me engano, 65 porcento das medições vão estar em sigma 1, 95 porcento em sigma 2, e 99 ponto sete porcento em sigma três. Quem estudou coisas como processos de controle de qualidade conhece Six Sigma, ou sigma 6 que é o famoso 99 ponto 99966 ou 3.4 defeitos por milhão.</p>
<p>Quando a gente fala 99.7% parece muito bom, porque nas nossas concepções mais casuais, parece &ldquo;perto de 100 porcento o suficiente&rdquo;, mas não é. Um contrato de SLA Sigma 3 de 99.7% significa 1 dia inteiro fora do ar num ano, por exemplo. Em uma indústria que produz milhões de itens por mês, significa 3 MIL defeitos por milhão de itens, por mês. Infelizmente, perfeição, ou seja, zero defeitos é quase impossível no mundo real por períodos prolongados de tempo. Eventualmente vai ter erros humanos, falhas mecânicas. Six Sigma costuma ser a meta.</p>
<p>A última coisa a se lembrar sobre distribuições normais e médias é que elas só valem pra eventos independentes entre si. Quando você mensura coisas onde um não afeta o outro, como jogar dados, peças fabricadas em massa, altura de uma população. Mas um lugar onde todo mundo usa errado é salários, porque o salário de um influencia o dos demais, não é um evento independente. É um número dinâmico que varia por diversos fatores. Outras distribuições estatísticas vão se encaixar melhor como a Lei de Potência ou Power Law, mas não a gaussiana.</p>
<p>Enfim, essa tangente não tem nada a ver com a minha errata, foi só algumas coisas que eu já  queria compartilhar mesmo. Mas a errata é que na explicação sobre funções de hashing eu disse que o ideal seria uma função que conseguisse devolver números que caracterizam uma distribuição gaussiana. Mas tá errado, pois significa que na média ele ia devolver o mesmo valor, com alguns números diferentes próximos da média. Por exemplo, se fosse uma função que devolve números entre 1 e 10, ele poderia só ficar devolvendo o número 5 por exemplo, que é exatamente o oposto do que a gente quer.</p>
<p>Na verdade a distribuição pra esse tipo de função deve ser uniforme onde, aí sim na média, ele devolve a mesma quantidade pra cada número do range. E por isso minha confusão antes. No exemplo, se for uma função que devolve números de 1 a 10, e eu rodar essa função cinquenta vezes, cada número de 1 a 10 deveria aparecer na média 5 vezes, e assim balancear o uso do meu hashtable.</p>
<p>E mesmo assim não deveria devolver cinco números 1s, depois cinco números 2s etc de forma fácil de prever, determinístico, e aí eu disse que deveria ser o mais próximo de aleatório quanto possível, se mantendo numa distribuição uniforme. E eu sei que isso que eu acabei de falar, por si só é assunto pra outro episódio, então pesquisem sobre números pseudo-aleatórios pra mais detalhes depois.</p>
<p>Continuando, eu usei o exemplo de função de hashing djb2 e disse que não é necessariamente a melhor, mas não tá correto também, na realidade a djb2 é uma boa função se você não souber nenhuma outra. Como disse antes, o importante é os resultados caírem o mais perto possível de uma distribuição uniforme e evitar colisões, ou seja, evitar devolver muito do mesmo resultado.</p>
<p>Dentre as funções de hashing mais conhecidas, um exemplo didático é o Lose Lose, um dos piores, muita colisão. Tem o Super Fast Hash, que como o nome diz, é rápido, mas também muita colisão. E tem o CRC32 que é bom mas precisa de um lookup table de 1 kilobyte, então consome mais recursos que outros algoritmos, o que pode fazer diferença num ambiente limitado, como embarcado, mas é bom pra todo o resto. Não tenho certeza de cabeça, mas acho que é usado em coisas como protocolos de rede, por exemplo.</p>
<p>O que eu venho explicando desde os episódios sobre Turing como tudo são números. Endereços de memória são números. Strings é uma cadeia de bytes, tudo é um numerozão binário. E hashing é uma forma de conseguir números menores que representam um número maior, como Strings. Então um texto da wikipedia é um linguição de bits gigantes, um numerozão enorme. E com hashing podemos etiquetar esse textão com um número menor. No caso de um SHA-256 da vida, seria com uma etiqueta numérica de 256 bits. É como um número de série de um item. O ideal é ser um número único, mas não é garantia e por isso temos colisões onde dois itens diferentes, dois textões diferentes de wikipedia podem chegar no mesmo hash.</p>
<p>Quem já programa poderia pensar, mas porque não usamos coisas como SHA-512? Eles são usados pra segurança, não são melhores? Eles tem mais ou menos a mesma função, devolver um número que representa uma cadeia de bits, um String, como a chave do nosso hash. E esse número costuma ser o mais único quanto possível. Nunca vai ser 100% sempre único, mas quanto maior o valor do resultado, estatisticamente menores as chances disso acontecer.</p>
<p>Porém a categoria de funções de hashing como SHA-512 foram desenhados pra ter características de segurança, se usados em conjunto com coisas como salts, como eu expliquei no episódio de criptografia. São mais avançados e consomem mais recursos e mais processamento pra gerar esse número. Portanto, não são necessariamente eficientes como geradores de chaves num hashtable se não há requerimentos de segurança junto.</p>
<p>Em particular o SHA-512 é rápido em máquinas de 64-bits porque as funções de cálculo vem implementado em hardware no CPU hoje em dia. De qualquer forma não quer dizer que não poderia ser usado, só que não vai ser o mais eficiente pra hashtables genéricos. E voltando pra errata, eu falei no video que o cálculo do djb2 faz bitwise shift de 5 bits pra esquerda, que é um cálculo rápido e é equivalente a multiplicar por 3 em decimal. O certo é multiplicar por 32.</p>
<p>Eu ainda cometi dois erros sobre complexidade. O primeiro foi quando falei que NP é tempo não polinomial, mas o correto, que até falo depois no vídeo, é que NP é Non Deterministic Polynomial Time ou Tempo polinomial não determinístico. Outro erro foi quando falei que complexidade exponencial é Big O de 2 elevado a N mas na realidade é N elevado a N. E falando nisso eu disse que fatorial é a complexidade mais cara, mas na realidade é a exponencial, porque por exemplo, 15 fatorial é 15 vezes 14 vezes 13 vezes 12 etc até 1. Mas 15 elevado a 15, exponencial, é 15 vezes 15 vezes 15 etc 15 vezes.</p>
<p>Exemplo de trecho que tirei direto da cabeça e não revisei direito. Eu disse que um exemplo de complexidade fatorial é a implementação do cálculo de fatorial recursivo. Tá errado e nem lembro porque eu falei isso, mas na realidade é O linear e não fatorial. O exemplo correto pra fatorial é o Travelling Salesman, que eu traduzi direto do inglês pra Vendedor Viajante mas no Brasil povo tá acostumado a falar em problema do caixeiro viajante. Na real tanto faz, se você procurar em português vai achar como caixeiro mesmo, mas eu recomendo procurar como travelling salesman que vai achar artigos muito melhores sobre o problema.</p>
<p>Tudo isso dito, vamos ver se neste último episódio da série eu cometo menos erros. Sempre leiam os comentários, tem um povo que tá prestando atenção e corretamente corrigindo. Falando nisso eu fico surpreso que tem gente que acha que pessoas como eu não erram. Óbvio que erram e não é pouca coisa, no máximo eu diria que pessoas experientes no geral reconhecem o erro mais rápido e corrigem mais rápido. Pessoal inexperiente, por alguma razão, insiste no erro por muito mais tempo do que deveriam. No final todo mundo erra, então não fiquem frustrados se vocês erram, o problema é errar a mesma coisa o tempo todo.</p>
<p>Pra você não se perder vamos recapitular. Ano passado eu adaptei dois vídeos de dois canais que eu gosto, do Ben Eater e do Gaming Mechanics Explained. Primeiro pra mostrar no nível do transistor, num protoboard, como bits são armazenados numa memória e como instruções de &ldquo;baixo nível&rdquo; são construídas com transistores, a verdadeira linguagem de máquina. Tudo começa com instruções simples como soma, construído em hardware, passando elétrons por transistores, formando portas lógicas. E com isso você chega no Assembly.</p>
<p>Depois, de propósito, resolvi contar a história de Turing e Von Neumann pra explicar como instruções e dados populam a mesma linguiçona de bits e o que isso significa, os primórdios da computação moderna. Na sequência eu quis falar sobre tipos de arquivos, que todo mundo usa e ninguém entende que arquivos texto e arquivos executáveis é tudo a mesma coisa, tudo um linguição de bits. Todo programa, todo arquivo, é nada mais que um numerozão gigante.</p>
<p>A ordem que esses bits aparecem nessa fita determina seu formato. Cada bit tem um endereço, que é a posição na fita. E quando você tem sequências especiais de bits em determinados endereços vai começar a diferenciar entre uma imagem jpeg e um executável de Linux, por exemplo. Aliás, quando eu falei de memória virtual nos dois últimos episódios, a divisão de segmentos como stack, heap, ROData, Text é específica de Linux e executáveis ELF. Windows e Mac o layout dos segmentos é diferente, ou seja, o range de endereços de cada coisa vai ser diferente. Mesma coisa entre arquiteturas de 32 ou 64-bits.</p>
<p>Enfim, daí chegamos na trilogia de agora, onde comecei com diferentes Hello World pra mostrar como essa fita é usada e como a memória começa a ser organizada em coisas como stack e heap e finalmente no último episódio mostrei como existem diferentes tipos de listas: arrays, listas ligadas, hashtables. Não cheguei a mencionar, mas você pode implementar Stacks que são pilhas e Queues que são filas usando qualquer uma dessas estruturas. Números inteiros, arrays de chars, e coisas assim são tipos primitivos, coisas “concretas”. Pilhas ou filas são conceitos, que podem ser implementados de vários jeitos, e por isso chamamos de tipos abstratos.</p>
<p>Pilhas podem ser arrays ou listas ligadas onde você só tem acesso ao último elemento, no caso de pilha, ou seja, só operações de push pra empurrar um elemento pro fim da lista e pop pra tirar o elemento do começo da lista. Filas você também tem duas operações principais, uma enqueue pra colocar no fim da lista e uma dequeue pra tirar do começo da lista. São casos de estruturas que chamamos de lineares.</p>
<p>Então também existem estruturas não-lineares, e aqui entra o assunto de Grafos. Eu não lembro se era correto dizer assim, mas um caso particular de Grafo unidirecional é uma Árvore. Voltando. Grafos você vê todos os dias. Toda rede, seja rede local ou de internet, é um grafo. Um conjunto de nós ou Nodes, como computadores, ligados por Edges que são tipo o cabo ethernet pro roteador ou mesmo a conexão wifi que liga seu smartphone no roteador.</p>
<p>Uma rede social é um grafo, onde os Nodes são as pessoas. Os Edges são os relacionamentos entre elas. E você pode ter todo mundo ligado com todo mundo ou diversos grupos ligados por algumas poucas pessoas em comum entre os grupos. Se quiser entender redes sociais, você começa entendendo Grafos.</p>
<p>Mas grafos é um assunto muito grande pra explicar em pouco tempo. Por hoje, entendam que existe essa matéria pra estudar. Praticamente tudo que você chama de Rede pode ser descrito com grafos. Por exemplo, o algoritmo de como encontrar o caminho mais curto entre dois Nodes num Grafo, que é o Shortest Path do Dijkstra. Poderia ser o caminho mais curto entre duas localizações num Google Maps ou o caminho mais curto no relacionamento de pessoas num LinkedIn.</p>
<p>E na estatística existem vários estudos sobre as particularidades de distribuição de diferentes grafos ou redes. Se quiser se aprofundar, recomendo livros de rede do Steven Strogatz e Duncan Watts, que inclusive tem um modelo que leva o nome deles, o modelo Watts-Strogatz. Entendendo redes de forma estatística você chega nos temas de small worlds ou mundos pequenos, os famosos seis graus de separação.</p>
<p>Acho que na faculdade o povo ensina até o modelo de Erdös e Rényi. O tema de mundos pequenos explodiu por conta do aparecimento das redes sociais massivas no começo do século XXI. Porque agora temos dados reais massivos pra analisar, o tal Big Data, que foi material de pesquisa pra um Duncan Watts quando trabalhou no Yahoo e hoje na Microsoft. Daí você chega nos modelos de Watts-Strogatz e Barabási-Albert. Se você acha que entende redes sociais e nunca ouviu falar deles, ainda não sabe o que são redes sociais.</p>
<p>Isso tudo dito, deixa eu dar uns passos pra trás. Depois de aprender arrays, listas ligadas e hashtables acho que é importante fechar com árvores que, por si só, é um assunto bem grande, podemos ir pra B-trees que sendo honesto eu não lembro mais tudo de cabeça. B-trees é o que tá por trás de índices de bancos de dados, por exemplo. Mas falando genericamente de árvores, você vai ver essa estrutura em tudo que é lugar.</p>
<p>Exemplos simples? Basta ver seu Windows Explorer, Mac Finder ou GNOME Nautilus. Seu file system, o sistema de arquivos é inteiro baseado em árvores. Diretórios e arquivos são Nodes na árvore, a raíz pode ser o seu C:. Veja um documento HTML, qualquer página da Web. Sua representação é o famoso DOM ou Document Object Model, uma árvore onde cada Node, ou entidade, são elementos como parágrafos, forms, imagens e tudo mais. E falando em objetos, veja um Java da vida cuja biblioteca é toda herdada a partir da classe Object. Toda herança de classes e interfaces é representada numa árvore.</p>
<p>E sem árvores você não consegue seguir pras próximas matérias na ciência da computação como sistemas operacionais ou compiladores. Todo seu código fonte, seja numa linguagem compilada como C ou interpretada como Python ou Javascript passa por etapas como análise léxica, depois análise sintática, que gera coisas como um AST ou abstract syntax tree, que é a representação do seu código texto no formato de uma árvore. E mesmo pra entender sistemas operacionais você vai precisar de árvores.</p>
<p>Quando falei de memória virtual vimos dois exemplos de segmentos importantes. Primeiro a stack, que controla o estado da execução do programa a cada instrução e que é implementado como uma estrutura de dados linear de Pilha. Já o segmento Heap pode ser gerenciado com uma árvore. Na realidade o Heap usa uma estrutura de dados baseada em árvore chamada Heap. Um Heap pode ser uma implementação de um tipo de dados abstrato chamado Fila de Prioridade ou Priority Queue. São mais tipos abstratos como a Pilha ou Fila, que podem ser implementadas com as estruturas de dados primitivas que expliquei antes.</p>
<p>Se for uma binary heap, então é implementado usando uma binary tree, ou árvore binária. A distinção é importante porque nos exemplos de file system ou DOM de HTML as árvores não são binárias, porque cada Node não tem só dois elementos, eles podem ter múltiplos filhos ou até múltiplos pais, então são grafos mas não árvores binárias. Provavelmente B-trees, que vou explicar o que são mais pro final.</p>
<p>A estrutura de Heap é a usada no algoritmo de ordenação Heapsort. Lembram quando falei de Quicksort no episódio anterior? Tem outras nos livros de algoritmos como mergesort, insertion sort; e heapsort costuma aparecer junto. Mas o importante é saber que sua memória tá organizada numa árvore binária.</p>
<p>Uma árvore tem como característica começar de um Node raíz ou Root. Root pra quem não sabe, é tradução pra raíz, o primeiro Node. O primeiro usuário num Linux chama Root, por isso. Quando você digita caminhos de diretórios a primeira barra ao contrário, backslash, a gente chama de root do file system. Num Windows o “C:\” só é a raíz do seu primeiro HD que o Windows sempre chama de C (e pra quem não sabia é porque A e B eram reservados pra drives de disquetes). Mas enfim, você vai ver esse nome root ou raíz em vários lugares.</p>
<p>Lembram da Lista Ligada? É uma struct que tem um valor qualquer que queremos guardar e um ponteiro pra outro Node, o previous ou next no nosso exemplo. Numa árvore, cada Node vai apontar pra mais de um Node. Numa árvore binária, em particular, aponta pra 2 outros Nodes que comumente chamamos de <code>left</code> e <code>right</code> ou esquerda e direita.</p>
<p>Uma árvore pode ser balanceada ou não-balanceada. Numa árvore binária, não balanceada, onde vamos incluindo elementos, digamos, tudo pra direita da árvore, vai se comportar basicamente como uma lista ligada com complexidade O linear pra procura. Vamos dar um exemplo besta de árvore assim.</p>
<p>Pense numa lista de números quaisquer como vinte e sete, quatorze, trinta e cinto, dez, dezenove, trinta e um e quarenta e dois. Agora vamos imaginar uma Struct Node com ponteiros left e right pra outros Nodes. E vamos desenhar como ficaria na memória a versão ineficiente, onde só vou preencher o ponteiro right de cada Node. Veja como elas ficam sequenciais como se fosse uma lista ligada.</p>
<p>É o pior caso. Mas a coisa só fica interessante quando falamos em árvores binárias balanceadas ou pelo menos semi-balanceadas. E nesse caso o tempo de procura vai ser proporcional ao height, ou altura da árvore em vez de ser proporcional ao total de elementos inseridos.</p>
<p>De exemplo, podemos desenhar a mesma sequência mais bonitinho, de uma forma balanceada pra ter ambos left e right preenchidos. Veja como fica mais bonito, com uma altura tamanho 2, sem contar a raiz, em vez de ter altura 7 que era a altura da lista ligada. Mas até aqui não ganhamos muita coisa a não ser reorganizar a estrutura. Se eu quiser procurar onde tá a chave 31 ainda ia precisar sair comparando quase todos os Nodes e a procura seria O linear do mesmo jeito que na lista ligada. Vamos ver como melhorar isso.</p>
<p>Eu falei antes que um dos problemas de um array é quando queremos inserir um elemento no meio dele. Digamos, inserir na posição 5 de um array de 10 elementos. Tem duas formas. A primeira é copiar os elementos da posição 6 até 10 uma posição mais pra frente - se ainda tiver espaços vazios) e inserir o novo elemento na posição 6. Outra forma é se for uma lista ordenada, daí inserimos o novo elemento no fim do array e mandamos ordenar ele usando um Timsort ou Quicksort da vida.</p>
<p>Qualquer uma das formas pode ter uma complexidade maior que O linear, provavelmente loglinear que é a complexidade de um algoritmo de ordenação como Quicksort. Mas, e se fosse possível inserir um elemento numa lista pré-ordenada em complexidade menor que linear, talvez logaritmica?</p>
<p>Apesar do nome árvore, e apesar da imagem na cabeça ser algo como diretórios e arquivos numa hierarquia, pense em árvores binárias como se fosse uma lista mesmo. Aqui vem o negócio que se fala sempre sobre abstrações. O objetivo é o seguinte: ter uma lista ordenada que, no geral, é mais barato de procurar e mais barato de inserir ou apagar elementos do que um array ou lista ligada.</p>
<p>Árvores aparecem porque, como expliquei no video anterior, se temos um problema que podemos ir particionando, tipo quebrando uma lista ao meio, depois quebrando essa metade ao meio, isso é característica de algo logarítmico. Pense numa árvore visualmente como se fosse uma lista particionada. A raíz poderia ser o elemento mais do meio da lista e cada um de seus filhos tem metade da lista e assim sucessivamente até o fim da lista.</p>
<p>Mas só particionar não tem muita vantagem se a lista estiver bagunçada, tudo fora de ordem. O grande truque é ter essa árvore com os itens ordenados. Porém, o que significa Nodes ordenados numa árvore que não parece ter uma única direção? Quem é o primeiro elemento? Quem é o segundo? Vamos pegar essa sequência de 14 números aleatórios e organizar numa árvore binária ordenada. Veja como o número 0 tá mais pra esquerda, o 34 virou a raíz e mais pra direita tem números maiores como 99.</p>
<p>O único código que vou mostrar linha a linha hoje é da binary search tree ou árvore de procura binária. Vamos começar criando a struct Node, parecido com o da lista ligada e do hashtable. É muito simples, tem um campo pro número que vamos guardar. Daí os ponteiros de left e right pros Nodes filhos. Agora vamos direto fazer uma função main que vai popular essa árvore. Note que eu declarei a variável root só com o tipo Node em vez de <code>struct Node</code> como vim fazendo antes. Isso porque eu declarei ele como um novo tipo com <code>typedef</code> então <code>Node</code> é a mesma coisa que <code>struct Node</code>. Fica mais curto de escrever e agora vocês já viram do outro jeito pra ver a vantagem de usar assim.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

typedef struct Node {
    int data;

    struct Node* left;
    struct Node* right;
} Node;

Node* insert(Node *root, int data) {
    Node *temp = (Node*) malloc(sizeof(Node));

    temp-&gt;data = data;
    temp-&gt;left = NULL;
    temp-&gt;right = NULL;

    if (root == NULL) {
        root = temp;

    } else {
        Node *current = root;
        Node *parent = NULL;

        while(1) {
            parent = current;

            if (data &lt; parent-&gt;data) {
                current = current-&gt;left;

                if (current == NULL) {
                    parent-&gt;left = temp;
                    return root;
                }
            } else {
                current = current-&gt;right;

                if (current == NULL) {
                    parent-&gt;right = temp;
                    return root;
                }
            }
        }
    }
    return root;
}

Node* search(Node *root, int data) {
    Node *current = root;
    printf(&#34;Visiting elements: &#34;);

    while(current) {
        printf(&#34;%d &#34;, current-&gt;data);

        if (data &lt; current-&gt;data) {
            current = current-&gt;left;
        } else if (data &gt; current-&gt;data) {
            current = current-&gt;right;
        } else {
            return current;
        }
    }
}

void pre_order_traversal(struct Node* root) {
    if(root) {
        printf(&#34;%d &#34;, root-&gt;data);
        pre_order_traversal(root-&gt;left);
        pre_order_traversal(root-&gt;right);
    }
}

void inorder_traversal(struct Node* root) {
    if(root) {
        inorder_traversal(root-&gt;left);
        printf(&#34;%d &#34;, root-&gt;data);
        inorder_traversal(root-&gt;right);
    }
}

void post_order_traversal(struct Node* root) {
    if(root) {
        post_order_traversal(root-&gt;left);
        post_order_traversal(root-&gt;right);
        printf(&#34;%d &#34;, root-&gt;data);
    }
}

int _print_t(Node *tree, int is_left, int offset, int depth, char s[20][255])
{
    char b[20];
    int width = 5;

    if (!tree) return 0;

    sprintf(b, &#34;(%03d)&#34;, tree-&gt;data);

    int left  = _print_t(tree-&gt;left,  1, offset,                depth &#43; 1, s);
    int right = _print_t(tree-&gt;right, 0, offset &#43; left &#43; width, depth &#43; 1, s);

    for (int i = 0; i &lt; width; i&#43;&#43;)
        s[2 * depth][offset &#43; left &#43; i] = b[i];

    if (depth &amp;&amp; is_left) {

        for (int i = 0; i &lt; width &#43; right; i&#43;&#43;)
            s[2 * depth - 1][offset &#43; left &#43; width/2 &#43; i] = &#39;-&#39;;

        s[2 * depth - 1][offset &#43; left &#43; width/2] = &#39;&#43;&#39;;
        s[2 * depth - 1][offset &#43; left &#43; width &#43; right &#43; width/2] = &#39;&#43;&#39;;

    } else if (depth &amp;&amp; !is_left) {

        for (int i = 0; i &lt; left &#43; width; i&#43;&#43;)
            s[2 * depth - 1][offset - width/2 &#43; i] = &#39;-&#39;;

        s[2 * depth - 1][offset &#43; left &#43; width/2] = &#39;&#43;&#39;;
        s[2 * depth - 1][offset - width/2 - 1] = &#39;&#43;&#39;;
    }

    return left &#43; width &#43; right;
}

void print_t(Node *tree)
{
    char s[20][255];
    for (int i = 0; i &lt; 20; i&#43;&#43;)
        sprintf(s[i], &#34;%80s&#34;, &#34; &#34;);

    _print_t(tree, 0, 0, 0, s);

    for (int i = 0; i &lt; 20; i&#43;&#43;)
        printf(&#34;%s\n&#34;, s[i]);
}

int main() {
    int i;
    int array[] = { 34, 84, 15, 0, 2, 99, 79, 9, 88, 89, 18, 31, 39 };
    Node *root = NULL;

    for(i = 0; i &lt; 17; i&#43;&#43;) {
        root = insert(root, array[i]);
        printf(&#34;Inseriu: %d\n&#34;, array[i]);
    }

    Node *temp = search(root, 30);

    if(temp) {
        printf(&#34;[%d] Element found.\n&#34;, temp-&gt;data);
    } else {
        printf(&#34;[x] Element (%d) not found.\n&#34;, i);
    }

    temp = search(root, 88);

    if(temp) {
        printf(&#34;[%d] Element found.\n&#34;, temp-&gt;data);
    } else {
        printf(&#34;[x] Element (%d) not found.\n&#34;, i);
    }

    printf(&#34;\nPreorder traversal: &#34;);
    pre_order_traversal(root);

    printf(&#34;\nInorder traversal: &#34;);
    inorder_traversal(root);

    printf(&#34;\nPost order traversal: &#34;);
    post_order_traversal(root);

    printf(&#34;\n&#34;);
    print_t(root);

    return 0;
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Começamos com um array de 14 elementos, números aleatórios. E um Node chamado root pra ser a raíz. Fazemos um loop pra inserir todos os elementos desse array na árvore. Então já sabemos que vamos precisar de uma função chamada <code>insert</code>. Só pra ficar mais fácil de explicar e não precisar falar de ponteiro de ponteiros, a primeira variável vai ser o endereço pra raíz, que começa nulo e a função devolve o endereço da raíz de volta quando popular. Tem jeito melhor de fazer isso, mas pro exemplo isso é suficiente.</p>
<p>Chegou a hora de tentar procurar um elemento dentro da árvore criando outra função, a <code>search</code>. Mesma coisa dos outros episódios, vamos passar o endereço da raíz e o número que tamos procurando. Agora vamos voltar lá pro começo do arquivo e criamos essas funções, começando pela <code>insert</code> que vai receber um ponteiro pra raíz e o número que queremos inserir e retorna de volta o endereço da raíz.</p>
<p>Começamos criando uma instância de Node no heap com malloc e associando o endereço na variável <code>temp</code> de temporário. Daí preenchemos a estrutura com o número que passamos no campo <code>data</code> e inicializamos os ponteiros de left e right pra ser nulo. Aliás, importante, isso foi uma coisa que esqueci mesmo nos exemplos de lista ligada e hashtable. Se não inicializar os ponteiros com NULL, explicitamente, nada garante que no espaço alocado não havia bits de outra coisa lá e os campos podem começar já preenchidos com lixo, apontando pra algum lugar aleatório bizarro. Das duas uma, ou eu inicializo na mão como fiz agora gravando NULL ou em vez de <code>malloc</code> posso usar <code>calloc</code> pra que já faz essa limpeza.</p>
<p>Voltando, o começo é simples. Se a raiz, ou root, for nula como é o caso na primeira vez, ele passa a apontar pro mesmo lugar que a variável <code>temp</code>. Daí termina o <code>if</code> e dá return do novo root. Caso contrário, vamos fazer uma cláusula <code>else</code> e começamos criando um novo ponteiro temporário chamado <code>current</code> que aponta pro Node do root e outro ponteiro chamado <code>parent</code> de pai ou mãe, tanto faz, que começa nulo.</p>
<p>Começamos fazendo um loop infinito com <code>while(1)</code>. Se a gente não fizer nada dentro desse loop pra sair, vai ficar repetindo dentro do loop pra sempre. Dentro começamos fazendo a variável <code>parent</code> apontar pra <code>current</code> que, na primeira passada do loop é o primeiro Node que já tá inserido. O truque começa aqui.</p>
<p>Vamos checar dois casos, se o número que queremos inserir agora for menor que o número do parent. Se não for, cai no <code>else</code> que é o caso oposto, se o número que estamos inserindo for maior ou igual ao do Node parent. No nosso exemplo, a raíz é o número 34. O próximo número da lista é 84. Então ele cai no bloco de else.</p>
<p>Nesse bloco fazemos a variável current apontar pra direita do Node atual, do 34. E agora podemos ter dois casos. Se o Node current for nulo ou se já tiver outro Node. Nesse caso do 2o item ele ainda é nulo, então fazemos o ponteiro pra direita do node parent apontar pro temporário e saímos do loop dando return da raíz de novo.</p>
<p>Vamos completar o código com o próximo item da lista que é o 15. Vai ser um novo insert, passamos o root e o número 15, daí criamos um novo Node temp pra acomodar esse valor 15. Root não é nulo, então entramos no primeiro bloco <code>else</code>. Variável current aponta pro root, parent pra nulo. Entramos no <code>while</code> infinito. Agora fazemos a variável parent ser igual ao current porque vamos usar pra navegar pela árvore; e o parent sempre vai tá apontando pro Node imediatamente acima dela. Fica mais fácil de entender tendo esse diagrama na cabeça.</p>
<p>Agora checamos que 15 é menor que o valor no Node parent, que é raíz 34, então o current vai ser o espaço pra esquerda do Node atual. Por acaso, esse espaço ainda tá vazio, então vai passar a ser o Node 15 que acabamos de criar. Retornamos o root de novo e quebramos o loop infinito. E neste estágio temos uma lista com o 34 no topo, 15 na esquerda e 84 na direita. O código de inserção tá completo, vamos testar mais uma vez com o próximo número da lista, o zero.</p>
<p>Começamos no topo da função insert, passando a raiz e número 0. Criamos o node temporário que vai conter o zero, root não é nulo então seguimos criando o ponteiro current apontando pro Node 34 e parent nulo. Entra no loop infinito e começamos com o parent igual ao current que é a raiz.</p>
<p>Como zero é menor que o parent, que é o 34 ainda, fazemos o current ser o Node à sua esquerda, que é o 15. Sendo o current não nulo, então sai desse bloco e chega ao fim do while. Como o loop é infinito repetimos. Agora o parent vai ser o current 15. De novo, zero é menor que o atual parent que é 15 então fazemos o current ser o Node left à esquerda. Agora não tem ninguém lá, é nulo, e é onde vamos colocar o node zero. Pronto, atribuímos o left do parent 15 pra ser o node temporário zero e damos return pra sair do loop infinito.</p>
<p>Vou acelerar aqui do lado pra inserir os elementos que faltam, e a árvore vai ficando dessa forma. Obviamente não fica bonitinho assim na memória, mas cada Node tem ponteiros apontando pra até dois outros Nodes e podemos rearranjar num diagrama pra ficar visualmente assim. O importante é o seguinte: começa lendo do Node mais à esquerda e vai indo pra direita. Começa pelo zero, depois dois, nove, daí quinze, dezoito, trinta e um e chegamos na raíz trinta e quatro, continua pro trinta e nove, setenta e nove e vai indo até o noventa e nove. Notaram como a lista tá ordenada?</p>
<p>Antes de mostrar como fica a função search, acho que vale mostrar como pegar de volta a lista ordenada e mostrar na tela. Temos 3 formas de navegar por essa lista, pre order, post order e in order. Pre order começa da raiz e vai pra esquerda primeiro, depois pra direita. Indo de cima pra baixo. Post order começa do primeiro node mais à direita, no caso o nove e vai subindo, de baixo pra cima.</p>
<p>In order, como o nome diz é na ordem. E qualquer um desses casos é simples usando recursão. Primeiro vamos colocar na main a chamada pra função inorder_traversal passando o node raiz. Precisamos criar essa função. Ela começa checando se o node passado é nulo, se for, não faz nada e retorna. Caso contrário chama ela mesma passando o Node à esquerda.</p>
<p>Com os nossos valores de exemplo, se a gente pensar como ficaria a Stack do programa, vamos começar chamando inorder pro Node 34. Ela não é nula. Então chama ela mesma de novo passando o Node 15. Acumula no topo da Stack porque não retornamos ainda. Agora o node continua não sendo nulo, então chama ela de novo com o próximo node à esquerda, que é o do zero. Empilha na Stack, de novo. E quando chamarmos outra vez, o Node zero não é nulo, então passamos o node à esquerda dela que, agora é nulo. Empilhamos.</p>
<p>Agora a função vai receber nessa variável root um nulo, então o <code>if</code> vai ser verdadeiro, não faz nada e retorna. Desempilha da Stack. Voltamos pra chamada anterior que vai continuar do Node 0 e dar <code>printf</code> na tela. Vai imprimir zero e agora vamos chamar a função de novo passando quem tá à direita do Node 0, e tem o Node 2. Empilhamos.</p>
<p>Mesma coisa, prestem atenção, O node não é nulo, agora chamamos a função passando o ponteiro pro Node à esquerda do 2, por acaso é nulo. Empilhamos. Já sabemos que o <code>if</code> vai checar a variável como nulo, não faz nada e desempilha. Agora dá <code>printf</code> pro 2 e chama a função passando quem tá à direita do dois. Por acaso é o Node 9. Empilha.</p>
<p>Mesma coisa, node não é nulo, chamamos a função pro node à esquerda do nove que é nulo, já sabemos que vai empilhar, entrar e desempilhar porque é nulo, então vamos pular e na sequência vai dar <code>printf</code> do 9. Agora tenta chamar a função pro Node à direita do 9, que é nulo. De novo, vai empilhar, checar que é nulo, não faz nada e desempilha. E acaba a função pro 9, retorna e desempilha.</p>
<p>A função do 2 também termina, desempilha. A chamada pro zero também acaba e desempilha. Voltamos pro Node 15. Agora continua dando <code>printf</code> pra ele, imprime 15 na sequência do 9 e chama a função pro node à direita do 15, que vai ser o 18. Empilha. Node não é nulo então chama a função pro node à esquerda, que é nulo, podemos pular porque sabemos que vai empilhar e desempilhar. Continua dando <code>printf</code> pro 18. Agora chama a função pro Node 31 à direita.</p>
<p>Pausa, agora olha só, já imprimiu o zero, dois, nove, quinze, dezoito e o próximo vai ser 31, tudo na ordem crescente, ordenadinho e se continuar fazendo a mesma coisa vai imprimir até o 99 na ordem. Se você nunca viu isso antes deve ter dado um nó na cabeça. Mas é justamente isso que precisa acontecer, porque essa estrutura e esse jeito de operar não é intuitivo se você nunca estudou estruturas de dados. Por intuição você vai parar nas listas lineares e nunca vai entender o que vem depois, que são grafos.</p>
<p>Ao final, só de ter inserido um monte de números aleatórios na árvore binária conseguimos pegar os elementos já ordenados. O mesmo resultado poderia ser obtido inserindo os números num array e passando um Quicksort da vida neles. Mas entenda a diferença. Se for considerar puramente só inserção de novos valores, é mais rápido colocar num array, porque é complexidade O constante. Já na árvore vai ser O logaritmico. Ou seja, a inserção na árvore é mais lenta.</p>
<p>Mas a comparação é injusta. A complexidade de inserir elementos numa árvore de procura binária é O logaritmico porque ele já insere os elementos ordenados. Já no array é O constante pra inserir no fim porque não estamos ordenando. Se também quisermos o array ordenado a cada inserção, precisaria rodar um quicksort no mínimo uma vez no final, daí vai ficar mais caro que O loglinear.</p>
<p>Quando o array estiver todo ordenado, podemos pesquisar usando procura binária. Eu não vou mostrar código, mas é assim. O jeito ingênuo de procurar num array é fazer um loop e ir comparando elemento a elemento da esquerda pra direita até achar o 88. Você ia precisar de 10 operações de comparação pra chegar lá. Mas tem um jeito mais rápido que só funciona se a lista já tiver sido ordenada antes.</p>
<p>Você pega o tamanho do array e divide por dois, no caso vai ser 7. Daí pega o endereço do primeiro elemento e soma pelo tamanho de cada elemento multiplicado por sete, que é o elemento 39, mais ou menos no meio da lista. Agora compara, 88 é menor que 39? Não é, então já sabemos que não tá na metade à esquerda do 39 e não precisamos olhar nenhum deles. Só de fazer isso já eliminamos metade da lista que não precisamos nos preocupar.</p>
<p>Recursivamente fazemos a mesma coisa, à direita do 39 temos 6 elementos sobrando, dividido por 2 são 3, somado à posição 7 chegamos na posição 10, que é o meio da metade da direita. E vai ser o elemento 88 por acaso. Comparamos com o valor procurado 88 e já chegamos nele. Ou seja, com 2 operações de endereço pra chegar no meio do meio e 2 operações de comparação chegamos no 88. 4 operações no total, contra os 10 do jeito ingênuo, menos da metade do processamento. E isso porque essa lista é muito pequena. Quanto maior a lista maior vai ser a economia.</p>
<p>Por isso tem vantagem trabalhar com listas ordenadas. Porém, como disse antes, custa O n vezes log de n, loglinear pra ordenar um array. E dependendo do algoritmo, pode custar espaço na memória pra fazer essa ordenação. Um mergesort por exemplo, acho que era um que custava mais caro em espaço extra pra conseguir ordenar.</p>
<p>Lembram da animação que mostrei de mergesort no episódio anterior? Vamos ver de novo. Estão vendo que ele vai guardando valores aqui embaixo? Isso é o espaço temporário que o mergesort gasta pra conseguir ordenar a lista em cima. Então, não é só velocidade de processamento pra se preocupar como também espaço temporário de memória que se gasta. Agora pensa na árvore. Ela não custa muito espaço adicional pra ordenar, e custa literalmente O logaritmico, tudo bem que é pra cada elemento, mas a complexidade é uma ordem de grandeza mais rápida que qualquer ordenação de array.</p>
<p>E uma vez tendo a árvore ordenada, a procura é parecida com o que acabamos de fazer com procura binária de array. Vamos ver. Voltando ao exemplo, precisamos implementar a função <code>search</code>, que vai começar recebendo como argumento o Node raiz da árvore e o valor que estamos procurando, no caso o 88.</p>
<p>Criamos uma variável temporária chamada <code>current</code> apontando pro <code>root</code>. De novo criamos um loop com <code>while</code> que vai quebrar se o node current for nulo. E isso só vai acontecer se a gente navegar pela árvore e nenhum dos Nodes tiver o valor que procurado. Agora é a mesma coisa que na busca binária de array. A inserção garante que nossa árvore binária já tá ordenada como acabei de mostrar.</p>
<p>Vamos comparar o número 88 com o valor do current que é a raíz 34. Como não é menor, pula pro <code>else if</code>. Como é maior faz o <code>current</code> ser o Node 84. Repete o loop. 88 ainda não é menor que 84, pula pro <code>else if</code> de novo e faz current ser o node à direita, right, que é o 99. Repete o loop. Agora sim, 88 é menor que 99 então vamos fazer o current pegar o Node à esquerda de 99, o ponteiro left. Repete o loop. Agora o Node que achamos não é menor, não é maior, então finalmente cai no último <code>else</code> e pronto, achamos o 88.</p>
<p>Em quantidade de comparações é equiparável à busca binária de array ordenado, ordem de grandeza Big O logaritmico porque ambos os casos estamos particionando. Entenda a relação. Inserir elementos no fim de um array é rápido, O constante, caso tenha espaços vazios sobrando no final. Se não tiver, precisa criar um novo array maior e copiar os elementos da anterior pra ela. Complexidade O linear. Se quiser que a lista esteja ordenada pra conseguir procurar via busca binária, vai custar O loglinear de um quicksort da vida. No hashtable não se ordena porque a posição depende do hashing da chave. A procura vai ser O linear também.</p>
<p>Então a pesquisa numa árvore binária vai ser melhor do que num array, lista ligada ou hashtable. Tudo depende de pra que você vai usar a lista. Pra guardar os campos que vem num formulário simples de cadastro, qualquer lista serve, são poucos elementos. Mesmo na força bruta ingênua, a diferença é pouca. Agora, pra gerenciar coisas como um cache de banco de dados, você vai escolher alguma coisa melhor que um hashtable ou uma árvore binária.</p>
<p>Não chegamos no fim da história. Essa nossa árvore binária rudimentar é só a base. O caso ruim pra árvore binária, da forma como implementamos, é tentar adicionar uma lista de números consecutivos. Por exemplo, vamos começar de novo a partir de 99 e tentar inserir cem. Como é maior que 99, vai pra direita. Agora cento e um. Como é maior que 99, vai pro 100. Sendo maior que 100, vai pra direita. Cento e dois, mesma coisa, quando chegar no cento e um, vai pra direita. E se continuarmos adicionando, olha como ficou nossa árvore. Parece um galho seco esperando pra cair, ficou basicamente como uma lista ligada, o pior caso.</p>
<p>Pior caso porque, sendo praticamente uma lista ligada, a complexidade pra procurar deixou de ser O logarítmico e voltou a ser O linear. Ou seja, pra achar o último elemento, vai precisar comparar com todos os outros anteriores. Uma árvore assim é desbalanceada. A gente precisa balancear e distribuir em outros galhos dessa árvore.</p>
<p>O que vimos até aqui é chamado de árvore de procura binária ou binary search trees, cujo acrônimo é BST. O que costuma ensinar depois é como balancear essas BSTs e uma das formas de fazer isso é o que se chama de RBTrees ou Red Black Trees. Antes que alguém saia correndo pros comentários, vou falar sobre AVLs já já, calma aí.</p>
<p>Uma das razões de eu querer pular esse assunto é porque realmente não estou com vontade de explicar linha a linha de como se faz inserções numa árvore Red Black. É muito detalhezinho, vai dar um puta trampo pra editar e indo linha a linha num video vai ser maçante pra caramba. Então, recomendo que vocês procurem o código em qualquer linguagem e testem linha a linha na máquina de vocês. Em vez disso, vou fazer diferente aqui.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>#include&lt;stdio.h&gt;
#include&lt;stdlib.h&gt;

#define RED   &#39;R&#39;
#define BLACK &#39;B&#39;

#define COUNT 10

typedef struct Node {
    int          key;
    char         color;
    struct Node *left;
    struct Node *right;
    struct Node *parent;
} Node;

// Based on CLRS algorithm, use T_Nil as a sentinel to simplify code
struct Node  T_Nil_Node;
Node* T_Nil = &amp;T_Nil_Node;

Node* Root = NULL;

// A utility function to create a new BST node
Node* newNode(int key)
{
    Node *temp   = (Node*) malloc(sizeof(Node));
    temp-&gt;key    = key;
    temp-&gt;color  = RED;
    temp-&gt;left   = NULL;
    temp-&gt;right  = NULL;
    temp-&gt;parent = NULL;

    return temp;
}

void rotateLeft( Node** T, Node* x)
{
    Node *y  = x-&gt;right;    // set y
    x-&gt;right = y-&gt;left;     // turn y&#39;s left subtree into x&#39;s right subtree{
    if (y-&gt;left != T_Nil)
        y-&gt;left-&gt;parent = x;
    y-&gt;parent = x-&gt;parent;  // link x&#39;s parent to y
    if (x-&gt;parent == T_Nil)
        *T = y;
    else if (x == x-&gt;parent-&gt;left)
        x-&gt;parent-&gt;left = y;
    else
        x-&gt;parent-&gt;right = y;
    y-&gt;left   = x;            // put x on y&#39;s left
    x-&gt;parent = y;
}

void rotateRight(Node** T, Node* y)
{
    Node *x  = y-&gt;left;     // set x
    y-&gt;left  = x-&gt;right;    // turn x&#39;s right subtree into y&#39;s left subtree{
    if (x-&gt;right != T_Nil)
        x-&gt;right-&gt;parent = y;
    x-&gt;parent = y-&gt;parent;  // link y&#39;s parent to x
    if (y-&gt;parent == T_Nil)
        *T = x;
    else if (y == y-&gt;parent-&gt;right)
        y-&gt;parent-&gt;right = x;
    else
        y-&gt;parent-&gt;left  = x;
    x-&gt;right  = y;         // put y on x&#39;s right
    y-&gt;parent = x;
}

void redBlackInsertFixup(Node** Root, Node* New)
{
    Node* temp;
    while (New-&gt;parent-&gt;color == RED)
    {
        if (New-&gt;parent == New-&gt;parent-&gt;parent-&gt;left)
        {
            temp = New-&gt;parent-&gt;parent-&gt;right;
            if (temp-&gt;color == RED)
            {
                temp-&gt;color = BLACK;
                New-&gt;parent-&gt;color = BLACK;
                New-&gt;parent-&gt;parent-&gt;color = RED;
                New = New-&gt;parent-&gt;parent;
            }
            else {
                if (New == New-&gt;parent-&gt;right)
                {
                    New = New-&gt;parent;
                    rotateLeft(Root, New);
                }
                New-&gt;parent-&gt;color = BLACK;
                New-&gt;parent-&gt;parent-&gt;color = RED;
                rotateRight(Root, New-&gt;parent-&gt;parent);
            }
        }
        else
        {
            temp = New-&gt;parent-&gt;parent-&gt;left;
            if (temp-&gt;color == RED)
            {
                New-&gt;parent-&gt;color = BLACK;
                New-&gt;color = BLACK;
                New-&gt;parent-&gt;parent-&gt;color = RED;
                New = New-&gt;parent-&gt;parent;
            }
            else {
                if (New == New-&gt;parent-&gt;left)
                {
                    New = New-&gt;parent;
                    rotateRight(Root, New);
                }
                New-&gt;parent-&gt;color = BLACK;
                New-&gt;parent-&gt;parent-&gt;color = RED;
                rotateLeft(Root, New-&gt;parent-&gt;parent);
            }
        }
    }
    Root[0]-&gt;color = BLACK;
}
void redBlackInsert(Node** T, int key)
{
    Node* z =  newNode(key);
    Node* y =  T_Nil;
    Node* x = *T;

    // Find where to Insert new node Z into the binary search tree
    while (x != T_Nil) {
        y = x;
        if (z-&gt;key &lt; x-&gt;key)
            x = x-&gt;left;
        else
            x = x-&gt;right;
    }

    z-&gt;parent = y;
    if (y == T_Nil)
        *T = z;
    else if (z-&gt;key &lt; y-&gt;key)
        y-&gt;left  = z;
    else
        y-&gt;right = z;

    // Init z as a red leaf
    z-&gt;left  = T_Nil;
    z-&gt;right = T_Nil;
    z-&gt;color = RED;

    // Ensure the Red-Black property is maintained
    redBlackInsertFixup(T, z);
}

#define MAX(a,b) (((a)&gt;(b))?(a):(b))

int height(Node* Root)
{
    int h = 0;

    if (Root != NULL) {
        if (Root == T_Nil)
            h = 1;
        else
        {
            int leftHeight  = height(Root-&gt;left);
            int rightHeight = height(Root-&gt;right);
            h = MAX(leftHeight, rightHeight) &#43; 1;
        }
    }

    return h;
}

void printTree(Node* root)
{
    if (root-&gt;left != T_Nil)
        printTree(root-&gt;left);
    printf(&#34;%d &#34;, root-&gt;key);
    if (root-&gt;right != T_Nil)
        printTree(root-&gt;right);
}

void padding ( char ch, int n ){
  int i;
  
  for ( i = 0; i &lt; n; i&#43;&#43; )
    putchar ( ch );
}

void structure ( struct Node *root, int level ){
  int i;
  
  if ( root == NULL ) {
    padding ( &#39;\t&#39;, level );
    puts ( &#34;~&#34; );
  } else {
    structure ( root-&gt;right, level &#43; 1 );
    padding ( &#39;\t&#39;, level );
    if(root-&gt;color) {
        printf ( &#34;%d,%c\n&#34;, root-&gt;key, root-&gt;color );
    } else {
        printf ( &#34;%d,N\n&#34;, root-&gt;key );
    }
    structure ( root-&gt;left, level &#43; 1 );
  }
}

int main(int argc, char* argv[])
{
    Node* Root = T_Nil;
    int list[14] = { 34, 84, 15, 0, 2, 99, 79, 9, 88, 89, 18, 31, 39 };
    //int list[10] = {1, 3, 2, 5, 4, 7, 6, 9, 8, 10};
    for (int i = 0; i &lt; 14; i&#43;&#43;)
    {
        //printf(&#34;%d &#34;, list[i]);
        redBlackInsert(&amp;Root, list[i]);
        printf(&#34;Inserindo %d\n&#34;, list[i]);
        structure(Root, 0);
    }
    printTree(Root);

    structure(Root, 0);
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O que muda numa árvore binária normal e numa Red Black? Pra começar o Node da árvore vai conter um campo extra de um bit, que pode ser zero ou um. Tanto faz se você chama zero de Red e um de Black ou vice versa, mas uma regra inviolável é que o Node raiz é sempre preto.</p>
<p>Por padrão, todo Node novo que formos criando vai começar sendo da cor vermelha. Mas depois de inserido na árvore precisamos checar a cor do pai e dos filhos, porque a regra é que um Node vermelho só pode ter pais e filhos pretos. Se depois de inserir não estiver assim, precisamos repintar os Nodes pra encaixar na regra. Parece estranho mas você vai entender.</p>
<p>Além disso, todo Node no fim da árvore, que você pode chamar de folhas ou leafs, e terminaria com os ponteiros left e right sendo nulo, vão apontar pra um Node de serviço que muitos nomeariam como Null Node ou TNil. Você pode chamar como quiser, mas é um Node que serve só pros ponteiros left e right dos últimos Nodes não serem nulo e seguir a regra das cores. Como é tudo ponteiro, todo mundo no final vai só apontar pra esse Node mas no diagrama, pra ficar mais bonito, vai parecer que cada um tá apontando pra um Node extra. Só lembrem que é tudo o mesmo Node TNil no final.</p>
<p>Uma das razões de ter esses Nodes no final é a regra que se contarmos todos os nodes entre a raíz e esse Nulo deve sempre ter o mesmo número de Node pretos. Na prática isso só vai acontecer se a árvore tiver a altura balanceada. Pra entender isso de balanço considere nossa árvore binária de exemplo. Conseguem ver como alguns galhos pendem mais pra um lado só? Os nodes 0 ou 15 pendem tudo pra direita. Do node 84 pende tudo pra esquerda.</p>
<p>Pra árvore ficar balanceada, precisamos fazer uma manutenção, movendo ponteiros de lugar durante a inserção. Lembra aquela ideia de fazer swap de ponteiro que eu falei no episódio anterior? É parecido aqui. Swaps ou trocas, são operações rápidas. Só muda uns dois ou três ponteiros de lugar, independente da quantidade total de Nodes, então é uma operação de complexidade O 1, constante.</p>
<p>Essa operação em árvores não é exatamente um swap, mas sim uma rotação. E isso tudo dito, vamos dar um exemplo visual com a mesma lista de números do exemplo, mas agora inserindo com o algoritmo de inserção de uma árvore Red Black. O 34 é a raíz, por isso ela é preta por padrão e seus ponteiros de left e right vão apontar praquele Node Nulo TNil. O próximo número é 84, que por padrão entra vermelho e igual na árvore binária vai pra direita porque é maior que 34. O próximo número 15 é menor que 34 então vai pra esquerda e também é vermelho por padrão.</p>
<p>Até aqui tudo bem, mas agora vamos inserir o número 0. Pelas mesmas regras, é menor que 34, menor que 15 então vai indo pra esquerda. E inserimos o Node zero vermelho. Mas a regra é que um node vermelho não pode ter um pai vermelho como o Node 15 em cima dele. Aqui começa o trabalho de zeladoria recolorindo o Node 15 e o Node 84 pra preto. Agora a regra foi satisfeita.</p>
<p>Sobre o caminho mais curto que falei, olha só. Quantos nodes pretos tem entre o 34 e o Nil embaixo do 0? 34 é preto, 1. 15 é preto, 2. 0 é vermelho, continua 2, e nil é preto, 3. Conseguem ver que qualquer outro caminho continua sendo 3 nodes pretos? Então as regras continuam satisfeitas. Vamos continuar tentando inserir o número 2.</p>
<p>E aqui a coisa ficou diferente. Olha o que aconteceu. Tentamos inserir o número 2. Pela regra da árvore binária, é menor que 34 então vai pra esquerda. É maior que zero então vai pra direita. Agora a árvore ficou desbalanceada. Por que? Pra começar temos dois Node vermelhos um atrás do outro. E o irmão do Node 0, que é o ponteiro pra direita do Node 15 tá vazio. Isso não pode acontecer, a ideia é que os nodes mais pra cima tenham left e right preenchidos, evitando ponteiros nulos antes de chegar nas folhas. Precisamos fazer a tal rotação.</p>
<p>Primeiro rotacionamos pra esquerda, subimos o Node 2 na árvore e descemos o Node 0 pra esquerda. Depois rotacionamos pra direita. Subimos o Node 2 pro mesmo andar da 15 e descemos o 15 pra direita. Finalmente repintamos o Node 2 e o Node 15 e pronto, voltamos a cumprir todas as regras do jogo. Nenhum Node vermelho tem pai ou filhos vermelhos e todos os caminhos do Node 34 até os nulos continua tendo o mesmo número de Node pretos, no caso 3. Vamos continuar.</p>
<p>Os números noventa e nove, setenta e nove e oitenta e oito vão sendo inseridos do mesmo jeito que na árvore binária. Mas agora vamos ver o 89. Mesma coisa, é maior que 34, vai pra direita. É maior que 84 vai pra direita. É menor que 89, vai pra esquerda e, sendo maior que 88, vai pra sua direita. Alerta! Temos dois nodes vermelhos um atrás do outro e o irmão do 88, ou seja o ponteiro pra direita do 89 tá vazio. Hora de rotacionar.</p>
<p>Começa rotacionando pra esquerda, daí o 89 passa a ser o pai de 88 e filho de 99. Rotacionamos pra direita e 99 passa a ser filho de 89 e irmão do 88. Vamos repintar o 88 e 89 e agora estamos cumprindo todas as regras de novo. Conseguem ver que se compararmos com a árvore binária de antes, agora parece visualmente mais balanceada? Ou seja, todos os Nodes antes do andar mais de baixo tem ambos os ponteiros left e right preenchidos, sem buracos sobrando.</p>
<p>Vamos continuar os outros números até o fim da lista sem parar mais. Continuamos com dezoito, trinta e um e trinta e nove. Daí escondemos os Nodes nulos no final pra ficar mais fácil de comparar. Olhem como a árvore Red Black e a árvore binária ficaram com um layout bem diferente. É assim que uma árvore binária balanceada se parece usando o método de Red Black.</p>
<p>Em termos de espaço extra sendo usado, é barato. Só um bit a mais pra cada Node, fora os ponteiros pro Node nulo no final de cada folha. E mesmo esse espaço tem como economizar e vou falar disso já já. O tempo de inserção é definitivamente mais caro, mas ainda assim na ordem de grandeza de O logaritmico.</p>
<p>Ou seja, em comparação com a árvore de procura binária, tá um pouco mais lento, mas a ordem de grandeza de complexidade não ficou muito mais caro. Procurar por itens nessa árvore é a mesma coisa da árvore de procura binária. Uma busca binária. E pra apagar vai ser um pouco mais lento também. Assim como na inserção precisa manter a árvore balanceada, então toda vez precisa checar se precisa rotacionar e recolorir.</p>
<p>Como eu prometi, ainda temos árvores AVL pra comentar, que chama assim por causa dos autores Adelson-Velsky e EM Landis, de 1962. Em resumo, diferente do sistema de bit de cor do Red Black, cada Node num AVL tem um número de balanço que pode ser menos um, zero ou um, pra árvore ser balanceada. Se for mais que menos dois ou dois, a árvore tá desbalanceada e precisa rotacionar e recalcular o balanço.</p>
<p>Embora ambos AVL e Red Black tenham operações com complexidade na ordem de grandeza O logaritmico, a procura numa árvore AVL é um pouco mais rápida, porque a Red Black ainda pode acabar menos balanceada que a AVL. A AVL é mais agressiva em manter a árvore estritamente balanceada. A inserção da AVL acaba sendo um pouco mais cara que a Red Black pra atingir isso. É sempre um trade-off. Se você fizer mais inserções do que procuras, um Red Black é melhor. Se o tempo de inserção não for importante, mas pra você o tempo de procura for muito importante, então talvez AVLs sejam melhores.</p>
<p>Mais do que isso, diferente dos Nodes de Red Black que são só 1 bit, os nodes de AVL precisam de dois bits extras, então vai ocupar um pouco mais espaço na memória. Além disso, os Blacks tem outra vantagem dependendo da implementação. Lembram quando falei sobre números inteiros com sinal e sem sinal? Se a gente souber que os números que vão ser armazenados na árvore sempre é positivo, podemos usar o primeiro bit, que normalmente seria pro sinal pra ser a cor do Node, daí economizamos esse bit a mais na estrutura de Node.</p>
<p>E só pra piorar o caso do AVL, eu disse antes que as rotações de Red Black são simples, complexidade O 1 constante. Mas as inserções de AVL podem ter mais rotações mais complicadas, daí o tempo varia mais. Por tudo isso, na dúvida, a escolha padrão acaba sendo Red Black, e AVL em casos especiais que você sabe que precisa das propriedades específicas dele.</p>
<p>Vou deixar linkado na descrição abaixo um documento do site da kernel do Linux que explica mais sobre como árvores Red Black são usados no Kernel. Parafraseando, os schedulers de I/O deadline e CFQ usam rbtrees pra rastrear requisições. RBTree você lembra que são Red Black Trees. O pacote de CD e DVD faz o mesmo. O código de timer de alta resolução usa rbtrees. O filesystem ext3 rastreia diretórios em uma rbtree. VMAs ou áreas de memória virtual usam rbtrees.</p>
<p>Seu Node.js e todo servidor com I/O assíncrono, por baixo usa o epoll do Linux. Rastreamento de descritores de arquivos, chaves criptográficas e scheduler de pacotes de rede usam rbtrees. Só pra comparar, lembram nosso código de exemplo de procura numa árvore binária? Vamos ver como essa documentação do site da kernel sugere fazer. E olha só, é bem parecido né? As mesmas comparações pra esquerda, pra direita ou quando encontra o Node. Meu exemplo foi rudimentar mas a estrutura básica é a mesma nos casos mais complexos.</p>
<p>Não sei porque as linguagens mais populares interpretadas não tem árvores Red Black por padrão. Eu diria que elas deveriam, porque assim como outras estruturas de dados, seriam melhor implementadas em C ou C++ do que em alto nível como Javascript ou Python. A classe <code>std::map</code> do C++ implementa árvores Red Black, assim como as classes TreeSet ou TreeMap do Java. Se for uma linguagem compilada como Go ou Rust, você pode baixar do GitHub alguma uma implementação que funciona em boa velocidade porque são compiladas. Mas tirando Java e C++ não sei de nenhuma outra que já vem embutida. Se alguém souber mande nos comentários.</p>
<p>Mas lembrem-se, árvores Red Black e AVL tem como objetivo chegar numa árvore de procura binária, como no nosso exemplo anterior, mas balanceadas. Os algoritmos de inserção que usam o sistema de cores ou balanço e rotação servem pra nos dar uma árvore balanceada que vai oferecer o menor tempo médio de procura binária. Mas o que vimos até agora continua sendo uma árvore de procura binária, com Nodes que só tem um valor e dois ponteiros, um pra esquerda e outro pra direita.</p>
<p>Depois disso ainda existe a B-Tree que é uma estrutura de árvore que também é auto-balanceada, ou seja, durante a inserção e remoção de Nodes ele faz a zeladoria de se rebalancear. Mas em vez de Nodes que só tem dois filhos, num B-Tree os Nodes podem ser X filhos maiores que 2. E mais coisas que não vou detalhar aqui. A definição vem do livro do Knuth e todo mundo usa a mesma, então pesquisem sobre B-Trees como lição de casa. Visualmente é algo como nessa imagem do lado. Ainda uma árvore, idealmente balanceada, mas com mais filhos por Nodes.</p>
<p>E indo mais longe chegamos na B PLUS Trees, que você pode pensar como B-Trees mas onde os Nodes não contém os valores em si. Em vez disso, aponta pra folhas, e esse nodes folha é que vão ter os valores que você quer armazenar. Os Nodes pais vão ter só as chaves que identificam esses valores. E é essa árvore que você vai ver em tudo que usa armazenamento orientado a blocos.</p>
<p>Eu tô pensando em fazer um episódio falando sobre blocos de armazenamento. É por isso que se você já brincou com AWS ou Google Cloud ou Azure, já deve ter visto o termo “block storage” pra identificar HDs virtuais. Por agora só entenda que é isso que faz B+ Trees serem ideais pra coisas como filesystems e bancos de dados. Tudo que você imaginar que armazena dados em disco provavelmente é uma B+ Tree ou derivação. Exemplo. Os filesystem de Linux como ReiserFS, EXT4. O NTFS do Windows. O APFS dos Macs. A maioria dos bancos de dados relacionais como DB2, SQL Server, Oracle, SQLite e por aí vai. É tudo B+ Trees.</p>
<p>Falando em bancos de dados. Pra entender como os dados são armazenados em disco, você precisa entender B+ Trees. Se você já usou qualquer MySQL ou Postgres da vida vai lembrar que tem a boa prática de sempre criar índices pra melhorar o tempo das suas pesquisas. E se você leu stackoverflow o suficiente também vai lembrar que povo fala pra não sair criando índice pra tudo que é lado, como se não houvesse amanhã, senão vai acabar com a performance. E aí você fica sem saber o que fazer, cria ou não cria índices então?</p>
<p>Se você entendeu a ideia de pegar uma lista e colocar numa árvore de procura binária balanceada e a idéia de hashing da hashtable do episódio anterior, meio que já entendeu uma parte da charada. O que são registros de um banco? Pense como se fosse uma struct de C, como as que usamos em todos os exemplos até agora. Agora pense em gravar os bits de instâncias de structs no disco em vez da memória. Uma tabela de banco de dados grande pode ter milhares ou milhões de registros. Seria bem lento sair procurando registro a registro como numa lista ligada. Mesmo se for uma árvore.</p>
<p>Diferente de ler da memória, pense que num disco mecânico você precisa sair caçando bits em trilhas num disco. E ficar caçando endereços assim ia ser bem demorado. Mas se não tem nenhum índice, é isso que vai acontecer mesmo. Pelo menos fazendo uma busca binária, você vai eliminando uma boa parte de registros que não precisa vasculhar, mas em milhões de registros, mesmo com busca binária vai ser devagar. É o que muitos já devem ter visto nos seus bancos de dados favoritos quanto a pesquisa cai num table scan.</p>
<p>Quando você procura por chave primária ou por um campo indexado é bem mais rápido. Pense num índice como uma segunda árvore, uma B-tree que é a tal árvore onde cada Node pode ter vários filhos, e é auto-balanceada, ou seja, inserções e deleções re-balanceiam a árvore via operações como rotação. Ela fica mais compacta porque é como se fosse uma cópia da sua tabela só que com os campos indexados.</p>
<p>Imagina num exemplo simples de árvore de procura binária, como esse na imagem aqui do lado. Se eu fizer uma pesquisa pra achar todos os salários maiores que 10 mil, depois de eliminar a metade esquerda da árvore. Agora temos os IDs ou endereços de onde esses registros estão na outra árvore no disco, e podemos buscar só elas.</p>
<p>E porque não é bom criar um montão de índices? Pense em inserir uma tabela como inserir numa árvore Red Black. Pra manter a árvore balanceada, pra acelerar pesquisas, você precisa repintar e rotacionar toda hora. Cada índice da sua tabela é como se fosse uma árvore dessas. Quanto mais índices tiver, mais caro fica inserir em várias delas, porque você precisa inserir na tabela propriamente dita e depois atualizar cada um dos índices que você criou. Índices são bons pra acelerar as pesquisas, mas o custo é aumentar o preço de operações de inserts, updates e deletes.</p>
<p>De qualquer forma o objetivo de hoje não é detalhar como bancos de dados funcionam, mesmo porque eu mesmo não sei tudo de cabeça Mas só de saber que seu sistema de arquivos, seu banco de dados usam uma implementação de B+ Trees e que os índices que tornam suas pesquisas mais rápidas e suas inserções mais lentas, provavelmente é uma implementação de B-Trees, já vai ajudar você a procurar mais material pra estudar.</p>
<p>Uma última coisa é não confundir B-Trees com Binary Trees ou árvores binárias. Esse B é de Balanced Trees, árvores balanceadas. E balancear árvores tem um custo, as tais operações de rotação por exemplo. Mas no fim do dia, embora você pense visualmente numa árvore como uma hierarquia, na verdade é uma lista ordenada, como um array ou uma lista ligada, mas estruturada de forma diferente. De forma não linear.</p>
<p>A complexidade de cada tipo de algoritmo em cada tipo de estrutura de dados tem complexidades diferentes de processamento e uso de recursos. Alguns são rápidos mas consomem mais memória. Outros são mais devagar na inserção mas mais rápidos na procura. Casos excepcionais podem fazer um algoritmo O logaritmico cair pra O linear ou até mesmo O loglinear.</p>
<p>Outras coisas que você deve prestar atenção. Se você partir do zero, de C, sozinho, é possível que compreenda um pouco sobre as complexidades de manipular e procurar elementos num array. Com um pouco de prática, vai naturalmente chegar numa lista ligada. Acho difícil chegar num Quicksort sozinho, mas se por acaso esbarrar em algum texto sobre particionamento e logaritmos, talvez chegue em algo parecido pra ordenação e busca binária. John Von Neumann chegou no mergesort.</p>
<p>Se você se esforçar bastante, talvez consiga evoluir da lista ligada e array pra hashtable e árvore de procura binária desbalanceada que vimos hoje. Mas eu acho muito improvável você sozinho, sem a ajuda de nenhum livro, chegar na idéia de árvore balanceada e mecanismos como Red Black, AVL ou mesmo B-Trees. Pra isso você precisa de mais literatura. Especialmente porque conceitos como colorização e rotação não são intuitivos, mesmo se você já tiver a idéia de swap de variáveis na cabeça.</p>
<p>A ideia de colorização não é usada só em árvores Red Black. Um dos mecanismos de Garbage Collectors se chama TriColor Marking, onde a fase de marcação de objetos categoriza quem pode ou quem não pode ser descartado usando um sistema de três cores, preto, branco e cinza. Se você entendeu a ideia do vermelho e preto, já tem uma noção de TriColor marking. Talvez eu fale sobre isso se for explicar como funcionam os garbage collectors em alto nível , mas por hoje fica a dica.</p>
<p>Eu espero que pelo menos tenha conseguido fazer vocês que são iniciantes enxergarem um pouco mais de como as coisas funcionam por baixo do seu Javascript e Python. Mesmo que não usem diretamente no trabalho, reservem um tempinho pra estudar o básico de C. Especialmente porque se vocês se empolgarem, C e C++ vão ser úteis caso um dia queiram usar uma biblioteca em C dentro do Javascript. E sim, isso é possível e é como muitas bibliotecas são feitas de verdade.</p>
<p>O nosso linguição de bits sozinho é só isso, bits um atrás do outro. Mas quando definimos estruturas de dados, começamos a dar forma a pedaços desses bits. Delimitamos essas estruturas pelo tamanho de cada primitiva, como inteiros de 32 ou 64 bits, e os endereços em que elas se localizam no linguição. Os bits sozinhos não significam nada. Só quando nós programadores definimos o que cada pedaço é que ela começa a ter sentido. Por exemplo, uma sequência de inteiros um atrás do outro pode ser um array só de números mesmo ou de letras, formando um String, quem decide qual é qual sou eu, programador.</p>
<p>Um pacote de bits que tem codificado um número que é outra posição no linguição, é um Node. E esses Nodes podem ser organizados como listas ligadas, hashtables ou árvores. Daí vem o problema de navegar por esses pacotinhos de bits. O jeito simples é sequencialmente como num array, mas logo a gente vê que isso é limitado. Daí saímos pulando de endereço pra endereço de Nodes. Mas isso é lento e complexo. Aí temos que inventar formas de minimizar esse trabalho e chegamos em algoritmos como de ordenação e procura.</p>
<p>E à medida que evoluímos o conceito, uma árvore desbalanceada se torna balanceada. Uma procura que levava tempo linear vira logaritmico. E uma estrutura que era simples, vai ficando mais sofisticada. Saímos de heaps de memória até índices de bancos de dados com a mesma categoria de estruturas e algoritmos. O importante é: mesmo se você treinasse por anos a fio, sem estudar a fundação, nunca vai chegar no que vimos nesses últimos 3 episódios sozinho. E ia continuar fazendo errado sem saber porque. “Meu programa tá lento, deve ser meu CPU que é velho”. E esquece que anos atrás se fazia programas que rodam mais rápido que o seu em CPU ainda mais velho. Então o problema não é o hardware não ser o suficiente, mas sim você não saber usar o máximo que ele pode oferecer e se limitar a se conformar com “ah, pelo menos roda”.</p>
<p>Tudo que falei até aqui é o básico. Cada assunto que falei tem bem mais detalhes por trás. E mesmo quando você tiver estudado tudo, ainda assim não vai fazer programas bons. Mas ao longo do seu treino e estudos, as peças vão começar a se encaixar mais rápido e você vai começar a dar saltos maiores do que quem começou junto com você mas preferiu pular essa teoria toda. Peguem o livro do Cormen, Tanenbaum e estudem. É chato pra caramba, mas quanto mais cedo fizer isso, melhor vai ser depois.</p>
<p>E com isso, finalmente cheguei no final deste assunto. Como eu disse no começo, assistam esta série múltiplas vezes. Se você é iniciante, não vai entender vendo só uma vez e nem tem tudo aquia. Precisa pesquisar mais sobre os assuntos que falei, fazer você mesmo esses códigos como exercício e só assim começar a apreciar como as coisas funcionam. Se acharem algum erro no video não deixem de mandar nos comentários abaixo, se curtiram o video deixem um joinha, assinem o canal e cliquem no sininho pra não perder os próximos episódios. Compartilhem o video com seus amigos pra ajudar o canal. A gente se vê, até mais.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>ciencia-da-computacao</category><category>bancos-de-dados</category><category>akitando</category></item><item><title>[Akitando] #94 - O que vem DEPOIS do Hello World | Consertando meu C</title><link>https://akitaonrails.github.io/2021/03/22/akitando-94-o-que-vem-depois-do-hello-world-consertando-meu-c/</link><guid isPermaLink="true">https://akitaonrails.github.io/2021/03/22/akitando-94-o-que-vem-depois-do-hello-world-consertando-meu-c/</guid><pubDate>Mon, 22 Mar 2021 15:12:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/YyWMN_0g3BQ"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIÇÃO&lt;span class="hx:absolute hx:-mt-20" id="descrição"&gt;&lt;/span&gt;
&lt;a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Algoritmos e Estruturas de Dados é parte do conhecimento fundamental que todo programador precisa saber ou nunca vai avançar de verdade na profissão. Vamos ver a ponta do iceberg pra vocês terem uma idéia do que isso significa. E vou aproveitar pra consertar alguns erros que cometi no episódio anterior.&lt;/p&gt;
&lt;p&gt;Conteúdo:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;00:00 - Intro&lt;/li&gt;
&lt;li&gt;01:47 - Consertando meus erros de C&lt;/li&gt;
&lt;li&gt;04:13 - Segmentos da Memória Virtual&lt;/li&gt;
&lt;li&gt;11:53 - Arrays de Javascript são &amp;ldquo;Arrays&amp;rdquo;?&lt;/li&gt;
&lt;li&gt;15:56 - Lista Ligada em C&lt;/li&gt;
&lt;li&gt;21:37 - Hashtable em C&lt;/li&gt;
&lt;li&gt;39:11 - Algoritmos de Ordenação&lt;/li&gt;
&lt;li&gt;42:11 - Complexidade e Big O&lt;/li&gt;
&lt;li&gt;43:30 - Vendedor Viajante e Fatorial&lt;/li&gt;
&lt;li&gt;47:29 - Mergesort vs Quicksort&lt;/li&gt;
&lt;li&gt;52:18 - Melhor e pior caso, Bubble vs Quick&lt;/li&gt;
&lt;li&gt;55:45 - Livros sobre Algoritmos&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Links:&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/YyWMN_0g3BQ"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h2>DESCRIÇÃO<span class="hx:absolute hx:-mt-20" id="descrição"></span>
    <a href="#descri%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Algoritmos e Estruturas de Dados é parte do conhecimento fundamental que todo programador precisa saber ou nunca vai avançar de verdade na profissão. Vamos ver a ponta do iceberg pra vocês terem uma idéia do que isso significa. E vou aproveitar pra consertar alguns erros que cometi no episódio anterior.</p>
<p>Conteúdo:</p>
<ul>
<li>00:00 - Intro</li>
<li>01:47 - Consertando meus erros de C</li>
<li>04:13 - Segmentos da Memória Virtual</li>
<li>11:53 - Arrays de Javascript são &ldquo;Arrays&rdquo;?</li>
<li>15:56 - Lista Ligada em C</li>
<li>21:37 - Hashtable em C</li>
<li>39:11 - Algoritmos de Ordenação</li>
<li>42:11 - Complexidade e Big O</li>
<li>43:30 - Vendedor Viajante e Fatorial</li>
<li>47:29 - Mergesort vs Quicksort</li>
<li>52:18 - Melhor e pior caso, Bubble vs Quick</li>
<li>55:45 - Livros sobre Algoritmos</li>
</ul>
<p>Links:</p>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>No último episódio eu fiz diversos tipos de Hello World em C pra explicar o básico de programação mais de baixo nível, e foi super trabalhoso porque eu não queria quebrar o  conteúdo em vários episódios.</p>
<p>Eu não programo em C faz muitos anos então muita coisa enferrujou mesmo. Eu nunca fui um bom programador de C e agora então, só o básico. Eu editei aquele script algumas vezes e mudei os exemplos enquanto fui revisando, e tirei muita coisa de cabeça. Nisso acabei deixando passar vários erros que me deixaram bem frustrado porque eu só vi depois. O bom foi que vários de vocês viram e relataram nos comentários. Eu coloquei algumas na erratas daquele vídeo e vou usar o video de hoje pra complementar o que eu acho que faltou falar.</p>
<p>A ideia destes vídeos não é ser um curso de C, nem de longe. É a mesma coisa na faculdade, C ou Pascal era usado só pra não termos que usar alguma pseudo-linguagem pra ensinar os conceitos. Tem várias nomenclaturas e conceitos que aos olhos de quem programa C no dia a dia ficou feio. Muita coisa eu simplifiquei pra explicação ficar mais fácil. Pra aprender C de verdade tem que ver livros com The C Programming Language do Kernighan e Ritchie, os criadores da linguagem e estudar muito código fonte.</p>
<p>Se você é programador de C, perdeu uma oportunidade. Podia ter feito um video chamado “Vamos consertar o C cagado do Akita”. É um bom tema. Tem muito pouco video sobre C hoje em dia. De qualquer forma, já que ninguém fez, vou fazer eu mesmo. A primeira metade de hoje vai ser uma errata aproveitando pra adicionar mais conteúdo ao que eu já tinha explicado e a segunda metade vai molhar um pouco os dedos em mais coisas de estruturas de dados e algoritmos que não coube no episódio passado. Então vamos lá.</p>
<p>(&hellip;)</p>
<p>Vamos começar com alguns erros pequenos. Tem um que se eu não prestar atenção cometo toda hora sem perceber, até eu acho irritante. Provavelmente porque por muitos anos programei em 32-bits, eu confundo quando converto bits em bytes. Mas é simples, só dividir por 8. Endereços de 32-bits são armazenados em 4 bytes. Mas endereços de 64-bits são 8 bytes. Só que minha memória muscular me ferra.</p>
<p>Aos 6 minutos e 6 segundos do video eu falo que se você alocar um inteiro de 64-bits mas não for usar valores maiores que 255, vai estar desperdiçando 7 bytes desses 8. E isso tá correto. Mas durante a edição a memória muscular ficou me falando “32 bits é quatro bytes”, o que tá errado, e achei que eu tinha gravado falando errado. Daí escrevi uma correção na tela de que iria desperdiçar 3 bytes. Ou seja, eu falei certo na gravação e corrigi errado na edição. A mesma coisa aconteceu aos 45 minutos e 20 segundos que eu falo que é 4 bytes, mas são 8.</p>
<p>Outra coisa que vira e mexe me confundo se falar muito rápido foi como aos 40 minutos e 54 segundos eu falo que 255 BYTES é um quarto de 1 megabyte. Tá errado, lógico, é um quarto de KILObyte. 255 vezes quatro são 1024 bytes, ou um kilobyte. Daí aos 6 minutos e 50 segundos eu falei errado o range de inteiros de 8-bits com sinal, o correto é de menos 128 a positivo cento e vinte e sete. Em hexa é mais difícil de errar porque vai de hexa FF até 7F.</p>
<p>Mais pro fim, depois dos 57 minutos quando faço a <code>struct</code> Person e começo a fazer createPerson escrevi errado <code>nome</code> em vez de <code>name</code> e ficou meio estranho. Foi um typo feinho mas como não chega a estragar o exemplo em si resolvi não regravar só por causa disso. Pior foi quando dei copy e paste do código de inicialização da struct pra dentro da função createPerson e esqueci os valores hardcoded em vez de usar os argumentos. O código ficou desse jeito aqui, mas o correto deveria ter editado pra ficar assim. Por isso a gente tem que tomar cuidado ao ficar dando copy e paste de código.</p>
<p>Além desses erros menores, o erro que me deixou mais irritado foi perto dos 39 minutos quando eu quis explicar sobre stack e passagem de parâmetros por valor. O exemplo original era com inteiros e nesse caso ela teria sido copiada 3 vezes passando da <code>main</code> pra <code>f1</code> e depois pra <code>f2</code>, mas eu substituí pela string “hello world” e na hora nem pensei muito porque eu tava com pressa, só segui o mesmo exemplo como se o string estivesse sendo duplicado na stack. Obviamente que não tá. E pra explicar, preciso complicar a parte que eu não queria. Quando falei de memória virtual pode parecer que ela é dividida só entre stack e heap, mas na verdade tem mais que isso.</p>
<p>Se eu quiser complicar e mostrar mais, vai ficar deste outro jeito. Seguindo pra baixo do heap a gente tem o segmento BSS que é o block starting symbol, ou que alguns chamam de better save space. Toda variável global estática que só foi declarada mas você não atribui nenhum valor fica nesse espaço, automaticamente inicializado como zero ou nulo. É diferente de quando você usa um <code>malloc</code> ou <code>calloc</code> que vai pegar algum trecho de memória disponível da heap.</p>
<p>Embaixo do BSS tem mais dois segmentos que interessa pra gente hoje, a ROData e a Text. Esse segmento Text, ao contrário do que você pode entender pelo nome, é o espaço de endereços do seu programa executável propriamente dito, é um segmento de código, das instruções em linguagem de máquina. Agora o segmento ROData é mais interessante pra gente.</p>
<p>Quando eu fiz <code>char hello[] = &quot;Hello World&quot;</code> eu tinha dito que isso seria alocado na stack, o string inteiro. Tá errado. Que burro, dá zero pra ele. E até podia ser assim mesmo numa outra linguagem, mas no caso de C, o hello vai ser uma referência pra um endereço que fica nesse espaço chamado <code>ROData</code>. Quando o compilador GCC passa pelo meu código fonte, ele vê dados como esse <code>hello</code> que é fixo e constante e grava dentro desse segmento no binário final. RO é de Read Only, então ROData é um segmento de dados somente de leitura.</p>
<p>Isso não altera a explicação de passagem por valor. Toda vez que eu passo a variável <code>hello</code> como argumento das funções <code>f1</code> e <code>f2</code>, cada uma vai empilhar uma nova cópia no stack, mas não do string inteiro, e sim dos 8 bytes de endereço que apontam pro array &ldquo;Hello World&rdquo; que é fixo, constante e tá dentro do segmento <code>ROData</code>.</p>
<p>Pense nesse segmento com uma versão estática do Heap, que é dinâmico. O ROData é pra valores que você tem hardcoded no seu código fonte. Como strings. Mas strings dinâmicas, por exemplo, de valores que vieram num JSON que você puxou via API, de linhas do banco de dados que você puxou da rede, tudo isso vai ser alocado no Heap e, mesma coisa, os ponteiros pra eles vão sendo empilhados na stack durante a execução do programa.</p>
<p>Quando eu falei do formato ELF binário, que é o linguição de bits num formato específico pro Linux executar, a primeira instrução começa no endereço hexa 1000. Mas no mesmo binário ELF existem outros segmentos que você pode inclusive ver usando o comando no shell <code>objdump -h</code> e o executável que acabou de compilar. Então, se passarmos esse objdump no nosso hello vão aparecer todos os segmentos. Você vê que o décimo primeiro é o segmento <code>init</code> no endereço hexa 1000 que eu falei antes e na décima quinta posição tem o endereço hexa 2000 que é o segmento ROData.</p>
<p>No video anterior, ignorem quando eu falo que o string inteiro é alocado no Stack. Ele tá no ROData e na stack as variáveis <code>hello</code> são referências com o endereço pra esse string. É algo assim. As referências ocupam espaço, pouco espaço se comparado a um arrayzão, mas ocupam. E quando são passadas por valor, de função pra função, vão sendo duplicadas na stack. Três vezes no nosso hello world, cada uma com 8 bytes. Mas não é pra você se preocupar com uso de espaço na stack, isso raramente é um problema. Como eu já tinha dito só quando seu programa é pesado em coisas como recursão.</p>
<p>Outra coisa que eu Não falei no episódio anterior quando expliquei ponteiros é que depois de chamar <code>malloc</code> pra alocar espaço no heap, quando você termina o que tinha pra fazer, a boa prática é chamar a função <code>free</code>, que avisa o gerenciador de memória que aquele segmento não tá mais em uso e ele pode usar pra outra coisa. Eu não fiz <code>free</code> pra explicação não ficar extensa mas também porque o programa executa e sai muito rápido. E quando um programa termina, o sistema operacional se encarrega de tornar toda memória que você tava usando disponível de novo pra outra coisa, independente se você deu free antes ou não.</p>
<p>Programinhas que executam rápido como um hello world não precisam se preocupar. O problema são programas que ficam abertos muito tempo, digamos um navegador, ou um servidor web. Tirando em exercícios como o hello world, em código de verdade, você DEVE se preocupar com a memória sendo usada porque elas vão ficar em uso por tempo indeterminado. Podem ser horas ou dias e se você ficar dando <code>malloc</code> e nunca der <code>free</code> a memória disponível no heap vai acabar ou chegar perto de acabar, e quando isso acontece mecanismos como o OOM do sistema operacional pode forçar a parada do seu aplicativo. OOM é o sistema que monitora a memória usada por aplicativos, que significa Out of Memory Killer.</p>
<p>Justamente porque gerenciamento de memória é complicado de explicar, eu já tinha feito dois vídeos que recomendo que vocês assistam. Uma das vantagens de linguagens de mais alto nível com Java, C#, Go e todas as interpretadas como Javascript, Ruby, Python, é que elas tem algum garbage collector, cuja responsabilidade é gerenciar o heap no lugar do programador. Porém, a comodidade vem com um custo: todo garbage collector sempre vai usar mais memória do que se você gerenciar manualmente e causar picos de pausa pra limpeza durante o processamento, o que pode ser um problema pra você. Por outro lado tem menos riscos de vazar memória por causa de código mal feito.</p>
<p>As duas linguagens modernas que tem um bom balanço entre comodidade e não desperdiçar muita memória são o Swift e o Rust. Recomendo estudar o ARC do Swift que é uma ajuda do compilador pra gerenciar memória via contador de referências e o sistema de Borrow do Rust que é o mais diferente de todos.</p>
<p>Voltando aos meus erros, outra coisa que eu falei errado no video anterior foi quando disse que o Stack, ou Pilha, tem dois tipos, LIFO e FIFO e logo na sequência falei que tem Queues ou filas. Eu bobeei aqui, uma cadeia onde a gente vai empilhando elementos no topo e desempilhando do topo é last in first out, LIFO que é uma Pilha. Uma cadeia onde a gente vai empilhando elementos no fim mas vai tirando do começo, ou seja, vai tirando por ordem de chegada, é first in first out, FIFO também conhecido como Fila. Então toda pilha é LIFO e toda fila é FIFO.</p>
<p>Pilhas são usadas na execução de quase todo programa. Filas você vê todo dia no supermercado ou banco, ou se é desenvolvedor web. Todo servidor web tem uma fila, as requisições http vão chegando e sendo enfileiradas e vão sendo servidas na ordem de chegada. Todo job em background usa uma fila. Tudo que você precisa segurar porque não dá pra processar tudo de uma vez usa uma fila. Em sistemas distribuídos é particularmente importante pra coordenar e controlar a carga. Não adianta sua linguagem ser rápida em conseguir iniciar um monte de jobs assíncronos e sobrecarregar a máquina. Você precisa controlar quanto processamento paralelo quer ter num determinado momento, e filas é a estrutura básica por baixo desses tipos de mecanismos de controle.</p>
<p>E antes que alguém comente, eu disse que “quase” toda execução de programas coloca frames no stack mas existem linguagens chamadas stackless, que significa “sem stack”, embora isso não seja totalmente verdade. Inclusive existe uma versão de Python stackless. Não é que elas não usam nada do stack, mas elas criam um stack próprio no heap também. É um assunto longo por si só então só quis mencionar pra quem tiver curiosidade, mas pode ser interessante em casos de muita recursão ou concorrência onde você separa a stack de cada thread pra evitar coisas como condições de corrida.</p>
<p>Continuando, ficou outra dúvida nos comentários sobre arrays, porque eu disse que array é uma sequência contínua de elementos de mesmo tamanho, ou mesmo tipo. Tipo, em C, meio que define o tamanho do elemento. Se for um uint8_t é um inteiro sem sinal de 8-bits, portanto o tamanho é de 1 byte. Uma struct como a Person que eu mostrei, tem 3 campos, uma string de 10 chars, depois 2 ints de 8-bits, portanto 12 bytes no mínimo. O tamanho e formato do binário é definido pelo compilador e não vai ser necessariamente na ordem que eu mostro no exemplo.</p>
<p>Mas num Javascript eu posso fazer arrays conterem strings, números, outros arrays e o que eu quiser. Então ele é um array ou não é um array? Sendo honesto nunca parei pra ver o código em C++ que implementa Arrays de Javascript mas se ele for parecido com a forma que outras linguagens como Java ou C# da vida fazem ele provavelmente é um array de ponteiros mesmo.</p>
<p>Se o espaço do Array acaba e você tenta inserir um novo elemento, ele vai alocar um novo array um pouco maior e copiar todos os elementos da antiga pra nova, e no final liberar o espaço do array original. E todos os elementos dentro vão ser ponteiros, endereços pra objetos que pode ser objeto de string, objeto de número, objeto de array. Esses objetos estão alocados em algum lugar no heap, mas nesse array não vai ter o objeto em si, só o endereço que aponta pra esses objetos. E todos os endereços vão ocupar o mesmo tamanho de 8-bytes, portanto são todos elementos de mesmo tamanho, exatamente o requerimento pra um array.</p>
<p>Se uma linguagem suporta coisas como Generics, daí você consegue limitar um array de objetos pra ter elementos só de um determinado tipo, como Strings, e nunca aceitar um objeto de número por exemplo. Por baixo dos panos, no binário, não faz diferença porque tudo vai ser um ponteiro. Mas importa pra linguagem. Tipos em C eu acho mais importante pra determinar o layout binário das coisas. Tipos em linguagens de alto nível como Java é pra determinar a semântica do seu código.</p>
<p>Se você nunca estudou estruturas de dados sempre vai coçar a cabeça de porque um Java da vida tem classes como ArrayList, LinkedList, Vector, ArrayDequeue, HashSet, e tantas outras que herdam das interfaces de Iterable, Collection, Set e List. Sem conhecimento prévio, à primeira vista, tudo parece só um array.  É impossível eu explicar tudo, mas quero tentar explicar dois tipos diferentes de coleções hoje, que são dois dos mais usados pra vocês entenderem porque tem diferença.</p>
<p>Recapitulando, a grande vantagem de um array é rapidamente encontrar os valores de qualquer posição, com a premissa que você sabe que posição quer acessar. Só pegar o endereço do primeiro elemento e multiplicar pela posição e pelo tamanho do elemento, e por isso é importante todo elemento ser do mesmo tamanho. A desvantagem é se esse array precisa crescer o tempo todo e você não sabe o tamanho final. Daí toda vez que chega no final, precisa criar um outro array maior pra poder continuar, copiar todos os elementos do anterior e liberar essa memória, e ficar fazendo isso até conseguir preencher com todos os dados que precisa.</p>
<p>Vamos fazer de conta que na sua linguagem favorita ele instancia um array de 10 elementos por padrão. Daí você conecta num banco de dados, faz uma pesquisa e começa a vir linhas de dados. Você vai colocando na posição 0 do array, a próxima linha na posição 1, depois na posição 2 e vai indo. Aí preenche as 10 posições mas ainda tem mais linhas vindo do banco de dados. Por baixo dos panos, vai alocar outro array, agora com 20 posições, copiar as 10 da anterior, liberar o espaço dessas 10 antigas e agora você continua preenchendo, posição 10, posição 11 e vai indo.</p>
<p>Digamos que no final vieram 100 linhas da tabela do banco de dados. Pra comportar isso na memória, seu array precisou ser expandido 10 vezes. Ou seja, teve 10 operações de alocar um novo array e copiar o anterior nele. E na última etapa, quando você tinha um array de 90 elementos e acabou espaço, ele precisou alocar um de 100 elementos. Ou seja, num determinado momento você ocupou o dobro de memória, além de ter que fazer operações de alocação e cópia de todos os elementos até 9 vezes. É um desperdício de processamento e de memória.</p>
<p>Imagina se fosse uma tabela com cem mil linhas. Ou no outro extremo, imagine se você precisasse de 100 arrays que só vão ter 2 elementos cada. Nas no nosso faz de conta, cada array começa com 10 posições pré-alocadas. Agora você tem 800 posições que vão ficar desocupadas, mas ainda assim ocupando espaço na memória. Então a linguagem não pode nem pré-alocar um array grande demais pra pra não ter que processar muitas operações de alocar novo array e copiar tudo e nem pode pré-alocar muito pequeno pra economizar memória. É um dilema, por isso sempre que possível você deve iniciar arrays dizendo pra ele quanto de espaço pretende usar.</p>
<p>Se você está no caso de listas que vão expandir e você não sabe quanto, pode escolher não usar arrays. Vamos começar usando o struct que aprendemos e criar uma chamada <code>Node</code> que traduz pra um nó, em português. Dentro vamos criar dois elementos, um valor inteiro pra guardar qualquer número, e eu vou usar número só porque Strings em C puro é mais chatinho e ia complicar o exemplo, mas eu recomendo de lição de casa vocês tentarem fazer o valor do nó ser um String. O próximo elemento é mais importante, vou chamar ele de <code>previous</code> pra apontar pro nó que foi criado antes desse. Esse é o truque da lista ligada, é uma lista que liga elementos um atrás do outro ou um na frente do outro, como você preferir.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;stdbool.h&gt;

struct Node {
	int value;
	struct Node *previous;
};

struct Node* createNode(int value, struct Node *previous) {
	struct Node *node = (struct Node*) malloc(sizeof(struct Node));
	node-&gt;value = value;
	if (previous) {
		node-&gt;previous = previous;
	}
	return node;
}

void main() {
	struct Node *first = createNode(2020, NULL);
	struct Node *second = createNode(2021, first);
	struct Node *third = createNode(2022, second);


	struct Node *iterator = third;
	while(iterator) {
		printf(&#34;%d, &#34;, iterator-&gt;value);
		iterator = iterator-&gt;previous;
	}
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pra facilitar, vamos criar uma função que aloca e inicializa um nó, semelhante ao que fizemos no video anterior com a struct Person. Como argumentos ele vai receber um valor inteiro qualquer e o nó que foi criado antes pra ele apontar pra ele. Dentro vamos usar <code>malloc</code> pra alocar essa nova struct, depois preenchemos o campo <code>value</code> com o que veio como primeiro argumento e checamos se o segundo argumento veio nulo. No caso, o primeiro Node criado não aponta pra ninguém, então deixa ele nulo, senão preenche o campo <code>previous</code> pra apontar pro nó que passamos.</p>
<p>Agora no <code>main</code> vamos criar 3 nodes bestas, um com valor de 2020 e mandando NULL que é nulo, porque é o primeiro nó. O segundo nó vai ter 2021 e passamos a referência pro <code>first</code> que criamos antes. Mesma coisa pro terceiro nó que passamos o valor 2022 e a referência do segundo nó. Agora que a coisa fica interessante.</p>
<p>Vou criar um quarto ponteiro, chamado <code>iterator</code> que vai apontar pro mesmo endereço da variável <code>third</code>, a última. Assim começamos um loop que vai parar quando a variável <code>iterator</code> for nulo. Nulo em C e várias linguagens interpreta pra <code>false</code> e quando der isso o loop <code>while</code> vai sair. A cada rodada do loop vamos imprimir o valor inteiro do nó na tela separado por vírgula usando nossa conhecida função <code>printf</code>. Pra quem não sabia ele formata os valores que passamos pra ele. Então &ldquo;%d&rdquo; é de decimal, daí ele formata números. No vídeo anterior eu usei &ldquo;%x&rdquo; que formata números como hexadecimais e &ldquo;%s&rdquo; é pra interpretar como um string mesmo.</p>
<p>E finalmente, depois de usar o valor do node, fazemos a variável iterator apontar pro endereço que tá no campo <code>previous</code>. Como o iterator começou sendo a variável third, e como o previous dela aponta pro endereço que tava na variável second, é isso que ele vai se tornar. Daí o loop volta pro começo, checa que a variável iterator ainda não é nulo e imprime o valor que tá na struct apontada por second. Finalmente fazemos o iterator ser o endereço que tá no campo previous desse second, que vai ser o first, e repetimos o loop.</p>
<p>Como o iterator agora aponta pra mesma struct que a variável first, ela continua não sendo nula. Imprime o valor de first, e o iterator vai ser o que estiver no campo previous dela. Como ela foi a primeira a ser criada e passamos NULL no segundo argumento quando criamos, ela não aponta pra mais ninguém. Agora o iterator vai ser NULL e isso vai parar o loop do <code>while</code>, e também vai terminar a função main porque não tem mais nada pra fazer, então o programa acaba e sai.</p>
<p>Um Node ligado a outro Node forma uma lista de Nodes ligados. Por isso falamos em Lista ligada que é outra estrutura de dados importante na computação. Ela tem várias vantagens em cima de um array. Pra começar, é fácil adicionar novos nodes. Só alocar uma struct no Heap e fazer o campo de ponteiro dela apontar pro último nó que foi criado. Assim não precisamos pré-alocar espaço que não vamos precisar. Mais do que isso. Já pensaram como faz pra inserir um elemento novo no meio de um array?</p>
<p>Como o array é sequencial e fixo, você é obrigado a fazer alguma coisa como copiar todos os elementos do meio pro final pra outro lugar, inserir o valor novo no meio, e copiar todos os elementos que guardamos de volta. Vai desperdiçar espaço e processamento. No caso de uma lista ligada basta alocar uma nova struct e mudar os ponteiros dos nodes ao redor da posição onde queremos inserir. Fazemos o elemento seguinte apontar pra ele e a nova struct apontar pro elemento anterior e pronto, inserimos no meio. O custo de inserir um novo elemento no meio é praticamente igual a inserir no fim, e independe do tamanho da lista.</p>
<p>Mas então, porque não usamos listas ligadas pra tudo? Porque ela tem uma grande desvantagem. Repetindo, num array, pra eu pegar o valor do centésimo elemento, basta pegar o endereço do primeiro e multiplicar o tamanho de cada elemento vezes 100 e imediatamente chegamos. Mas numa lista ligada, preciso pegar a primeira struct, e ir seguindo os ponteiros pro próximo elemento 100 vezes. Então quanto maior for a lista ligada, mais lento vai ser achar elementos nela, porque cada struct fica em algum lugar aleatório no heap e não tem como achar só com aritmética de ponteiro.</p>
<p>Arrays e Listas Ligadas, à primeira vista, especialmente se tiver poucos elementos, funcionam praticamente igual. Você nunca vai notar a diferença de operações ou performance porque mexer em listas de 10 elementos, cem elementos, mesmo mil elementos, é muito rápido na sua máquina. Mas quando você conecta num banco de dados, ou precisa processar centenas de arquivos grandes, e precisa alocar listas pra manipular todos esses dados, usar arrays ou listas vai fazer uma grande diferença de performance. E sem saber como elas funcionam, você jamais vai entender o que tá dando errado.</p>
<p>E tem outras estruturas importantes. A próxima que eu quero explicar pra você é um Map, ou HashMap ou Hashtable ou dicionário. Tem vários nomes por diversas particularidades de implementação. Na maioria das linguagens você provavelmente chama só de Hash. É quando você cria uma lista, mas em vez de navegar nela direto por posição, navega usando uma chave.</p>
<p>Em Javascript é o que você chamaria de objeto JSON. Por exemplo, podemos criar uma variável person com os campos name, age e city, preencher com Fabio, 43, Sao Paulo e podemos acessar os valores com a sintaxe de colchetes que nem um array mas passando o nome do campo, como &ldquo;age&rdquo; e ele devolve 43. Se você não sabia disso, apesar da sintaxe parecer um array, isso não é um array e por baixo funciona bem diferente.</p>
<p>Existem diversas formas de implementar Hashtables. Quero mostrar uma forma rudimentar em C. Antes vamos ver como se usa. Na função <code>main</code> eu quero alocar primeiro uma struct chamada Hash que vamos criar depois. Daí vamos criar uma função chamada <code>insertNode</code> que recebe o ponteiro pra esse hash e vamos inserir alguns dados. Primeiro um valor &ldquo;world&rdquo; com uma chave &ldquo;hello&rdquo;. Se fosse javascript seria hash abre colchetes, daí &ldquo;hello&rdquo; igual &ldquo;world&rdquo;. Apesar da sintaxe não ser bonitinha com colchetes, pense no funcionamento como sendo a mesma coisa do equivalente em Javascript.</p>
<p>Vamos fazer mais um <code>insertNode</code> pro mesmo hash, mas com chave &ldquo;Fabio&rdquo; e valor &ldquo;Akita&rdquo;. Agora vamos imprimir na tela esses valores procurando pela chave. Se fosse javascript seria o equivalente a um console ponto log entre parênteses hash colchetes com a chave &ldquo;hello&rdquo;. Mas a gente vai fazer uma função chamada <code>search</code> que recebe como parâmetro o ponteiro do mesmo hash e a chave &ldquo;hello&rdquo;. Daí passamos o valor que ele vai retornar pro <code>printf</code> imprimir na tela.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;stdbool.h&gt;

#define HASH_SIZE 100

struct Node {
	char *key;
	char *value;
	struct Node* next;
};

struct Hash {
	struct Node *list[HASH_SIZE];
};

unsigned int hashCode(char *key) {
	unsigned long hash = 5381;
	unsigned int c;
	while(c = *key&#43;&#43;) {
		hash = ((hash &lt;&lt; 5) &#43; hash) &#43; c; // hash * 33 &#43; c
	}
	return hash % HASH_SIZE;
};

struct Node* createNode(char *key, char *value) {
	struct Node *node = (struct Node*) malloc(sizeof(struct Node));
	node-&gt;key = key;
	node-&gt;value = value;
	return node;
};

void insertNode(struct Hash *hash, char *key, char *value) {
	unsigned int index = hashCode(key);
	struct Node *node = hash-&gt;list[index];
	if(node == NULL) {
		hash-&gt;list[index] = createNode(key, value);
	} else {
		while(node) {
			if (node-&gt;next == NULL) {
				node-&gt;next = createNode(key, value);
				break;
			}
			node = node-&gt;next;
		}
	}
};

char* search(struct Hash *hash, char *key) {
	unsigned int index = hashCode(key);
	struct Node *node = hash-&gt;list[index];
	while(node) {
		if (strcmp(node-&gt;key, key) == 0) {
			return node-&gt;value;
		}
		node = node-&gt;next;
	}
	return &#34;&#34;;
}

void main() {
	struct Hash *hash = (struct Hash*) malloc(sizeof(struct Hash));
	insertNode(hash, &#34;hello&#34;, &#34;world&#34;);
	insertNode(hash, &#34;Fabio&#34;, &#34;Akita&#34;);

	printf(&#34;%s\n&#34;, search(hash, &#34;hello&#34;));
	printf(&#34;%s\n&#34;, search(hash, &#34;Fabio&#34;));
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora sabemos que precisamos implementar uma struct chamada Hash e pelo menos duas funções, a <code>insertNode</code> e a <code>search</code> que é procurar em inglês. Vamos começar pela struct Hash que vai conter uma variável chamada <code>list</code> que é um array de ponteiro pra struct chamado <code>Node</code>. O hashtable vai ser implementado com um array de tamanho fixo. Na prática nem precisava ser uma struct Hash, eu podia só ter criado direto um array, mas só pra ficar mais fancy.</p>
<p>Só pra ficar menos feio vamos definir essa constante 100 com o nome de HASH_SIZE lá em cima usando o pré-processamento #define, que eu expliquei no episódio anterior. É uma forma de não largar números mágicos por aí. Quando passarmos pelo compilador, imagine que vamos dar copy e paste do número 100 no lugar desse nome HASH_SIZE.</p>
<p>Agora temos que implementar a struct principal, que é a <code>Node</code> que vai conter os campos principais de chave e valor que são as strings <code>key</code> e <code>value</code>. Depois, como na lista ligada, um ponteiro pra <code>next</code> que vai ser o próximo Node na cadeia. De propósito antes eu mostrei uma lista ligada que vai apontando pro Node anterior. Aqui vai ser uma lista ligada na direção oposta, apontando pro próximo Node mais novo, só pra mostrar que tanto faz a direção. Dependendo do exemplo que você achar por aí vai ter jeitos diferentes de implementar.</p>
<p>Agora vamos implementar a função mais importante de um hashtable que é a função que converte a chave num identificador numérico. No nosso caso a chave vai ser só strings. Esse número identificador é o que chamamos de hash e eu expliquei mais sobre hashing nos episódios de criptografia, então dêem uma olhada depois. Grosseiramente é como se fosse um gerador de números aleatórios ou random, onde o seed ou semente é o valor da tal chave.</p>
<p>Gostaríamos que essa função de hash devolvesse números o mais aleatório possíveis, ou seja, cuja distribuição estatística seja próxima de uma curva normal ou gaussiana. Se não entendeu isso, coloque na lista estudar estatística básica. A razão disso é simples. A struct Hash que criamos vai conter um ponteiro pra um array de tamanho fixo. Num hashtable podemos inserir mais elementos do que o tamanho do array. A mágica pra isso começa em transformar a chave que é um string num número entre 0 e 99. Uma função de hash pega, por exemplo, a string &ldquo;hello&rdquo; e converte num número, por exemplo esse numerozão aqui do lado.</p>
<p>Pra isso vamos fazer uma função chamada <code>hashCode</code> que recebe essa chave. Se você é de Java já viu esse nome. Dentro começamos declarando um número mágico, no caso cinco mil trezentos e oitenta e um. Poderia ser qualquer número. Depois declaramos uma variável chamada <code>c</code> que a cada rodada do loop vai pegando uma letra da chave. Esse loop vai repetindo até acabar as letras da chave e o <code>c</code> ficar nulo e parar o loop.</p>
<p>O cálculo dentro do loop não é importante pra hoje, tem várias outras formas. Em particular neste exemplo começa com o valor inicial da variável hash que é o cinco mil e tantos acima. Daí multiplicamos por 33 que é o mesmo que fazer bitwise shift pra esquerda de 5 bits, somamos ao hash original junto com o endereço da primeira letra. E vamos fazendo isso letra a letra até o loop parar. No final vamos ter um numerozão que identifica essa chave. Esse número pode ser bem maior do que o tamanho do array e a gente gostaria que fossem posições válidas dentro do nosso array.</p>
<p>Sempre que você tem um range ou sequência limitada, no nosso caso que vai de 0 a 99, mas temos um número maior que isso, pra caber, usamos a operação de módulo. E aqui é mais um exemplo de porque matemática importa. Vamos precisar de módulo. Na prática um módulo faz um wrap. Qualquer número módulo 100, vai devolver um número menor que 100. Daí podemos usar isso como um índice ou hash. Toda chave sempre vai chegar no mesmo hash, mas a partir do hash não temos como voltar ao valor original da chave.</p>
<p>O cálculo do exemplo chamado de djb2. Não é o único e nem é o melhor, mas é fácil pra explicar pra hoje em uma linha só. Lembram quando eu disse que vídeos como esse e qualquer outro tutorial e curso priorizam a facilidade da explicação? Entenda que existem outros algoritmos mais sofisticados do que multiplicar por 33 e ir acumulando. Especialmente algoritmos estatisticamente melhor distribuídos. Mas por agora, o importante é entender que esta é uma das maneiras de converter um string num número e com o módulo, garantidamente devolver uma posição dentro do Array.</p>
<p>Antes de continuar, vamos criar uma função que instancia e popula uma struct Node. Podemos chamar de <code>createNode</code> que recebe como argumentos os strings de chave e valor, <code>key</code> e <code>value</code>. Dentro fazemos <code>malloc</code> pra reservar espaço pra essa struct. Em seguida populamos os campos <code>key</code> e <code>value</code> da struct com os valores que recebemos de argumento. Bem simples. Vamos manter o campo <code>next</code> da struct como nulo.</p>
<p>Como aprendemos no episódio anterior, ao usar a notação de setinha que é um traço e sinal de maior, ele acessa o valor do ponteiro de item que é a struct de Item que tá em algum lugar no Heap. Essa sintaxe de setinha é equivalente a fazer asterisco variável entre parênteses e usar a notação de ponto. Mas setinha fica mais bonito.</p>
<p>Finalmente chegamos ao filé mignon da explicação implementando a função <code>insertNode</code> que vai de fato armazenar o par de chave e valor dentro do nosso array fixo. Começamos com função <code>hashCode</code>, passando a <code>key</code> que veio como argumento e achando a posição numérica dentro do array, que vai ser essa variável <code>index</code>. Continuamos declarando uma variável node que vai apontar pro primeiro Node na posição index do arrayzão.</p>
<p>A lógica vai assim: se no array da estrutura de hash, na posição que chamamos de <code>index</code>, não existir nada, se for nulo, então criamos um novo Node com a função <code>createNode</code>, passando key e valor e armazenamos o endereço desse Node na posição <code>index</code> do Array chamado <code>list</code> dentro da estrutura que chamamos de hash.</p>
<p>Caso contrário, se na posição <code>index</code> já existir um Node, temos duas opções. Se no campo <code>next</code> desse Node não existir nada, for nulo, chamamos <code>createNode</code> pra criar um Node e associamos seu endereço no campo <code>next</code>. Agora temos uma lista ligada de dois elementos. Podemos quebrar o loop e sair fora. Se a lista tiver mais de um elemento já, esse <code>next</code> já vai estar apontando pra outro Node. Então fazemos a variável <code>node</code> apontar pra esse <code>next</code> e repetimos o loop, até achar o último Node cujo <code>next</code> é nulo e nele associamos nosso novo Node.</p>
<p>Essa nossa variável <code>list</code> na struct Hash não é um array simples como mostramos até agora. É um array onde cada posição vai apontar pra uma lista ligada, ou seja, é tipo uma matriz. O conceito é assim: cada lista ligada pode ter quantos elementos quisermos. Daí nesse array fixo vamos conseguir colocar mais elementos do que o tamanho total de posições só do array. Estamos combinando o melhor do array com o melhor da lista ligada numa estrutura só.</p>
<p>Outro conceito importante é que o número que o <code>hashCode</code> devolve identifica uma ou mais chaves. Já disse que ela não é única pra cada chave. Especialmente porque fazemos o módulo do hash pra ter a posição do array. Se múltiplas chaves podem chegar no mesmo número significa que mais cedo ou mais tarde vamos ter uma colisão, ou conflito. Esse é o termo mesmo. Quando acontecer uma colisão, dois Nodes vão competir pela mesma posição no array.</p>
<p>Vamos dar um exemplo, faz de conta que queremos inserir um valor &ldquo;bla&rdquo; com uma chave &ldquo;abc&rdquo;. E faz de conta que queremos inserir outro valor &ldquo;foo&rdquo; com uma chave sendo &ldquo;xyz&rdquo; e agora o principal, faz de conta que o cálculo da nossa função <code>hashCode</code> devolva o número 42 pra ambos os strings. Vamos simular um caso de colisão.</p>
<p>Da primeira vez, vamos pra posição 42 do array <code>list</code> e apontamos pra uma struct Node com a chave sendo &lsquo;abc&rsquo; e valor sendo &lsquo;bla&rsquo;. Na segunda tentativa o hash da chave &lsquo;xyz&rsquo; devolve o mesmo índice 42, mas na posição 42 do array já tem o Node com chave &ldquo;abc&rdquo;, então criamos um novo Node e fazemos o primeiro apontar pra ele. Agora temos os dois armazenados na posição 42 do array, entenderam?</p>
<p>Pra ficar claro, digamos que queremos incluir um terceiro par de valores cuja chave é “foo” e o valor é “bar” e de novo, faz de conta que o hashCode pra essa chave “foo” seja 42 também. Agora já existe uma lista ligada nessa posição do array. Começamos do primeiro Node com chave “abc”. O <code>next</code> dela aponta pra outro Node, com a chave “xyz” e agora o <code>next</code> é nulo, portanto é o fim da lista. Basta fazer esse <code>next</code> apontar pro novo Node com chave “foo” e pronto, adicionamos mais um elemento no hashtable. É isso que acontece num caso de várias chaves colidirem e competirem pela mesma posição no array.</p>
<p>De volta ao exemplo, na função <code>main</code> usamos a função <code>insertNode</code> duas vezes, na primeira pra inserir o par com chave <code>hello</code> e valor <code>world</code> e na segunda vez pra inserir o par com chave <code>Fabio</code> e valor <code>Akita</code>. A chave <code>hello</code> passando pela nossa função hashCode devolve o número 41 e passando a chave <code>Fabio</code> devolve o índice 90. Ou seja, a posição 41 do array <code>list</code> vai apontar pra uma instância da estrutura Node com o valor &ldquo;world&rdquo; e na posição 90 do array <code>list</code> vai existir outra instância de Node com o valor <code>Akita</code>.</p>
<p>Só pro nosso exemplo ficar mais interessante faz de conta que antes de inserir a chave <code>hello</code> já tinha um Node com chave <code>ano</code> e valor <code>2021</code> e faz de conta que se passarmos a string <code>ano</code> pra função hashCode vai dar o número 41. Daí a gente tenta inserir a chave <code>hello</code> e valor <code>world</code> e passando <code>hello</code> em <code>hashCode</code> sabemos que volta 41 também. Criamos um novo Node com a chave “hello” e colocamos seu endereço no ponteiro <code>next</code> do Node chave <code>ano</code>.</p>
<p>Esse é o mecanismo. O pior caso é quando a função de hashCode dá colisões demais e a maioria dos elementos que tentamos inserir acaba na mesma posição do array, e aí fica desbalanceado, por isso escolher a melhor fórmula vai fazer diferença e por isso eu falei em uma função que tivesse uma distribuição gaussiana, onde na média, cada posição do array vai ter mais ou menos o mesmo número de elementos.</p>
<p>Finalmente, uma vez inserido, e se a gente quiser achar o valor que corresponde à chave <code>hello</code>? Vamos criar a função <code>search</code> que devolve um ponteiro pra uma String, que vai ser esse valor. E como parâmetro vamos receber a referência pra struct Hash que contém o array <code>list</code> e como segundo parâmetro um ponteiro pra string que contém a chave que estamos procurando, no caso &ldquo;hello&rdquo;.</p>
<p>De novo, começamos calculando o tal hash da chave &ldquo;hello&rdquo; que já sabemos que vai ser 41. Daí pegamos o primeiro Node da lista ligada que tá na posição 41 do nosso array <code>list</code>. Agora começamos um loop. Enquanto a variável <code>node</code> não for nulo, entra no corpo do Loop. Dentro comparamos o valor da chave nesse Node com a chave que recebemos no parâmetro da função usando essa função do C chamada <code>strcmp</code> ou string compare, que vai devolver <code>zero</code> se as duas strings que passamos tiverem o mesmo valor.</p>
<p>No nosso exemplo vai dar false porque o primeiro Node contém um Item cuja chave é <code>ano</code>. Sendo assim, pulamos esse <code>if</code> e fazemos a variável <code>node</code> apontar pro <code>next</code> do atual. Repete o loop, a variável <code>node</code> ainda não é vazio. No <code>if</code> comparamos de novo o string do campo <code>key</code> com a variável <code>key</code> do parâmetro. E como esse segundo Node contém a chave <code>hello</code>, o <code>strcmp</code> vai dar verdadeiro e podemos retornar o campo <code>value</code> do Node, que vai ser <code>world</code>.</p>
<p>Pronto, em conceito é mais ou menos assim que funciona um hashtable pra inserir e procurar um Node. E é assim que conseguimos inserir centenas ou milhares de itens num array que é limitado em cem posições. Toda vez que tiver uma colisão de chave, anexamos o Node no final da lista ligada nessa posição do array. E quando for procurar a chave, calculamos o hash que dá a posição no array e vamos seguindo a lista ligada que está lá até encontrar o Node com a chave que procuramos.</p>
<p>A essa altura você deve estar pensando, &ldquo;por que todo esse trabalho&rdquo;? A vantagem é que se nosso array estivesse todo cheio, e quisermos achar onde tá o Node com a chave “hello”, rapidamente sabemos que tá na posição 41 do array, porque o cálculo do hash independe de quantos elementos existem. Mesmo se a hashtable tiver 200 Nodes já, chegamos na posição 41 com um simples cálculo. Daí só precisamos fazer mais 2 comparações de chaves na lista ligada pra ir de next em next até chegar no Node com chave “hello” e vamos achar o valor “world”.</p>
<p>Se usássemos só um array pra inserir 200 elementos, mas o array começa com 10 elementos pré-alocados, ia precisar expandir e copiar todos os elementos do array 20 vezes. Mas pra achar um valor numa posição específica é rápido. Por outro lado, se usássemos uma lista ligada pra inserir os 200 elementos, a inserção seria super rápida e econômica. Mas pra procurar teríamos, no pior caso, que vascular 200 elementos. Mas juntando o array com a lista ligada, diminuímos consideravelmente tanto o tempo de inserção quanto o tempo de procura.</p>
<p>Mas da forma como implementamos, não só agora eu posso usar uma string como chave em vez de um número, como estamos segmentando, ou seja, dividindo a lista ligada dentro de um array fixo. E isso nos dá o meio do caminho do melhor dos dois mundos entre um array e uma lista ligada. Não vamos desperdiçar memória e processamento tendo que duplicar o array toda vez que chega no fim. E ao mesmo tempo podemos procurar itens razoavelmente rápido porque em vez de vasculhar uma lista ligada inteira, vamos vasculhar só um trecho dela.</p>
<p>Então, toda vez que você fizer um hash em Javascript ou outras linguagens que suportam essa sintaxe de colchetes, por baixo dos panos é algo parecido com isso que tá acontecendo. Nessa nossa implementação rudimentar, esse <code>search</code> passando o <code>hash</code> e argumento <code>hello</code> seria o equivalente no Javascript a escrever <code>hash</code> colchetes e <code>hello</code> dentro.</p>
<p>Falando em Javascript, muitos que estão iniciando os estudos agora talvez já tenham visto exercícios implementando coisas como listas ligadas ou hashtables em Python, Javascript ou outra linguagem de alto nível. E de fato, eu poderia implementar exatamente esse hashtable que fiz em C só que em vez de usar structs eu poderia usar classes. Daí você poderia ter a péssima idéia de usar esse hashtable que escreveu em vez de usar a que já vem por padrão na linguagem.</p>
<p>Isso é uma má idéia porque nenhuma versão de hashtable que você escrever em Python puro vai ser mais rápido do que a que já vem nela. A explicação é simples. Vamos ver como o Python implementa o hashtable, que ele chama de <code>dict</code>. O Python mais usado é o CPython, e esse nome é porque o Python é escrito em C. E se a gente for no GitHub e vasculhar o código fonte do CPython vamos achar o arquivo <code>dictobject.c</code> que, chan chan chan chan, é escrito em C. Esse em particular tem mais de 5 mil linhas de C. Por isso eu repito, o exemplo que eu dei antes é uma simplificação rudimentar pra ser didático de explicar. Pra ver o de verdade, você precisa ver esse do Python.</p>
<p>A mesma coisa vale pra todas as estruturas de dados a algoritmos básicos de ordenação, procura e coisas mais avançadas como criptografia que o Python te dá. Arrays, listas ligadas, sets que são conjuntos e muito mais. E a mesma coisa acontece em todas as outras linguagens de alto nível como Ruby, como Javascript. Eu disse no episódio anterior que elas são ótimas linguagens pra colar estruturas feitas em C ou C++ com uma sintaxe de mais alto nível e mais fácil de usar. É isso que eu quero dizer.</p>
<p>Se você estudou ou for estudar a matéria de estruturas de dados e algoritmos, vai aprender a operar essas estruturas. Até agora eu só mostrei a coisa mais trivial que é inicializar e popular as estruturas. Mas e se quisermos procurar elementos num array gigante? Ou se quisermos ordenar em ordem alfabética? Daí você começa a aprender sobre algoritmos. Sem ter estudado nada, pense, como você ordenaria um array?</p>
<p>Ordenação é um dos tópicos da matéria e normalmente o primeiro algoritmo que você aprende é o famigerado bubblesort. Pra acelerar o código é este aqui do lado. Mas só ver o código talvez não ajude muito. Se você procurar no Google por sorting algorithms animation vai achar vários sites que mostram uma versão animada de como cada uma funciona. Vamos ver um bubble sort em ação. Conseguem ver como ele vai comparando pares de números e fazendo os números maiores irem pra direita? Compara um par, se o da esquerda for maior que o da direita, faz um swap e troca eles de lugar e vai fazendo isso até o fim da lista, e repete, até tudo estar ordenado.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>#include &lt;stdio.h&gt;

void main() {
	int total = 15;
	int array[] = { 40, 55, 11, 32, 67, 5, 74, 89, 38, 66, 27, 36, 79, 99, 2 };

	int i, j, swap;

	for(i = 0; i &lt; total - 1; i&#43;&#43;) {
		for(j = 0; j &lt; total - i - 1; j&#43;&#43;) {
			if(array[j] &gt; array[j&#43;1]) {
				swap = array[j];
				array[j] = array[j&#43;1];
				array[j&#43;1] = swap;
			}
		}
	}
	printf(&#34;Ordenado: &#34;);
	for(i = 0; i &lt; total - 1; i&#43;&#43;) {
		printf(&#34;%d, &#34;, array[i]);
	}
	printf(&#34;%d\n&#34;, array[i]);
}</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>No exemplo em C é assim. Começamos declarando uma variável total como sendo um número hardcoded 15 e em seguida criamos um array de 15 números fora de ordem qualquer. Daí precisamos de 3 variáveis inteiros pra ajudar, um chamado <code>i</code>, outro <code>j</code> e um chamado <code>swap</code>. Começamos um loop fazendo o <code>i</code> ir de 0 até o total menos um que é 14. Lembrando que arrays em C começam de 0 e vai até o total menos 1. Então um array de 15 elementos vai ter posições de 0 até 14.</p>
<p>Dentro tem outro loop, fazendo <code>j</code> ir de 0 também até o total menos <code>i</code>. Isso porque dentro comparamos o elemento na posição <code>j</code> com o seguinte que é <code>j</code> mais um, então não pode ir até o último senão vamos tentar pegar um décimo sexto elemento que não existe e vai dar pau. Daí comparamos entre o elemento na posição <code>j</code> com o de <code>j</code> mais um e se o de <code>j</code> for maior, fazemos o swap. Swap nesse caso é guardar o elemento de j mais um, copiar o elemento de j pra j mais um, e na posição j gravar o que tava na variável swap. Em outras linguagens tem como fazer swaps assim com uma operação só, mas por baixo dos panos sempre vai ter uma variável intermediária.</p>
<p>Se achou complicado depois tente você mesmo executar esse código em C com diferentes arrays, usando <code>printf</code> em cada etapa pra ver o que tem nas variáveis <code>i</code> ou <code>j</code> e o que tem no array nesses posições pra entender o que tá acontecendo. Mas o importante é entender o seguinte. Temos um loop dentro de outro loop percorrendo todos os elementos do array. Na primeira passada tem 14 comparações. Na próxima passada, o <code>i</code> vai ser 1 então total menos 1 menos 1 são 13 comparações. Na passada seguinte o <code>i</code> vai ser 2 então total menos 2 menos 1 são 12 comparações e vamos assim até o <code>i</code> ser igual a <code>14</code> e acabar.</p>
<p>Ou seja, vamos ter 14 mais 13 mais 12 mais 11 e assim por diante, que vai dar 105 comparações. Se colocarmos um contador no começo do código e ir incrementando dentro do segundo loop, no final podemos imprimir e ver que vai dar 105. Pra somar todos os número de 0 até N precisamos da matemática de análise combinatória. A fórmula é n vezes n menos 1 dividido por dois. Então 15 vezes 14 dividido por 2 é exatamente 105. Sendo essa fórmula da ordem de grandeza de N vezes N ou seja N ao quadrado, também dizemos que esse algoritmo tem complexidade quadrática.</p>
<p>Complexidade de tempo de um algoritmo é uma das coisas que você mais vai ver quando se compara algoritmos que fazem a mesma coisa. Por exemplo, no caso de ordenação de arrays, temos esse simples que é o Bubblesort e na média tem complexidade quadrático que escrevemos na notação big-O como sendo O n ao quadrado. Outros algoritmos melhores como o Mergesort ou Quicksort, na média tem complexidade O n vezes log de n ou logarítmico. E aqui eu vou falar de complexidade sem ser formal, então se você é matemático perdoe minha nomenclatura mais solta.</p>
<p>Complexidade é outro aspecto da matéria que vocês precisam entender. E pra resumir bem curto existem gráficos como este que compara os principais tipos. Se não me engano o pior tipo seria fatorial, que nem aparece aqui. No caso do nosso exemplo de 15 elementos, se nosso algoritmo fosse extremamente mal feito onde a ordem de grandeza fosse o fatorial de 15, o número de operações seria de 15 vezes 14 vezes 13 vezes 12 etc e isso dá mais de 1.3 trilhão de operações. Comparado com as 105 do bubblesort, que já é ruim, seria totalmente inviável.</p>
<p>Um exemplo de um algoritmo fatorial? É o próprio código da forma recursiva ingênua que calcula o resultado de um fatorial, que você encontra de exemplo em tudo que é lugar. Outro problema famoso que você aprende na faculdade é o do vendedor viajante, “the travelling salesman” resolvido usando força bruta. Tem até de fato um algoritmo de ordenação que de propósito foi feito pra demonstrar um caso fatorial, que é o snailsort, que pra cada elemento ele faz uma permutação de todos os outros elementos. Tudo que envolve permutações que são combinações de todos os elementos com todos os elementos, têm chances de ser fatorial.</p>
<p>Aliás, o algoritmo do vendedor viajante é o problema do sistema de logística de qualquer Amazon da vida. O caminho mais curto pra passar entre diversos endereços ou cidades diferentes. O jeito mais ingênuo de fazer isso, via força bruta, tem complexidade fatorial, ou seja, é absurdamente caro de processar porque a força bruta seria fazer a permutação de todas as possibilidades de rotas entre todos os pontos de destino e no final comparar pra achar a mais curta. E um algoritmo um pouco melhor vai cair de complexidade fatorial pra exponencial, que continua sendo lento.</p>
<p>Esse em particular é da categoria de problemas considerados NP Hard. Problemas ao redor de NP ou “tempo não polinomial” é uma coisa que eu mesmo tenho intuição mas não tenho bagagem matemática pra discutir formalmente. Só entenda que existem diversos problemas matemáticos que não tem uma fórmula simples. Pior, custam complexidade fatorial pra resolver.</p>
<p>Existem problemas P, NP, NP Complete, NP Hard. Problemas P podem ser resolvidos tem tempo polinomial, problemas NP são não-determinísticos cujos resultados podem ser verificados em tempo polinomial. Não se preocupe muito com isso no começo, basta saber que isso existe.</p>
<p>Mas voltando ao que nos interessa, acessar uma posição num array é complexidade constante, ou seja, não importa se o array tem 10 elementos ou 10 mil elementos, sabendo a posição, recuperar um valor custa sempre o mesmo tempo. Agora, procurar um elemento numa posição de uma lista ligada é complexidade linear, depende do tamanho da lista. Como não dá pra fazer aritmética de endereço, porque os elementos não são sequenciais um atrás do outro, a gente precisa ir de Node em Node seguindo o endereço pro próximo. Então pra achar o décimo Node de uma Lista Ligada onde só temos o endereço do primeiro Node, precisamos fazer a operação de seguir pro próximo ponteiro 9 vezes.</p>
<p>Pra achar o vigésimo elemento precisamos seguir ponteiros 19 vezes no pior caso, e assim por diante, ou seja, o tempo que vai custar depende da quantidade de elementos, por isso a ordem de grandeza é chamado de linear, que no gráfico de comparação é a linha que cresce igual nos eixos x e eixo y. Imagina carregar um arquivo com palavras, quebrar num array, e procurar uma palavra. Sem saber coisas como busca binária ou qualquer tipo de indexação via hashing ou algo assim, você vai primeiro fazer um loop e vasculhar elemento a elemento até achar o que procura. Isso é mais ou menos linear, lógico depende do que mais você processa, mas estou falando de ordem de grandeza do caso simples.</p>
<p>Agora, na categoria de logarítmico, estão coisas como busca binária, que eu não expliquei e não pretendo detalhar hoje pra não alongar. Mas pensa assim. Em vez de procurar um a um linearmente, você pode usar a estratégia de segmentar a procura quebrando sua lista primeiro ao meio, e recursivamente ir procurando em listas da metade do tamanho e cada pedaço. É meio que a estratégia de dividir pra conquistar. Imagina uma função chamada <code>pesquisa</code>. E dentro dela você quebra a lista em duas e passa cada metade pra mesma função <code>pesquisa</code>, e vai fazendo isso recursivamente. É a base da idéia de busca binária.</p>
<p>Então numa lista de 100 elementos, você quebra em 2. Agora tem a lista de 50. Quebra de novo em 2. Vai ter 25. Quebra de novo. 12. De novo. 6. De novo 3. De novo 1. Se o que procura estiver na primeira metade, talvez você ache em 6 operações em vez de 50. Essa mesma idéia tá por trás de algoritmos como mergesort ou quicksort que são mais rápidos que bubblesort que é complexidade quadrática. Se voltarmos na nossa animação de algoritmos de ordenação, vou mostrar tanto o merge quanto o quick um embaixo do outro.</p>
<p>Viram como eles segmentam a lista? Logaritmo é a operação inversa da exponencial, se uma complexidade exponencial seria 15 ao quadrado que seria ordem de 225 operações, a logaritmica seria o log de 15 na base 2, que daria menos de 4 operações. Complexidade exponencial fica mais caro mais rápido que linear. São opostos no nosso gráfico e você gostaria de escrever código mais próximo da logarítmica do que da exponencial. Mas não é sempre que você tem a sorte de achar algoritmos logaritmicos, mais comum eu acho que é achar loglineares que é linear vezes logaritmico ou O n vezes log n.</p>
<p>Algoritmos loglineares são por exemplo os mergesort e quicksort que mostrei, que embora sejam bem melhores que quadrático ou exponencial, ainda ficam caros mais rápido que puramente linear. Pra memorizar, veja que a notação O n vezes log n é como se fosse a complexidade linear que é O n vezes a complexidade logaritmica que é O log n.</p>
<p>Essa notação de O alguma coisa ou Big O provavelmente porque usamos uma letra <code>O</code> maiúscula é pra você ter noção da ordem de grandeza do tempo de processamento e/ou do uso de recursos de um algoritmo. Algoritmos podem ser qualquer código que você faz. Tudo que recebe algum dado de input e faz algum processamento pra resultar em alguma coisa. A notação é pra dar uma ideia da complexidade, no pior caso. Por exemplo, o quicksort na média é loglinear que é O n vezes log de n, mas no seu pior caso pode virar quadrático.</p>
<p>Por exemplo, pra acessar o valor de um array, eu repeti dezenas de vezes que é só pegar o endereço do primeiro elemento e somar pelo número da posição vezes o tamanho de bits de cada elemento. Num array de inteiros 64-bits de 100 posições, pra pegar o valor na nonagésima posição basta pegar o endereço da posição 0 e somar por oitenta e nove vezes 8 bytes que é o comprimento de inteiros de 64-bits. Pra qualquer outra posição é o mesmo cálculo.</p>
<p>Independente se o array tiver 10 elementos, 100 elementos ou 100 milhões de elementos, pra pegar o valor em qualquer posição o tempo gasto vai ser exatamente o mesmo, porque esse cálculo de endereço inicial mais a soma da multiplicação entre o número da posição e o tamanho de cada elemento independe do tamanho total do array. É o que em notação de Big O a gente chamaria de O 1, ou seja, tempo constante. Esse é o ideal, e todo algoritmo que chega perto disso pode ser considerado muito bom. Você vê no gráfico que à medida que o tempo corre pra direita no eixo x, a complexidade permanece constante no eixo vertical y, constante.</p>
<p>A Lista Ligada que implementamos tem complexidade O 1, constante, pra operações de inserir novos Nodes ou apagar existentes, porque independe do tamanho total da lista. Mesma coisa pro Array enquanto ele tem espaço sobrando no fim. Mas procurar elementos na Lista Ligada é da ordem de grandeza de O n, linear. No melhor caso, o elemento que queremos é o primeiro, daí só com uma comparação e achamos, no pior caso é o último elemento da lista, daí precisa navegar Node a Node até o fim.</p>
<p>Por isso existe a opção de Lista Duplamente Ligada, onde cada Node aponta pro próximo elemento e pro anterior ao mesmo tempo, assim dá pra começar uma procura do começo da lista ou do fim da lista. Mesmo assim, a complexidade, a ordem de grandeza, continua sendo linear, dependente da quantidade de elementos na lista, sendo o pior caso se o elemento que procuramos estiver no meio da lista, porque se começar a procurar do começo ou do fim, o meio é o ponto mais longe agora.</p>
<p>No hashtable que implementamos a complexidade depende do algoritmo de hashing. Se o hashing for bom, ou seja, resulta em números com pouca colisão, o tempo de inserção no melhor caso seria O 1 constante, porque por baixo é literalmente inserir num array. O pior caso é se o hashing der sempre o mesmo número pra toda chave. Por exemplo, digamos que queremos inserir 3 palavras quaisquer e o hashing de todas dá o número 10, agora todos vão estar na posição 10 do array. E a gente implementou cada elemento pra ser uma lista ligada. O tempo de inserção vira linear, porque tem que chegar até o fim da lista pra adicionar o Node novo.</p>
<p>Mas a pesquisa, que num array seria tempo constante, no hashtable vai ser linear, igual da lista ligada, O n, linear. Outra coisa importante, só porque dois algoritmos tem complexidade Big O iguais, digamos, O n, linear, não quer dizer que elas têm exatamente a mesma performance. Só significa que vão ficando mais lentas linearmente em proporção à quantidade de elementos pra processar. Ambos array e lista ligada tem complexidade O 1 constante pra inserir novos elementos, mas inserir num array é mais rápido porque já tem o espaço reservado pro elemento ao contrário da lista ligada que precisa chamar <code>malloc</code> pra reservar um espaço no heap pra cada elemento. Mas proporcionalmente o tempo de cada um é constante, independente de quantos elementos cada um tenha, entenderam?</p>
<p>O importante é lembrar que tudo que você codifica é um algoritmo (ou mais corretamente, pode ser uma heurística, mas vamos deixar isso pra outro dia). Todo algoritmo tem uma complexidade. Ela tem melhor caso, pior caso e tempo médio. No melhor caso, todas são parecidas. Se você testar um bubblesort, quicksort ou mergesort ou outros como insertion sort ou timsort, num array com 10 elementos, meio que não faz diferença nenhuma.</p>
<p>Você só vai sentir diferença quando começar a passar mil elementos, cem mil, 1 milhão, daí a diferença de tempo começa a ser mensurável e significativa. Isso é pra lembrar que só porque o código rodou na sua máquina, testando com pouca coisa, nem sempre vai funcionar pra todos os casos. Mas sem entender sobre complexidade, só vai entender como seu código é ruim quando estiver rodando em produção com dados reais, e vai ficar coçando a cabeça sem entender porque e vai ficar botando a culpa na linguagem ou no framework.</p>
<p>Seu código roda em tempos diferentes dependendo dos dados que você passar. Por exemplo, o melhor caso de um bubblesort é tentar ordenar uma lista que já tá ordenada. Ele não vai fazer swap de nenhum elemento e ao terminar de comparar com o último elemento, termina. Então vai rodar como se fosse O n, linear. Mas ironicamente, num quicksort, o pior caso é justamente passar uma lista que já tá ordenada, porque ele vai tentar ordenar e rodar em tempo quadrático em vez de loglinear. Ou seja, só porque no tempo médio o quicksort é bom, não quer dizer que é sempre bom.</p>
<p>Por isso mesmo, em linguagens como Java, Javascript, Python e outros, quando você dá <code>sort</code> não necessariamente vai ser um quicksort por baixo. No caso de Python se usa o algoritmo Timsort que é meio inspirado no mergesort com insertion sort que eu não expliquei. É mais estável e com pior caso melhor do que o quicksort. Vale a pena pesquisar. Mas o recado é que nem sempre o algoritmo mais rápido vai ser o melhor sempre, porque se o pior caso derrubar seu sistema, é melhor um algoritmo um pouco mais devagar mas que lide melhor com o pior caso.</p>
<p>Finalmente, antes que alguém jogue nos comentários que eu não mencionei, notação Big O é pode ficar controverso porque povo diz &ldquo;ah, algoritmo X é quadrático então é ruim, algoritmo Y é linear então é bom&rdquo; mas não entende que não é pra comparar só pelo Big O, especialmente sem levar em conta coisas como melhor ou pior caso, e sem levar em conta coisas como Big Omega e Big Theta. Na prática, Big O é uma notação de complexidade do limite superior, não necessariamente do pior caso. Big Omega é notação pro limite inferior, não necessariamente do melhor caso. E Big Theta é notação pro tempo entre Big O e Big Omega.</p>
<p>Se pareceu complicado é porque é um pouco mesmo, na prática Big Omega e Big Theta é mais interessante pro povo acadêmico, mais matemático da ciência da computação. Big O serve pra dar uma noção da ordem de grandeza do tempo de execução de um código ou algoritmo qualquer. Um programador não se preocupa muito com a notação formal matemática, basta entender que existe, e que algoritmos diferentes tem complexidades diferentes. Todo código é diferente, só porque códigos diferentes chegam no mesmo resultado não significa que são equivalentes. Certamente suas complexidades são diferentes. E certamente sua primeira tentativa vai ter complexidade quadrática, então toma cuidado.</p>
<p>Por agora vou encerrar essa introdução à algoritmos e estruturas de dados. E esses dois episódios foram de fato só o topo do iceberg. Mas pros próximos episódios eu precisava que pelo menos todo mundo estivesse na mesma página da existência desses conceitos e uma pequena noção do básico pra eu poder falar de outras coisas. Lembrem-se, o que eu mostrei nesses dois episódios não é um tutorial, nem um curso, pra isso tem material bem melhor. A ideia foi só mostrar que essas coisas existem pra vocês estudarem em mais detalhes.</p>
<p>Eu mesmo não sou acadêmico então me falta a matemática formal. Mas as noções principais incluem que todo algoritmo tem características diferentes. Tem o caso médio, tem os extremos, tem casos excepcionais e cada caso vai rodar com qualidades diferentes de performance e uso de recursos e por isso a gente fala que pra escolher um algoritmo, uma biblioteca, ou mesmo um framework ou arquitetura depende do problema que queremos resolver, como vai ser usado, porque aí uma solução que é boa pra um projeto pode não ser tão bom pra outro. Como já disse no episódio do livro The Mythical Man Month, não existe bala de prata e boa parte do trabalho de um programador é saber como escolher entre diferentes opções.</p>
<p>Quando eu tava na faculdade estudei essa matéria usando o livro de algoritmos do Nicolau Wirth, criador de linguagens como Modula e Pascal, então eu aprendi usando Pascal. Ela é bem simples de ler de cabo a rabo mas eu acho que pra hoje em dia tá defasado. De longe a melhor coleção é o The Art of Computer Programming, escrito por um dos maiores mitos da ciência da computação, Donald Knuth. Ele começou a escrever em 1968 um conjunto que era pra ser de 7 livros. Pausou no fim da década de 70 porque acho que tava trabalhando em outro projeto que tornou ele famoso, o sistema TeX que você deve conhecer como Latex se já escreveu algum artigo científico pra faculdade.</p>
<p>Ainda faltam 3 volumes e os 4 que já existem são bem difíceis de ler e entender sem ter uma base matemática forte. Eu mesmo não tenho paciência e só li trechos específicos. Talvez uma hora faça um episódio de alguns assuntos dele que me interessam. No geral, eu não recomendo esses livros. Pra começar são bem caros. A coleção dos 4 livros na Amazon custa 200 dólares, que convertendo hoje vai pra mais de mil reais. Qualquer livro que você achar que seja de estruturas de dados e algoritmos é suficiente. Veja a bibliografia de qualquer universidade pra ter referências.</p>
<p>Um que não é tão barato, melhor do que eu usei na faculdade, mas tá longe de ser o monstro que é o do Knuth, procure pelo Introduction to Algorithms do Cormen, Leiserson, Rivest e Stein. Diferente do que eu fiz neste vídeo, o livro do Cormen vai começar ensinando o Insertion Sort, no capítulo seguinte vai falar sobre notação assintótica que foi o que eu pulei quando falei sobre notação Big O e na sequência vai explicar o que é a estratégia de dividir pra conquistar e depois um pouco sobre análise probabilística.</p>
<p>Isso é só a introdução. Depois disso ele vai falar de algoritmos de ordenação como Heap Sort e Quicksort, depois estruturas de dados, grafos e só no final vai falar sobre NP Completeness. Eu também não li esse livro de cabo a rabo, mas se você puder, vai cobrir bastante coisa importante. Mesmo não estando no mesmo nível do Knuth, continua sendo um pouco mais pesado na parte matemática mas também com assuntos mais modernos como multithreading.</p>
<p>Mas como eu disse, não importa muito qual livro, contanto que estude pelo menos um que cubra esses tópicos principais de algoritmos, complexidade, estruturas de dados e entenda o que são algoritmos de tempo polinomial versus de tempo não polinomial. Isso é o que em vários vídeos eu venho falando que é parte da base fundamental da programação. Enquanto você não souber esse básico vai ter dificuldade de pular pra tópicos mais avançados e sempre vai ter dúvida se o que tá codando é o melhor jeito pro problema que tem que resolver, ou porque escolher biblioteca X em vez da Y ou porque certa boa prática funciona em alguns casos mas não em outros.</p>
<p>O estudo da ciência da computação é bem divertido quando você começa a sentir que tá dominando a ferramenta e não a ferramenta te dominando, e conquistar esse nível de controle vai exigir centenas de horas de esforço, estudo e exercícios. E esse estudo precisa dessa base. Espero que estes vídeos tenham dado um pouco de luz e vocês se motivem a procurar o resto sozinhos. Se ficaram com dúvidas mande nos comentários, quem já estudou tudo isso ajudem os colegas também, se curtiram o video deixem um joinha, assinem o canal, não deixem de clicar no sininho pra não perder os próximos episódios e continuem compartilhando com seus amigos pra ajudar o canal. A gente se vê, até mais.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>ciencia-da-computacao</category><category>programacao</category><category>akitando</category></item><item><title>[Akitando] #65 - A Dor de Aprender | Que Cursos/Livros?</title><link>https://akitaonrails.github.io/2019/10/23/akitando-65-a-dor-de-aprender-que-cursos-livros/</link><guid isPermaLink="true">https://akitaonrails.github.io/2019/10/23/akitando-65-a-dor-de-aprender-que-cursos-livros/</guid><pubDate>Wed, 23 Oct 2019 11:00:00 -0300</pubDate><description>&lt;div class="embed-container"&gt;
&lt;iframe
src="https://www.youtube.com/embed/HEaIsKm-pao"
title="YouTube video player"
frameborder="0"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
referrerpolicy="strict-origin-when-cross-origin"
allowfullscreen&gt;
&lt;/iframe&gt;
&lt;/div&gt;
&lt;h2&gt;DESCRIPTION&lt;span class="hx:absolute hx:-mt-20" id="description"&gt;&lt;/span&gt;
&lt;a href="#description" class="subheading-anchor" aria-label="Permalink for this section"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Pra continuar esta sequência respondendo algumas perguntas frequentes que as pessoas me tem feito em mensagens diretas em redes sociais, no episódio de hoje que explorar as perguntas de &amp;ldquo;Quais cursos são melhores? Quais livros vou aprender mais rápido? Devo começar com Design Patterns?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Eu não imaginava que as pessoas tinham tanta dificuldade no início, sobre o que exatamente devem fazer. Eu já falei bastante sobre &amp;ldquo;Aprendendo a aprender&amp;rdquo; e também sobre &amp;ldquo;prática deliberada&amp;rdquo; mas eu acho que nunca elaborei o suficiente pra ficar prático pra maioria das pessoas.&lt;/p&gt;</description><content:encoded><![CDATA[

<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/HEaIsKm-pao"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<h2>DESCRIPTION<span class="hx:absolute hx:-mt-20" id="description"></span>
    <a href="#description" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Pra continuar esta sequência respondendo algumas perguntas frequentes que as pessoas me tem feito em mensagens diretas em redes sociais, no episódio de hoje que explorar as perguntas de &ldquo;Quais cursos são melhores? Quais livros vou aprender mais rápido? Devo começar com Design Patterns?&rdquo;</p>
<p>Eu não imaginava que as pessoas tinham tanta dificuldade no início, sobre o que exatamente devem fazer. Eu já falei bastante sobre &ldquo;Aprendendo a aprender&rdquo; e também sobre &ldquo;prática deliberada&rdquo; mas eu acho que nunca elaborei o suficiente pra ficar prático pra maioria das pessoas.</p>
<p>Então eu quero dizer mais claramente o que, na minha opinião, tem de errado na maioria das recomendações pra iniciantes que eu sempre ouço, e o que eu acho que é a melhor forma de pensar no aprendizado em geral.</p>
<p>Links:</p>
<ul>
<li>Introduction to Algorithms (The MIT Press) 3rd Edition, Kindle Edition (<a href="https://www.amazon.com/Introduction-Algorithms-Press-Thomas-Cormen-ebook-dp-B007CNRCAO/dp/B007CNRCAO/ref=mt_kindle?_encoding=UTF8&amp;me=&amp;qid="target="_blank" rel="noopener">https://www.amazon.com/Introduction-Algorithms-Press-Thomas-Cormen-ebook-dp-B007CNRCAO/dp/B007CNRCAO/ref=mt_kindle?_encoding=UTF8&me=&qid=</a>)</li>
</ul>
<h2>SCRIPT<span class="hx:absolute hx:-mt-20" id="script"></span>
    <a href="#script" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Olá pessoal, Fabio Akita</p>
<p>Ultimamente eu venho tentando responder algumas perguntas frequentes, por isso fiz os episódios de não terceirizar decisões e depois sobre os níveis de experiência. Mas tem outra pergunta que muitos iniciantes fazem bastante e eu tenho tentado responder direto de vez em quando mas acho que é hora de fazer um vídeo a respeito.</p>
<p>O episódio de hoje é pra explorar a seguinte pergunta: &ldquo;Akita, que cursos eu deveria fazer ou que livros eu deveria ler pra começar em programação? Comprei o livro de Design Patterns, vai me ajudar?&rdquo; Mas e se eu te dissesse que eu leio muito pouco livro técnico e acho que nunca fiz nenhum curso de programação de verdade até hoje?</p>
<p>Pra ser exato, se você assistiu meu vídeo sobre meus primeiros 5 anos, eu fiz um pequeno curso de programação sim, em 1990, quando tinha uns 13 anos, de Basic. Este episódio é um daqueles difíceis de organizar as idéias, porque dá pra explicar de dezenas de jeitos muito diferentes. Se você não assistiu os outros vídeos do meu canal, eu sou programador desde 1991 e de lá pra cá eu aprendi e trabalhei com dezenas de linguagens e frameworks diferentes, de comerciais como Java ou C#, a open source como PHP e Ruby, a mobile como Objective-C e entreguei projetos de todos os tipos. E desde 1990 eu venho aprendendo a maior parte do que eu sei quase sempre sozinho. A intenção do vídeo de hoje é ser meio repetitivo mesmo porque eu quero tentar marretar alguns conceitos na sua cabeça.</p>
<p>Quando eu entrei na faculdade eu já sabia o básico de programação. Eu não tinha a fundação pra explicar o porquê das coisas, mas eu conseguia achar as pistas iniciais pra começar a resolver um problema. Então a faculdade nunca me ensinou a “programar” propriamente dito. Agora, eu sinto que muita gente que ansiosa tá no problema anterior: você se sente devagar. Sente que estuda, estuda, mas não avança. Parece que todo novo problema te joga de volta à estaca zero. Você consegue resolver um problema, mas o próximo não consegue mais.</p>
<p>Parece que existe um consenso num caminho: faça um curso, compre um livro, daí tente fazer um projetinho seu do zero. Já ouvi muito isso. Mas eu acho que isso não é eficiente pro aprendizado. Pra que fique claro: eu não estou dizendo que o conteúdo em si seja ruim. Eu só acho que em alguns casos não se leva em consideração que existem dois tipos distintos de público.</p>
<p>Eu dividiria os iniciantes entre aqueles que naturalmente são autodidatas e os que não são. Os que não são, por diversos motivos, tem dificuldade de desviar do material que é apresentado. Elas se sentem mais confortáveis seguindo uma receita ou um procedimento. E quando algum passo diverge do procedimento eles ficam angustiados e travam e não conseguem saber que passo tomar em seguida.</p>
<p>Autodidatas naturalmente não se importam muito com procedimento. A gente obviamente tenta seguir, mas quando um passo não funciona, em vez de sentir angústia a gente sente uma raiva que nos leva a querer entender porque o passo falhou. Foi culpa do procedimento? Foi minha própria culpa porque errei algum passo anterior? Se for esse o caso, deixa eu voltar passos e tentar de novo. Deixa eu voltar pro começo e tentar tudo de novo. Ainda não funciona? Deixa eu pesquisar. Deixa eu perguntar pra outras pessoas. E assim vamos até uma hora avançar pro próximo passo. Por isso eu digo que autodidatas sempre vão avançar independente da qualidade do material de ensino. Porque a gente naturalmente não se conforma. E quando um autodidata se dá bem, não foi por causa de algum curso ou material, foi só a determinação de avançar mesmo.</p>
<p>Por isso eu enfatizei a idéia de não delegar suas decisões num vídeo inteiro. No aprendizado os autodidatas naturalmente tomam uma decisão: se o procedimento não funciona, vou tentar descobrir porque. O procedimento não decide por mim, eu decido pelo procedimento. Os não-autodidatas quando encontram uma barreira, eles travam, e tem a expectativa que alguém ou alguma coisa vai tirar eles do problema. Como assim o procedimento não funciona? Devia funcionar. Vou cruzar os braços e esperar até alguém consertar o procedimento. E vou reclamar que o procedimento não funciona. E vou querer meu dinheiro de volta. Em qualquer outro contexto isso não está totalmente errado, mas em aprendizado é o exato oposto de aprender.</p>
<p>Eu não sei como chamar um não-autodidata então me perdoem se o termo não soa bem, mas vou chamar de pessoas passivas, que esperam as coisas acontecer em vez de fazer as coisas acontecerem. Em um ambiente de aprendizado, você precisa ser diferente: você não quer que as pessoas digam o que você tem que fazer. Você quer referências e orientações mas não ordens nem procedimentos. Mas a maioria dos tutoriais e cursos introdutórios que eu vejo se preocupam em ensinar o &ldquo;o que&rdquo; em vez do &ldquo;porque&rdquo; e faz sentido. Explicar “o que” fazer é muito mais fácil do que explicar “por que”. Vejam este canal. O &ldquo;o que&rdquo; normalmente tá no título, ou no primeiro minuto do vídeo. É o porque que custa 30 minutos ou 1 hora.</p>
<p>Agora, se alguém quer ensinar, ele quer que os alunos tenham o máximo de satisfação possível no final. Se tentar ensinar de verdade, forçar os princípios, explicar os porquês, criar situações de frustração pros alunos. A maioria vai desistir. Porém, se focar mais em exemplos simples e procedimentos, até o pior dos alunos vai conseguir passar por todos os passos. E a satisfação no final vai ser maior porque fica a falsa sensação que aprendeu alguma coisa, quando na verdade só seguiu um procedimento. E qualquer um consegue seguir procedimentos, não requer talento, tampouco habilidade.</p>
<p>É que nem quando você era criança e tinha aqueles livros de colorir com espaços numerados bonitinho. No número 1 você pinta de vermelho, no 2 você pinta de verde, vai seguindo as instruções e no final você tem um desenho colorido bonito o suficiente. Você aprendeu a pintar? Não. Você aprendeu a teoria das cores e como harmonizar essas cores no desenho? Não. Você pintou um desenho mas não avançou nenhum passo em aprender arte. Isso se chama passatempo.</p>
<p>Eu não sei se vocês já se interessaram em assistir vídeos ou documentários sobre o início dos computadores caseiros no fim dos anos 70 e começo dos 80. Na Europa com os ZX-Spectrum. Nos Estados Unidos com os Commodore. Mesmo no Brasil com os TK que eram clones dos Sinclair. E vários outros modelos similares e simples de 8-bits ou 16-bits. Você podia programar em Basic ou Assembler, linguagem de máquina mesmo. Eram computadores super fracos, com pouquíssima memória e processamento, mesmo pros padrões da época. E eles costumavam vir com um manual de instruções que normalmente ensinava o básico de Basic .. alguns exemplos de códigos pra animações e joguinhos simples nível jogo da velha. E era só isso que você tinha.</p>
<p>Muitas vezes você sequer tinha onde gravar o que programava, porque gravador de fita cassette era opcional, disk drives de 5 e 1/4 ou 3 e 1/2 eram mais caros. HD então era inacessível, muitos desses computadores nem aceitavam. A maioria das pessoas se contentava em comprar joguinhos e programas educacionais e ser só um mero usuário do computador. Uma pequena parcela aprendia a programar nessas máquinas limitadas. Sem cursos. Sem livros. Sem internet pra pesquisar nem nada.</p>
<p>Vocês perceberam que eu já contei muitas dessas histórias em episódios anteriores? Eu posso ter sido sutil, mas eu falei de tudo isso até hoje pra ver se vocês chegavam na pergunta mais óbvia. Como pessoas como eu, quando criança, conseguimos aprender a programar num ambiente muito mais hostil do que hoje em dia?</p>
<p>Eu já dei metade da resposta no vídeo de não delegar suas decisões e também no de como eu aprendi inglês sozinho. E a resposta não é porque eu ou essas outras crianças da época éramos gênios, com QI superior ou qualquer bobagem dessas. A resposta mais simples é porque a gente não deixou de ser criança até hoje. Vou relembrar o que eu disse no vídeo de inglês. Muita gente tentando aprender a falar em inglês não consegue, porque tem vergonha de falar errado. Então fica nervoso, gagueja, dá branco, e isso deixa ainda mais nervoso, gaguejando mais. Até o ponto onde a pessoa conclui: ahhh isso Não é pra mim, eu não sou inteligente o suficiente pra aprender (chora).</p>
<p>Eu não sou um profissional de ensino, nem estudioso do comportamento humano, mas eu tenho uma teoria. Tirando os casos de real deficiência física, doença, ou algo assim, eu realmente não acredito que as pessoas sejam incapazes de aprender qualquer coisa. E eu não digo isso porque eu sou algum tipo de humanista e tenho fé nas pessoas ou qualquer bobagem assim. Na verdade é até o oposto. Mas deixa eu apresentar um fato pra vocês. No exemplo do inglês por exemplo, pra você que acha difícil falar, que acha que o curso é ruim, ou que o material que você tem é ruim, ou que você só é burro mesmo e nunca vai aprender. Você fala português?</p>
<p>Concorda comigo que 100% das pessoas nascidas no Brasil, com ou sem educação formal, todas falam português? Porque existiria uma deficiência pra se aprender inglês mas não existe uma deficiência que te tornou incapaz de aprender português quando você era criança? Minha teoria é que todos nós nascemos sem noção de &ldquo;sentir vergonha&rdquo; ou qualquer tipo de &ldquo;auto-julgamento&rdquo;. Quanto mais nova uma criança, menos vergonha ela tem, mesmo falando errado. E ela fala errado o tempo todo, alguns adultos acham bonitos, outros acham irritante. A gente tenta corrigir e elas não ficam mudas de vergonha, elas atentamente copiam os adultos. Elas vão de poucas sílabas sem sentido, palavras que não formam frases direito, até conseguir argumentar coisas complicadas com os adultos. E tudo isso sem NENHUM treinamento formal.</p>
<p>Nenhuma criança precisou de curso pra aprender a chutar bola na rua. Nenhuma criança precisou de curso pra aprender a se comunicar com as outras no jardim da infância. Elas simplesmente vão e fazem. À medida que se envelhece - e aqui eu não tenho bagagem técnica pra explicar porque - cria-se uma barreira. Em algum momento você tentou fazer alguma coisa que ainda não sabia como, você errou, alguém te criticou ou tirou sarro de você, e você parou de tentar. A partir desse dia, tudo que antes você não julgava difícil começou a ficar angustiante, frustrante, você se sente desajustado, inadequado, burro e até mesmo incapaz.</p>
<p>A grande maioria das pessoas de 40 anos, se eu falar, suba num palco e fale. Ou, grave vídeos e mostre pras pessoas. Elas vão ficar aterrorizadas. Eu também fico. Até hoje quando vou dar uma palestra, não importa se é pra 500 pessoas ou pra 10 pessoas, me dá frio na barriga, começa a vir os pensamentos de &ldquo;e se me der um branco&rdquo;. Se eu errar será que as pessoas vão achar que eu sou uma fraude? A diferença é que independente disso eu sou kamikaze, então eu ligo o &ldquo;foda-se&rdquo; e simplesmente vou. Pessoas como eu não fazem as coisas porque não temos medo. A gente faz as coisas apesar do medo. O medo que você sente eu também sinto e ela continua aqui. Mesmo depois de anos e anos. Você pode se esconder e evitar o medo ou você pode aprender a conviver com o medo. Em qualquer dos dois casos o medo vai continuar, então porque não só ligar o foda-se?</p>
<p>Deixa eu fazer uma tangente. Alguns meses atrás eu decidi comprar um uniciclo. É super difícil se equilibrar nessa porcaria. A dificuldade inicial é mais ou menos a mesma de skate, do básico de skate, quero dizer. Eu passei 2 ou 3 dias caindo no chão. Se você quer aprender coisas novas, você não pode pensar &ldquo;puts, que coisa feia eu quarentão caindo no chão e me ralando todo como se fosse uma criança de 10 anos&rdquo;. É extremamente vergonhoso você sair na rua pra treinar e cair na frente dos outros. E eu tenho tomado tombo na rua mesmo. E o que eu vou fazer? Desistir e inventar justificativas de porque alguém da minha idade não devia estar fazendo essas coisas? Nem a pau.</p>
<p>E veja que interessante. A loja onde comprei até ofereceu um grupo de suporte pra iniciantes treinarem juntos. Mas eu sou muito mais teimoso e arrogante pra isso. Eu gosto de aprender as coisas sozinho. É uma coisa minha. Eu prefiro tentar usar meu tempo da forma mais eficiente possível e me deslocar pra algum lugar, num horário pré-determinado, nunca me pareceu eficiente pra mim. Eu não gosto de ter outras pessoas do lado pra cair na armadilha de usar a muleta do &ldquo;puts, isso é difícil mesmo né? Se a gente tivesse 10 anos já tinha conseguido&rdquo; e aí fica um se sentindo mais confortável porque o outro tá lamentando também. Eu vira e mexe me vejo lamentado mas eu odeio ficar me lamentando, e eu detesto mais ainda ouvir o lamento dos outros.</p>
<p>Então, pra aprender, eu comecei num sábado de manhã cedo e eu treinei sem parar, sem pausas mesmo, tomando tombo e tentando de novo, tombo atrás de tombo, até lá pelas 9 horas da noite que foi quando eu consegui andar em linha reta pela primeira vez sem cair. Eu terminei o dia com as pernas roxas, inchadas e raladas de tanto cair. E quando eu caía eu caía mesmo, deixava meu corpo inteiro ir pro chão. Acordei domingo cedo e fiz a mesma coisa, horas a fio até conseguir dar curvas. Eu tinha colocado na minha cabeça que eu ia aprender a andar naquela porcaria em 2 dias, então eu fui e fiz. E isso pra aprender o básico, não estou pulando rampas nem fazendo malabarismo não.</p>
<p>Como qualquer um faria, eu assisti videos de tutorial na sexta feira. Nenhum me ajudou muito porque nenhum disse a coisa mais óbvia. Você já tentou subir numa bicicleta e ficar parado em cima dela? Não dá, ela despenca pros lados. Como você faz pra não despencar? Você sobe na bicicleta já dando a primeira pedalada e sai pedalando. É a inércia do movimento que impede você de cair. É a mesma coisa no uniciclo, você precisa subir nela já andando. Sem isso você vai cair. Sei lá porque; tanto eu, como todo mundo que pediu pra tentar, o primeiro instinto é ficar de pé parado em cima do negócio. Agora que eu sei, é claramente idiota isso, porque ninguém vai conseguir ficar imóvel em cima disso, é impossível. No máximo você vai fazer a roda ficar girando pra frente e pra trás pra se equilibrar no mesmo lugar. Mas parado, parado é impossível.</p>
<p>E eu aprendi a andar a primeira linha reta quando esse insight me ocorreu. Daí o próximo passo foi brigar com o próprio corpo que instintivamente quer parar quando ele se vê indo pra frente sem as pernas estarem se mexendo. Sério, a memória muscular do corpo é uma droga. O corpo quer frear sozinho, daí são horas até você obrigar o seu corpo a desaprender e aceitar que o novo estado natural dele é andando pra frente em cima do negócio. Aprendizado, no seu núcleo, é uma briga de você contra você mesmo. Por isso eu acho que nós anti-sociais temos uma vantagem, porque a gente tá acostumado a brigar com nós mesmos.</p>
<p>Desculpa fazer essa longa tangente de uniciclo mas esse episódio me lembrou dessa situação, porque é igualzinho com qualquer outra coisa que você tá aprendendo. Primeiro, você precisa perder a vergonha de cair, porque é lógico, você é iniciante, você tem que cair, tem que se ralar todo, tem que ficar roxo e machucado. Segundo, você precisa desaprender o que você acha que sabe, porque senão você freia automaticamente e cai sem saber porque.</p>
<p>Respondendo a pergunta que eu fiz agora a pouco. Como nós aprendemos a programar naqueles computadores dos anos 80? A gente caía. Nossos códigos davam pau. A diferença é que a maioria desiste nas primeiras tentativas. Mas alguns de nós ficam roxos e não se importam de ficar mais roxos. Eu pensei nesse exemplo porque este fim de semana mesmo, lá estava eu andando de uniciclo na rua, não prestei atenção, tropecei e caí com tudo no chão. O que eu fiz no dia seguinte? Tomei um remédio pra dor e resolvi andar 20 quilômetros pela cidade, porque depois da queda eu senti meu corpo arregando e ficando com medo, então eu fui ensinar uma lição pra ele, que quem manda sou eu.</p>
<p>O prazer de um autodidata é provar pra ele mesmo que ele suporta a dor. A gente sente dor, igual todo mundo, mas em vez de evitar a dor, a gente faz esforço pra se acostumar a sentir dor. É bem doloroso quando você faz um curso, lê um livro, e seu código ainda não funciona ou você nem sabe como fazer um código diferente do que foi ensinado. É super frustrante. E sua primeira reação é o que? O curso é que não deve ter sido bom. Ou deve ter outro livro que vai ensinar de verdade o que precisa saber. Desculpe se eu sou o primeiro a dizer isso: a menos que você esteja disposto a encarar a dor, engolir o choro, esquecer a frustração, e começar a cair e ficar roxo na frente dos outros, não adianta nenhum curso, nenhum livro, nada, você nunca vai aprender. Ou você pára de desperdiçar dinheiro indo atrás de fórmula mágica, ou engole a dor, pára de mimimi, se compromete e foca no objetivo.</p>
<p>Vou repetir: programação é uma profissão de prática. Qualquer atividade manual como culinária, artes, esportes, como subir num uniciclo, tudo é prática. Eu aprendi o básico, sei andar num uniciclo, cobrir longas distâncias, e não cair, mas eu ainda não sei o avançado. Eu não sei pular rampas por exemplo, estou treinando agora a andar numa perna só. E porque eu faço isso? Não tenho a mínima idéia, eu simplesmente quero.</p>
<p>Até hoje todo mundo chama os invasores digitais russos ou chineses de hackers. O termo acabou sendo distorcido e hacker passou a significar, ou algo ruim e criminoso, ou algo tecnicamente muito difícil que exige um QI superior, como você vê num Mr. Robot da vida. Mas isso é o que antigamente a gente chamava de Cracker. Só que a definição original de hacker é só uma pessoa curiosa. Na verdade mais que só curioso, alguém que é curioso é alguém que faz a pergunta. Mas um hacker é alguém que também procura a resposta. Todo bom programador é um hacker.</p>
<p>Eu sempre achei que a forma mais eficiente de aprender é primeiro tentando fazer, de qualquer jeito, sem objetivos. Sem instrução nenhuma. Existem algumas etapas que você provavelmente vai passar. A primeira é a do zero. Não estou dizendo pra não fazer curso. Faça. Se você ainda não sabe, use pra aprender o básico do ambiente. Como eu uso o editor de textos que o curso apresentou? Como eu salvo o código? Como eu faço pra esse código rodar. Por que vocês acham que os vídeos técnicos que eu fiz primeiro são de como instalar o sistema operacional e configurar as formas de rodar as primeiras linguagens?</p>
<p>Mas você precisa também passar por um período, alguns dias por exemplo, de copiar código de livro, de tutorial ou de qualquer lugar do GitHub, sem se importar se você tá fazendo certo ou errado. Você tá nos primeiros dias. Sério, não tem importância nenhuma saber se você tá fazendo direito ou não. Vou repetir: não importa. A vantagem de código é que ele não custa nada. Você não gasta nada. Se der erro você não perde nada. Não se machuca. Eu gosto de correr antes de aprender a andar. Se cair do uniciclo não machucasse, a primeira coisa que eu ia fazer era pular de uma rampa, depois aprender a andar em linha reta. Mas eu provavelmente ia quebrar uma perna, então não dá pra fazer isso. Mas com código dá.</p>
<p>Corre e depois anda. Copia um monte de código. Parece idiota isso, mas abre um código do GitHub de um lado, o editor do outro lado e vai copiando, da mesma forma como você copiaria um texto de um livro no Word. A intenção é ganhar memória muscular. Ao fazer isso várias vezes, você vai instintivamente começar a ganhar noção de como os códigos são escritos, como programadores melhores que você dão nome pras coisas, com que estética. A silhueta do que é um código vai começar a se formar na sua cabeça. Nesse ponto não importa se esse código vai servir pra alguma coisa ou não. Só digita muito, por horas, dias. Você precisa se familiarizar com o máximo possível de código num período curto de tempo.</p>
<p>Eu aprendi a datilografar desse jeito. Lembram no episódio de Henry Jones que eu disse que eu copiava enciclopédia? Não era uma vez só não. Tinha verbetes que eu copiava múltiplas vezes, porque eu achava que não tinha ficado bom da primeira vez. Mas pra que fazer isso? Não tem nenhum motivo, só porque eu gostava da sensação de sair do ponto onde eu era devagar até ficar rápido copiando. Foda-se o conteúdo do que eu tava copiando. Eu tô dizendo pra você fazer a mesma coisa, mas não só pra ficar rápido. Copiando muitos textos eu pouco a pouco fui entendendo o que é o tamanho adequado de um parágrafo? Como é a composição das frases? Que palavras são mais ou menos usadas?</p>
<p>Pouco a pouco, treinando desse jeito, você vai percebendo uma coisa interessante: padrões. No sentido de repetições. Diferentes códigos de diferentes pessoas tem certos padrões embutidos nelas. Algumas são estruturais, algumas são estéticas. Pouco a pouco você vai diferenciando quais são obrigatórias, quais são opções estéticas. Nomenclatura das coisas. Tamanho das funções. Complexidade do código. Diferentes formas de resolver o mesmo problema.</p>
<p>Quando a gente é iniciante dificilmente pensa &ldquo;quero fazer um programa X&rdquo; e daí vai magicamente conseguir. Você não vai, e mesmo se chegar perto ou conseguir, vai estar uma droga. É o contrário. Você vai copiando muitos códigos, entendendo o que cada trecho faz. Aos poucos você aprende &hellip; ahh é assim que se abre um arquivo. ahh é assim que se grava um arquivo &hellip; ahh é assim que eu posso passar uma URL e baixar o conteúdo. Uma hora você pensa &hellip; peraí, se eu consigo abrir e salvar coisas num arquivo e se eu consigo baixar coisas da internet &hellip; isso é um programa que podia ser útil, vou fazer um programa que baixa os artigos dos sites que eu gosto e guarda em arquivos.</p>
<p>O objetivo aparece sozinho depois que você começa a aprender o que cada trecho diferente de código faz. Inverte sua expectativa. No fundo não importa o que você acha que quer fazer porque na prática você ainda não sabe o que é possível fazer, então suas idéias sempre vão ser medíocres e provavelmente tudo que você consegue imaginar já existe pronto. A menos que você já seja um estudioso de outra área, seja de física, matemática, direito, qualquer outra coisa, suas idéias iniciais sempre vão ser simplórias. Por isso Não importa tanto o objetivo do código, só importa você codar.</p>
<p>Estou sendo bem repetitivo porque parece que não é tão óbvio. Quantas horas você já gastou digitando código? A diferença entre hoje e na minha época é que hoje você tem um troço chamado GitHub onde pode baixar todo tipo de exemplo de código possível e imaginável. Eu nunca mais ia dormir se aos 13 anos eu tivesse um GitHub. Falando em não dormir, o que eu vou dizer agora é o clichê do clichê mas eu vou dizer mesmo assim. Pessoas como eu amam fazer as coisas a noite. Estudar ou qualquer coisa. E existe uma grande razão pra isso: todo mundo tá dormindo de noite. Ninguém te liga. Ninguém te manda mensagem. Ninguém te atrapalha nem te interrompe. Se você mora com outras pessoas na mesma casa, elas provavelmente estão dormindo.</p>
<p>A melhor coisa pra estudar ou produzir coisas criativas é estar completamente focado. É o estado que as pessoas olham pra mim e me chamam de autistas. Porque quando eu quero entrar nesse modo eu literalmente ignoro todo mundo. Meu celular fica permanentemente em “Não perturbe”. Eu literalmente não atendo meu celular. Mesmo mensagens em whats ou direct ou email, eu raramente respondo na hora. Eu respondo quando eu estiver com vontade de responder, nunca antes. E por isso também eu gravo e edito meus vídeos a noite. Não tem barulho, não tem maldito cachorro barulhento do vizinho, não tem barulho de carro, nada pra atrapalhar. Você precisa encontrar seu espaço isolado e usar ele pra estudar e produzir. E não existe mais viciante e mais improdutivo do que redes sociais. Redes sociais é a definição de perda de tempo. Não tem absolutamente nada em nenhuma rede social que exige sua atenção imediata. E se você se acostumar a não querer engajar em alguma coisa na hora que você vê, e espera 5 minutos, quando voltar vai ver que não era assim tão importante que merece seu tempo de comentar. Threads de redes sociais são basicamente coisas inúteis, mas é bom pra quando você vai no banheiro e já tá enjoado de candy crush.</p>
<p>A idéia não é você sair contribuindo em projetos de código aberto também. Se tiver a oportunidade pode fazer, mas não é esse o objetivo principal pra você que é iniciante. O objetivo é você pegar fluência no código. Fluência não é fazer tudo certo em todos os passos. É você dar a cara pra bater e errar sem se intimidar em sentir vergonha. Fluência em uma língua nova, tipo inglês, é a mesma coisa, é a atitude de simplesmente falar, mesmo se estiver errado, e não ter vergonha e se intimidar quando alguém te corrigir.</p>
<p>Você vai notar que a recomendação aqui é igual do vídeo de como aprender inglês. Porque é a mesma coisa. Em vez de tentar decorar gramática, decorar vocabulário e tentar escrever um texto todo certo da primeira vez, e se frustrar, porque obviamente você não vai conseguir. Eu acho mais fácil ler e ouvir o máximo possível de tudo que é lugar. Igual quando você aprendeu português quando era criança. Você aprende a falar igual o que se fala na televisão e igual os adultos ao redor falam. Você não aprendeu nenhuma regra gramatical, você aprendeu via reconhecimento de padrões, ou patterns.</p>
<p>Nós seres humanos somos muito bem equipados pra detectar patterns via nossos sentidos. O que significa isso? Significa que somos bons em detectar coisas que se repetem ao nosso redor. Mas é uma faca de dois gumes. Ilusões de ótica, por exemplo, brincam com os bugs nesse nosso sistema. Muitas vezes esse sentido nos engana, e aí criamos superstições. Por exemplo, associamos muito tempo atrás a correlação de que passar embaixo de uma escada dá azar. Provavelmente porque algumas pessoas que foram vistas passando debaixo de uma escada depois sofreram algum infortúnio. E o pattern de &ldquo;passar embaixo de escada e sofrer&rdquo; acabou pegando. A maioria das besteiras que dão trending no Twitter são isso: falácias lógicas por viés de confirmação, uma falha de correlação, vítima do nosso excelente sistema de detecção de patterns. O principal: só porque você nota uma grande repetição de alguma coisa, não a torna boa, pode ser o que chamamos de anti-pattern.</p>
<p>Eu já expliquei no meu vídeo de inglês sobre a diferença entre patterns e padrões. Mas vou repetir aqui de novo pra relembrar. Em inglês existem três palavras diferentes que traduzem pra padrão em português. Temos Standard que é &ldquo;o&rdquo; padrão, como um regulamento ou regra a ser seguida. Temos Default que é a escolha padrão, ou seja, dentro de várias opções se você não escolher nenhuma o sistema pode escolher o default. Default pode ser também quando você não paga uma dívida e dá default nela. Finalmente, o que a gente usa em tecnologia que é &ldquo;um&rdquo; padrão ou repetição, isso é um Pattern. Patterns não representam uma regra ou regulamento ou o melhor jeito, são simplesmente coisas que se repetem várias vezes.</p>
<p>Isso é importante explicar aqui porque várias vezes muitos iniciantes me falam que estão estudando o livro de Design Patterns do Gang of Four, e me perguntam se isso vai ajudar eles a virarem programadores melhores. Minha resposta é simples: se você é iniciante, isso vai ajudar muito pouco pra quase nada. Patterns é uma coisa que você vai entendendo melhor ao longo do tempo e não adianta só estudar uma vez, você vai revisitando de anos em anos e vai entendendo melhor a cada nova revisitada.</p>
<p>Entendam, a idéia de patterns foi inspirada no livro A Language Pattern do arquiteto Christopher Alexander. Arquiteto de construções de verdade mesmo. Ele compila os patterns vistos em arquitetura que podem ajudar na composição de novos designs usando uma linguagem comum usada a séculos em construções pelo mundo. É um catálogo de organização urbana com mais de 250 patterns que descrevem a visão do Alexander de como uma cidade perfeita poderia ser. Do layout das ruas, até o café da esquina.</p>
<p>O que é um pattern em arquitetura. Vejamos um problema trivial: como entrar e sair de uma casa? Um buraco com uma tábua que abre e fecha. Nome do pattern? Porta. Dá pra implementar de outras formas? Claro, pode ter uma câmera e um computador pra abrir e fechar sozinha. Pode ser redonda em vez de retangular. Pode ser uma porta com outra porta no meio.</p>
<p>Agora, você não precisou ter o livro do Alexander pra saber o conceito da porta. Se você construir uma casinha do zero e esquecer da porta, você não vai conseguir entrar na casa. Mas você sempre pode pegar uma marreta e abrir uma porta na força pela parede. É meio idiota mas funciona. Em código é mais fácil. Se você esquecer da porta, sempre pode refatorar ou reescrever o trecho do código que precisava da porta. O custo do código é proporcionalmente quase zero se comparado a corrigir um erro no mundo físico, de tijolo e madeira.</p>
<p>Meu ponto é que os patterns surgem da observação de centenas de construções. E criam uma linguagem pra descrever os elementos dessas construções. Milhares de pessoas ao longo da história vieram implementando essas construções e agora só ganharam um nome pra podermos falar delas. Eu pensei isso quando o livro do Gang of Four saiu pela primeira vez em 1994. Lembre-se que antes disso a gente já fazia bons softwares e não havia uma formalização de patterns. Ela é útil e importante, mas não é crucial, especialmente no começo.</p>
<p>O livro ganhou notoriedade porque coincidiu com o nascimento do mercado de Java. Então pra uma novo mercado de programadores, orientação a objetos e design patterns pareciam uma coisa nova que andavam juntos e eram a “regra”, o “standard” a ser seguido. Mas na realidade, comece a programar, observar o código de outras pessoas, e você naturalmente chega na maioria dos patterns do livro. De command, a visitor, a observer, a builder. A grande maioria dos patterns, aliás, foi derivado de frameworks de interfaces gráficas como Motif nos Unix. Se você já teve a oportunidade de criar ou usar uma toolkit de elementos gráficos como um tcl/tk ou Qt ou Gtk ou WPF ou Apple UIKit, vai facilmente ver muitos dos patterns do gang of four neles.</p>
<p>E esses não são os únicos patterns. Hoje em dia, por alguma razão, o Domain Driven Design do Eric Evans, que foi lançado em 2003 foi ressuscitado por causa do famigerado pattern Repository. No mundo Ruby on Rails a gente se inspirou no Patterns of Enterprise Application Architecture do Martin Fowler pra pegar emprestado o pattern que ele cunhou como ActiveRecord. Todo guideline visual de interfaces gráficas da Apple, do Gnome, e outros são patterns de como implementar aplicações gráficas coerentes.</p>
<p>Tudo isso e muito mais são patterns. Mas eu acho muito pouco útil estudar os patterns sem ter experiência anterior em programação. Eu acho mais útil estudar patterns depois que você já treinou escrevendo muito código e lendo muito código dos outros. Daí o estudo dos patterns vai ajudar a criar uma linguagem pra descrever patterns de programação que você já estava acostumado a ver mas só não sabia os nomes. Foi o que aconteceu comigo. Ah, isso que eu fazia se chama Visitor, bom saber. Ah, esse outro jeito aqui é um chain of responsability, que fancy, bom saber também.</p>
<p>Se você com pouca experiência começa estudando e decorando os patterns primeiro e depois vai querer tentar aplicar, você vai usar tudo errado. Patterns são ferramentas. Você não deve usar todas ao mesmo tempo. Cada problema tem uma ferramenta pra escolher e nem sempre essa escolha é simples. Até hoje muita gente discute ActiveRecord versus Repository. Eu acho engraçado povo discutindo isso em 2019 sendo que foi ponto de grande discussão em 2003 ao ponto de eu ficar enjoado de ouvir sobre Repository e na época era versus DAO ou Data Access Objects que a Microsoft usava bastante.</p>
<p>Vejam, 16 anos depois e até agora as pessoas ainda não chegaram num consenso sobre acesso e abstração de dados. Além de ActiveRecord, Repository, DAO ainda temos outros patterns como Data Mapper, Table e Row Data Gateway e outros patterns. Patterns não são regras, não são “o” jeito certo de resolver todos os problemas, são apenas repetições, cada qual emergiu pra resolver um problema diferente numa situação diferente.</p>
<p>Por isso eu digo que tanto faz que curso você faça, que livro você leia, no fundo eles são quase todos iguais porque todos vão ensinar mais ou menos do mesmo jeito: do jeito linear, passo a passo, que raramente é o que você vai encontrar no mundo real. No mundo real você tem um problema pra resolver, e sua solução vai depender do quanto você treinou com quantas ferramentas e técnicas diferentes. Pra chutar uma combinação de elementos que pode ou não resolver o problema da forma mais eficiente possível, dentro das limitações e circunstâncias em que você se encontra. Quanto menos você treinou, menos opções você vai ter. Se você só treinou um jeito, você sempre só vai querer resolver de um jeito, e normalmente não é o jeito mais eficiente. E por isso eu falo pra não se apaixonar por uma linguagem ou uma ferramenta. Daí vem o ditado: pra quem só conhece martelo, todos os problemas parecem pregos.</p>
<p>Alguns anos atrás eu comecei a estudar Elixir, Crystal e Rust. Eu li a documentação dos sites deles primeiro. Eu procurei os poucos tutoriais que eles tinham disponíveis 4 anos atrás. E eu simplesmente fui codando, qualquer coisa. Códigos sem nenhum propósito além de exercício. A minha vantagem quando aprendi essas linguagens é que eu já tinha experiência de programação em outras linguagens, então eu não levei mais que 30 dias pra aprender cada uma. A que eu até hoje tenho menos fluência é Rust porque foi o que eu menos treinei. Elixir foi o que tinha melhor material pelo fato que ele pôde aproveitar as décadas de existência e maturidade do Erlang que está por baixo dele, então o framework OTP já era bem compreendido e com inúmeros exemplos, patterns e anti-patterns conhecidos e bem documentados. Crystal foi quase trivial porque ele se valeu de usar quase a mesma sintaxe de Ruby e usar os patterns de Go pra concorrência, então se você já sabe Ruby ou se você já sabe Go, pular pra Crystal é uma questão de poucos dias.</p>
<p>Quanto mais você vai desistindo a cada dificuldade que aparece e desiste de ir até o fim pra resolver, mais difícil as coisas vão ficando. Se você aprender a aprender, que é hackeando seu caminho, resolvendo problemas que o material não previu, ou seja, aprendendo a ser autodidata, a vantagem é que quanto mais você aprender assim e criar seu próprio ritmo, menos difícil vai ficando aprender coisas novas. A maioria das coisas novas é uma melhoria de coisas anteriores. Então as primeiras linguagens que eu aprendi em 1990, que foram Basic e dBase, são hiper simples pros padrões de hoje, mas eu levei meses e meses pra aprender. Porque eu não tinha nem material, nem base de referência. Linguagens ordens de grandeza mais complicados como um Elixir, pra mim, é uma questão de semanas pra me tornar produtivo nelas. Não me tornar um expert, cuidado, me tornar produtivo.</p>
<p>A idéia não é falar mal dos cursos e livros, só apontar que é impossível criar um curso ou livro que consiga te mostrar como é programação na vida real. Eles virariam livros de milhares de páginas e extremamente tediosos de ler. A estrutura de cursos e livros é tentar transmitir um conhecimento no menor tempo e esforço possíveis. É como um livro de artesanato, ele pode te dar passo a passo de como pregar madeira e prego e fazer uma cadeira. Mas se você nunca usou um martelo nem nunca cortou madeira na vida, na realidade você vai quebrar um monte de tábuas, entortar um monte de pregos, provavelmente vai martelar o próprio dedo e se cortar um monte de vezes até conseguir fazer uma cadeira que não vai se parecer nada com a foto do livro. No final a diferença é só isso: quem desiste, o caminho acaba aí. Não tem outro caminho a não ser ir em frente, independente da sua frustração ou da sua vergonha.</p>
<p>Sobre conhecimento de fundação. Eu diria que se tem uma matéria de faculdade que é obrigatório e separa os amadores e hobistas dos que vão fazer uma carreira de verdade em programação é Algoritmos e Estruturas de Dados. Qual é a diferença entre um Tuple ou uma Lista Ligada? Qual a diferença de um String de conteúdo imutável e um String Buffer que você pode adicionar e modificar o conteúdo? Aliás, se é tudo binário, qual é a diferença de um integer e um string? O que de fato é a diferença entre um float e um double? Toda linguagem tem essas coisas, é a base de TODAS as linguagens. Um Java parece difícil porque ela tem umas 20 classes diferentes só pra Listas. Por exemplo, qual a diferença de um ArrayList, um LinkedList, um Stack, um SortedSet, uma Queue?</p>
<p>Daí você precisa aprender: qual a forma mais fácil de estruturar os dados pra ser fácil recuperar o que você precisa depois? Vai passar pelas coisas triviais como Quicksorts ou Mergesorts até chegar em coisas mais legais como Bloom filters e até hash rings e consistent hashing. Quando você entende esses elementos, coisas como computação distribuída, replicação de bancos de dados como NoSQL e tudo mais começam a clicar na sua cabeça mais fácil. Mas pra chegar nisso você precisa sair do básico. Entender as estruturas de dados e algoritmos mais simples primeiro, de um mero struct de C, union types, nomes como markov, levenshtein, huffman e assim por diante.</p>
<p>Algoritmos e Estruturas de Dados eu não vejo como você poderia aprender organicamente, sem um material formal. Vai precisar estudar a teoria e praticar centenas de vezes pra realmente ganhar fluência. Por isso eu acho que linguagens como C ou Pascal eram boas pra começar, porque você era obrigado a entender essas coisas pra fazer qualquer coisa. A maioria das linguagens modernas esconde essas coisas de você, então você não nota que vai precisar até muito tarde.</p>
<p>Esquece estudar Design Patterns ou Arquiteturas de Sistemas antes de aprender Estruturas de Dados e Algoritmos. Estou repetindo isso várias vezes pra vocês colocarem isso na cabeça. Antes de dominar o básico, esquece o avançado, não vai acontecer. É que nem querer aprender culinária e achar que não precisa aprender Mis en place ou descascar batata. Tudo que você constrói precisa de fundações sólidas. Quanto mais porcaria for sua fundação pior vai ser seu puxadinho. Você vai bater num teto muito rápido e não vai conseguir crescer, e seu puxadinho toda hora arrisca desabar. Se preocupe em fundações sólidas, não em hype da moda.</p>
<p>Hype da moda, linguagem ou framework da moda, tudo isso é simples. Sério, se você tem boas fundações, aprender um novo framework “tem” que ser simples. Aprender uma nova linguagem tem que ser simples. Claro, ganhar fluência exige tempo, mas você não vai ficar coçando a cabeça toda hora ficando frustrado porque não consegue começar a aprender. Você tem dificuldade de aprender porque tem preguiça e tá postergando sofrer a fundação, que é chato, teórico, difícil e parece que não tem utilidade. Mesma coisa técnica de usar a faca direito pra cortar as coisas. Você teima em não aprender, e não consegue cortar mais rápido sem cortar os dedos juntos. Porque você é burro, a gente já sabe o jeito certo, isso é o básico, a fundação, o que todo mundo que vai cozinhar já devia saber.</p>
<p>Aliás, caso não seja óbvio, frameworks são implementações de patterns pra resolver um determinado tipo de problema. Por exemplo, frameworks de GUI pra interfaces gráfica como o UIKit da Apple ou frameworks web como Rails e Laravel pra aplicativos web. Ou frameworks como OTP do Erlang pra sistemas distribuídos. Frameworks são manifestações em forma de implementação de patterns reusáveis de programação.</p>
<p>Outra coisa interessante é que muitos design patterns podem ser considerados defeitos nas linguagens. A gente costuma chamar esse tipo de boilerplates, que são trechos de código que todo mundo meio que convencionou a fazer toda vez. É uma sensação de .. “porque eu estou tendo toda hora que fazer esse mesmo código em todo lugar em vez da linguagem ou mesmo do framework fazerem isso por mim?” .. É quando você é obrigado a criar funções ou classes vazias ou arquivos pra lá e pra cá, só pro negócio compilar ou rodar.</p>
<p>Eu vou dizer que ainda não tô totalmente contente com essa minha explicação até agora. Mas os pontos principais pra resumir: Primeiro, estude obrigatoriamente Algoritmos e Estruturas de Dados. Segundo, desligue redes sociais, coloque seu celular em modo de não perturbe o tempo todo. Terceiro, não se preocupe em codar coisas com objetivo no começo, os objetivos do exercício não importam, o único objetivo é codar e codar. Quarto, não importa qual curso e qual livro você vai escolher pra aprender a primeira linguagem, tanto faz mesmo, a diferença entre a maioria dos cursos mais conhecidos é marginal. Quinto, treine, treine, treine e treinar não é fazer projetinho do zero. Nada de bom sai de uma cabeça vazia. Treinar é copiar qualquer código sem nenhum objetivo, quebrar esse código, combinar códigos de pessoas diferentes, mas principalmente, copiar a maior quantidade de código dos outros quanto possível. Só depois de copiar e copiar trechos por centenas de horas você vai começar a identificar sozinho alguns patterns. Quando finalmente você se sentir confortável com os puxadinhos de código que começam a funcionar de forma precária, talvez você possa estudar os diversos design patterns que existem por aí só pra dar nome pros seus puxadinhos. E é assim que você começa.</p>
<p>Eu falei isso no vídeo de Conhecimentos Gerais no começo da série Começando aos 40. Eu acabei de dizer que a coisa mais importante é se expôr à maior quantidade de código dos outros quanto possível, mesmo se não entender nada da primeira vez que ler. É como aprender uma nova língua e desligar as legendas dos filmes. A idéia é você sofrer mesmo, e aprender a lidar com essa angústia de não estar entendendo nada. Pra isso você pode e deve usar o GitHub que está dando sopa com milhões de projetos em todas as linguagens e frameworks que você pode imaginar. É a biblioteca de Alexandria do código. Pra isso, a primeira coisa que você deve aprender é a usar Git, e o Google tá aí pra isso. Hoje em dia tem muito tutorial simples. Se você ainda não sabe, aprende. Qual é a desculpa?</p>
<p>E é isso aí, eu queria deixar uma resposta mais completa pra essa indagação do que eu estava conseguindo dizer nas mensagens diretas. Espero que tenha ajudado a criar um modelo mental do que você devia estar fazendo pra superar essa fase inicial de aprendizado em qualuqer coisa. Se ficou com dúvidas não deixe de mandar nos comentários, se tiver outras dicas também são muito bem vindas. Se curtiram o vídeo mandem um joinha, não deixem de assinar o canal e cliquem no sininho pra não perder os próximos episódios. A gente se vê, até mais.</p>
]]></content:encoded><category>aprendizado</category><category>algoritmos-e-estruturas-de-dados</category><category>akitando</category></item><item><title>Como o Bitcoin Força Consenso entre os Generais Bizantinos?</title><link>https://akitaonrails.github.io/2017/11/01/como-o-bitcoin-forca-consenso-entre-os-generais-bizantinos/</link><guid isPermaLink="true">https://akitaonrails.github.io/2017/11/01/como-o-bitcoin-forca-consenso-entre-os-generais-bizantinos/</guid><pubDate>Wed, 01 Nov 2017 14:02:00 -0200</pubDate><description>&lt;p&gt;&lt;em&gt;&amp;ldquo;Dá pra quebrar o blockchain?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;É uma pergunta justa. Quem conhece minimamente a arquitetura do blockchain conclui instintivamente que &amp;ldquo;não&amp;rdquo;, é praticamente improvável que alguém consiga quebrar. Na prática, é basicamente impossível.&lt;/p&gt;
&lt;p&gt;O que me espanta é que a maioria dos programadores que conheço tem uma dificuldade enorme de superar o preconceito contra criptomoedas. Não faço ideia de onde vem esse preconceito, mas conheço pessoas muito inteligentes que resolvem os problemas mais difíceis de escalabilidade web e nunca deram uma olhada sequer no paper original de Satoshi Nakamoto — um documento curtíssimo descrevendo o blockchain.&lt;/p&gt;</description><content:encoded><![CDATA[<p><em>&ldquo;Dá pra quebrar o blockchain?&rdquo;</em></p>
<p>É uma pergunta justa. Quem conhece minimamente a arquitetura do blockchain conclui instintivamente que &ldquo;não&rdquo;, é praticamente improvável que alguém consiga quebrar. Na prática, é basicamente impossível.</p>
<p>O que me espanta é que a maioria dos programadores que conheço tem uma dificuldade enorme de superar o preconceito contra criptomoedas. Não faço ideia de onde vem esse preconceito, mas conheço pessoas muito inteligentes que resolvem os problemas mais difíceis de escalabilidade web e nunca deram uma olhada sequer no paper original de Satoshi Nakamoto — um documento curtíssimo descrevendo o blockchain.</p>
<p>Sério, o paper <a href="https://bitcoin.org/bitcoin.pdf"target="_blank" rel="noopener">Bitcoin: A Peer-to-Peer Electronic Cash System</a> é tão ridiculamente pequeno e fácil de entender que qualquer estudante de ciência da computação consegue acompanhar. Qualquer programador mediano lê e entende em uns 30 minutos.</p>
<p>O modelo mental mais simples é o de uma Lista Ligada, onde cada nó da lista é o que chamamos de Bloco. Cada Bloco é uma struct simples, com os ponteiros de anterior/próximo de sempre, e um corpo em estrutura de árvore (uma <a href="https://brilliant.org/wiki/merkle-tree/"target="_blank" rel="noopener">Merkle Tree</a>, para ser preciso).</p>
<p>O detalhe que muda tudo: cada bloco carrega a assinatura hash do bloco anterior, formando uma &ldquo;cadeia&rdquo; segura. Daí o nome &ldquo;block-chain&rdquo;.</p>
<p>Em termos de ciência da computação, estamos falando de estruturas de dados de primeiro ano. Se você entende Lista Ligada e Árvore Binária, mais a coisa mais simples de criptografia — um Digest Hash como SHA256 —, pronto, você entende a espinha dorsal do banco de dados blockchain.</p>
<p>Porque é só um banco de dados. Um banco distribuído, para ser mais exato. Ou melhor: um banco distribuído bastante cru e simples. Não é eficiente, fica atrás de bancos NoSQL distribuídos sérios como Redis ou Cassandra. As capacidades de consulta são praticamente inexistentes além de encontrar um bloco pelo seu identificador.</p>
<p>Claro, o código-fonte do Bitcoin é mais sofisticado do que isso — mas o básico é tão simples que você não precisa de mais de 20 linhas de Ruby para replicar. Veja esta implementação de exemplo do <a href="https://github.com/openblockchains/awesome-blockchains/tree/master/blockchain.rb"target="_blank" rel="noopener">Gerald Bauer</a>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;digest&#34;</span>    <span class="c1"># for hash checksum digest function SHA256</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Block</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="kp">attr_reader</span> <span class="ss">:index</span>
</span></span><span class="line"><span class="cl">  <span class="kp">attr_reader</span> <span class="ss">:timestamp</span>
</span></span><span class="line"><span class="cl">  <span class="kp">attr_reader</span> <span class="ss">:data</span>
</span></span><span class="line"><span class="cl">  <span class="kp">attr_reader</span> <span class="ss">:previous_hash</span>
</span></span><span class="line"><span class="cl">  <span class="kp">attr_reader</span> <span class="ss">:hash</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">initialize</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">previous_hash</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@index</span>         <span class="o">=</span> <span class="n">index</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@timestamp</span>     <span class="o">=</span> <span class="no">Time</span><span class="o">.</span><span class="n">now</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@data</span>          <span class="o">=</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@previous_hash</span> <span class="o">=</span> <span class="n">previous_hash</span>
</span></span><span class="line"><span class="cl">    <span class="vi">@hash</span>          <span class="o">=</span> <span class="n">calc_hash</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">calc_hash</span>
</span></span><span class="line"><span class="cl">    <span class="n">sha</span> <span class="o">=</span> <span class="no">Digest</span><span class="o">::</span><span class="no">SHA256</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">    <span class="n">sha</span><span class="o">.</span><span class="n">update</span><span class="p">(</span> <span class="vi">@index</span><span class="o">.</span><span class="n">to_s</span> <span class="o">+</span> <span class="vi">@timestamp</span><span class="o">.</span><span class="n">to_s</span> <span class="o">+</span> <span class="vi">@data</span> <span class="o">+</span> <span class="vi">@previous_hash</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">sha</span><span class="o">.</span><span class="n">hexdigest</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">first</span><span class="p">(</span> <span class="n">data</span><span class="o">=</span><span class="s2">&#34;Genesis&#34;</span> <span class="p">)</span>    <span class="c1"># create genesis (big bang! first) block</span>
</span></span><span class="line"><span class="cl">    <span class="c1">## uses index zero (0) and arbitrary previous_hash (&#34;0&#34;)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Block</span><span class="o">.</span><span class="n">new</span><span class="p">(</span> <span class="mi">0</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="s2">&#34;0&#34;</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">next</span><span class="p">(</span> <span class="n">previous</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="s2">&#34;Transaction Data...&#34;</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="no">Block</span><span class="o">.</span><span class="n">new</span><span class="p">(</span> <span class="n">previous</span><span class="o">.</span><span class="n">index</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">data</span><span class="p">,</span> <span class="n">previous</span><span class="o">.</span><span class="n">hash</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Pois é.</p>
<p>Uma pergunta permanece: como essa estrutura simples vira um banco de dados &ldquo;distribuído&rdquo;?</p>
<p>Você precisa de uma &ldquo;cópia-mestra&rdquo; centralizada da qual todas as outras cópias replicam — ou precisa de alguma forma de &ldquo;consenso&rdquo; entre as cópias diferentes.</p>
<p>Como chegar a consenso entre nós aleatórios e sem controle espalhados pelo globo? Esse é o problema chamado <a href="http://pmg.csail.mit.edu/papers/osdi99.pdf"target="_blank" rel="noopener">&ldquo;Tolerância a Falhas Bizantinas&rdquo;</a>, explicado e resolvido brilhantemente por Barbara Liskov e Miguel Castro, do MIT, em 1999.</p>
<p>Em resumo: imagine generais bizantinos, cada um com seu próprio exército, cercando uma cidade hostil. A decisão é atacar ou recuar. Mas todos os generais precisam tomar a mesma decisão, em consenso. Como chegar a esse consenso quando não há comunicação direta com todos os generais e, pior, quando alguns podem ser traidores ou agentes duplos?</p>
<p>Esse é exatamente o tipo de problema que temos aqui. Qualquer pessoa na internet pode baixar uma cópia do blockchain e verificar que os blocos são válidos e não adulterados, recomputando os hashes de cada bloco.</p>
<p>Mas como adicionar novos blocos e fazer os outros nós aceitarem o seu bloco?</p>
<p>Foi por isso que Satoshi adicionou o chamado &ldquo;Proof of Work&rdquo; à equação. Cada bloco está encadeado ao anterior por conter o hash do bloco anterior — e computar um hash digest é algo trivial hoje em dia.</p>
<p>Em Ruby, por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Digest</span><span class="o">::</span><span class="no">SHA256</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">(</span><span class="s2">&#34;abcd&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;88d4266fd4e6338d13b845fcf289579d209c897823b9217da3e161936f031589&#34;</span>
</span></span><span class="line"><span class="cl"><span class="no">Digest</span><span class="o">::</span><span class="no">SHA256</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">(</span><span class="s2">&#34;123&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso roda em uma <strong>fração de milissegundo</strong>.</p>
<p>Agora, e se eu pedir para você encontrar o hash que começa com uma certa quantidade de &ldquo;zeros&rdquo; no início?</p>
<p>Por exemplo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="c1"># Quero encontrar 4 zeros (&#34;0000&#34;) no hash:</span>
</span></span><span class="line"><span class="cl"><span class="no">Digest</span><span class="o">::</span><span class="no">SHA256</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">(</span><span class="s2">&#34;79026&#34;</span> <span class="o">+</span> <span class="s2">&#34;123&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># =&gt; &#34;0000559fb4a55f135c7db3d83405b86b4b63cd035993873a5b676bae08b64334&#34;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como eu sei que preciso acrescentar &ldquo;79026&rdquo; antes? Não sei. Preciso começar do zero e incrementar um a um até encontrar o hash no formato desejado.</p>
<p>Olhando o <a href="https://github.com/openblockchains/awesome-blockchains/blob/master/blockchain.rb/blockchain_with_proof_of_work.rb#L29-L45"target="_blank" rel="noopener">exemplo do Gerald</a>, implementaríamos essa busca assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">compute_hash_with_proof_of_work</span><span class="p">(</span> <span class="n">difficulty</span><span class="o">=</span><span class="s2">&#34;00&#34;</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">nonce</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">  <span class="kp">loop</span> <span class="k">do</span>
</span></span><span class="line"><span class="cl">    <span class="nb">hash</span> <span class="o">=</span> <span class="n">calc_hash_with_nonce</span><span class="p">(</span> <span class="n">nonce</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">hash</span><span class="o">.</span><span class="n">start_with?</span><span class="p">(</span> <span class="n">difficulty</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="k">return</span> <span class="o">[</span><span class="n">nonce</span><span class="p">,</span><span class="nb">hash</span><span class="o">]</span>    <span class="c1">## bingo! proof of work if hash starts with leading zeros (00)</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="n">nonce</span> <span class="o">+=</span> <span class="mi">1</span>             <span class="c1">## keep trying (and trying and trying)</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">calc_hash_with_nonce</span><span class="p">(</span> <span class="n">nonce</span><span class="o">=</span><span class="mi">0</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">sha</span> <span class="o">=</span> <span class="no">Digest</span><span class="o">::</span><span class="no">SHA256</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl">  <span class="n">sha</span><span class="o">.</span><span class="n">update</span><span class="p">(</span> <span class="n">nonce</span><span class="o">.</span><span class="n">to_s</span> <span class="o">+</span> <span class="s2">&#34;123&#34;</span> <span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">sha</span><span class="o">.</span><span class="n">hexdigest</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Um SHA256 simples leva entre 0.000010 e 0.000020 segundos (lembre: frações de milissegundo). Quanto tempo leva para encontrar aquele &ldquo;79026&rdquo; (o que chamamos de &ldquo;nonce&rdquo;)?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>&gt; puts Benchmark.measure { compute_hash_with_proof_of_work(&#34;0000&#34;) }
  0.190000   0.000000   0.190000 (  0.189615)</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Consideravelmente mais: 0.18 segundos em vez de 0.000020. Podemos aumentar a variável de &ldquo;dificuldade&rdquo; para tornar a busca pelo nonce ainda mais trabalhosa. E é exatamente assim que o Bitcoin funciona: cada bloco ajusta a dificuldade de modo que o nó mais rápido leve em torno de 10 minutos para encontrar o hash do próximo bloco.</p>
<p>E isso, meu caro, é o que chamamos de <strong>&ldquo;MINERAÇÃO&rdquo;</strong>. O que um minerador faz é executar um loop, incrementando nonces sobre o digest do bloco, até encontrar o nonce correto.</p>
<p>Encontrado o nonce, o minerador adiciona o bloco ao blockchain e o propaga para os outros nós. Os outros nós fazem a verificação (agora de volta ao procedimento de 0.000020 segundos — rapidíssimo).</p>
<p>Quando os nós verificam e confirmam o nonce, todos adicionam o bloco ao topo do blockchain. Conforme outros mineradores continuam empilhando blocos sobre ele, o bloco vai se tornando &ldquo;solidificado&rdquo;. O bloco mais recente no topo é geralmente instável, mas com mais blocos em cima ele é considerado mais &ldquo;garantido&rdquo; — por isso a maioria das exchanges e serviços que aceitam Bitcoin aguarda as famosas &ldquo;6 confirmações de blocos&rdquo;.</p>
<p>Como a dificuldade é calibrada para que o nó mais rápido leve cerca de &ldquo;10 minutos&rdquo; para encontrar o nonce, um bloco é considerado &ldquo;seguro&rdquo; quando cerca de 1 hora passa e 6 blocos são adicionados depois dele.</p>
<h2>Dá pra quebrar isso?<span class="hx:absolute hx:-mt-20" id="dá-pra-quebrar-isso"></span>
    <a href="#d%c3%a1-pra-quebrar-isso" class="subheading-anchor" aria-label="Permalink for this section"></a></h2><p>Agora você vai entender por que falamos em &ldquo;hash power&rdquo; quando o assunto é mineração.</p>
<p>Minerar é assinar e confirmar blocos no blockchain. É um serviço de manutenção — por isso você recompensa os mineradores com &ldquo;taxas de transação&rdquo; e alguns &ldquo;satoshis&rdquo; (frações de 1 Bitcoin) pelo trabalho. E por isso também o mecanismo se chama &ldquo;Proof of Work&rdquo;: quando alguém encontra um nonce, sabemos que essa pessoa passou por uma enorme quantidade de computação de hashes para chegar lá.</p>
<p>Daí também falamos em &ldquo;hash rates&rdquo; quando nos referimos às CPUs ou GPUs dos mineradores. Você precisa de uma capacidade absurda para minerar Bitcoins hoje em dia. Ninguém usa um PC caseiro para isso. É preciso hardware especializado, como os famosos <a href="https://www.cryptocompare.com/mining/bitmain/antminer-s9-miner/"target="_blank" rel="noopener">AntMiners</a>. Um AntMiner S9 de USD 1.500 consegue computar em torno de 14 TH/s.</p>
<p>Cada criptomoeda diferente do Bitcoin calcula hashes de forma distinta, então o hashrate varia de moeda para moeda.</p>
<p>O Hash Power total da rede de consenso do Bitcoin estava chegando próximo a 14 EH (exa-hashes, ou seja, milhões de tera-hashes).</p>
<p>Suponha que eu seja bilionário e queira trollar a comunidade Bitcoin adicionando hash power suficiente para superar o hash power total da rede. Precisaria comprar 1 milhão de AntMiner S9, um investimento de aproximadamente USD 1,5 bilhão! E isso sem contar a energia necessária para ligar e manter todas essas máquinas rodando.</p>
<p>Mesmo assim, sabe o que aconteceria? Lembra da variável de dificuldade que mencionei? Ela se ajusta novamente, garantindo que o próximo bloco continue levando 10 minutos para ser computado!</p>
<p>Então não: mesmo disposto a jogar USD 1,5 bilhão fora, você não vai quebrar. E é assim que o Bitcoin lida com os generais bizantinos nessa rede de consenso.</p>
]]></content:encoded><category>bitcoin-e-criptomoedas</category><category>ciencia-da-computacao</category><category>algoritmos-e-estruturas-de-dados</category></item><item><title>Partidas, Rankings, A Rede Social, League of Legends e Ruby?</title><link>https://akitaonrails.github.io/2016/11/01/partidas-rankings-rede-social-league-of-legends-ruby/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/11/01/partidas-rankings-rede-social-league-of-legends-ruby/</guid><pubDate>Tue, 01 Nov 2016 10:20:00 -0200</pubDate><description>&lt;p&gt;Tem um trecho de uma série de palestras que venho apresentando no Brasil há uns 3 anos sobre o qual nunca escrevi no blog. Ontem acabei de &lt;a href="http://www.akitaonrails.com/2016/10/31/implementacao-ruby-on-rails-sistema-ranking-popularidade-correto"target="_blank" rel="noopener"&gt;postar sobre a forma correta de ranquear conteúdo por &amp;ldquo;popularidade&amp;rdquo;&lt;/a&gt; então achei que valia revisitar o tema.&lt;/p&gt;
&lt;p&gt;Pra começar, acredito que a essa altura quase todo mundo já assistiu ao filme &amp;ldquo;A Rede Social&amp;rdquo;. É curioso que ouvi várias pessoas dizerem como esse filme as influenciou a começar suas próprias startups. Aí eu fiquei pensando, &amp;ldquo;o que esse filme realmente ensina?&amp;rdquo;&lt;/p&gt;</description><content:encoded><![CDATA[<p>Tem um trecho de uma série de palestras que venho apresentando no Brasil há uns 3 anos sobre o qual nunca escrevi no blog. Ontem acabei de <a href="http://www.akitaonrails.com/2016/10/31/implementacao-ruby-on-rails-sistema-ranking-popularidade-correto"target="_blank" rel="noopener">postar sobre a forma correta de ranquear conteúdo por &ldquo;popularidade&rdquo;</a> então achei que valia revisitar o tema.</p>
<p>Pra começar, acredito que a essa altura quase todo mundo já assistiu ao filme &ldquo;A Rede Social&rdquo;. É curioso que ouvi várias pessoas dizerem como esse filme as influenciou a começar suas próprias startups. Aí eu fiquei pensando, &ldquo;o que esse filme realmente ensina?&rdquo;</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/567/pasted-image-765.jpg" alt="The Social Network Casting"  loading="lazy" /></p>
<p>Bom, do filme aprendemos que David Fincher é um diretor fantástico, que Aaron Sorkin escreve diálogos afiadíssimos e envolventes, que Justin Timberlake faz um Sean Parker melhor que o real, que Andrew Garfield faz um Eduardo Saverin razoável, e que Jesse Eisenberg vai ser para sempre o Mark Zuckerberg.</p>
<p>E é só isso, não dá pra aprender mais nada do filme.</p>
<p>Ou dá?</p>
<h3>Facemesh<span class="hx:absolute hx:-mt-20" id="facemesh"></span>
    <a href="#facemesh" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Uma das minhas cenas favoritas do filme é quando Zuckerberg/Eisenberg fica puto com o término com a Erica Albright e começa a raspar fotos de mulheres dos sites de Harvard, organizando elas num site trollesco chamado &ldquo;Facemesh&rdquo;, onde coloca as fotos pra competir entre si. As pessoas votam em qual foto preferem e o site as ordena num ranking de popularidade.</p>
<p>Quando Eduardo Saverin/Andrew Garfield aparece, Zuckerberg pergunta:</p>


<div class="embed-container">
  <iframe
    src="https://www.youtube.com/embed/BzZRr4KV59I"
    title="YouTube video player"
    frameborder="0"
    allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
    referrerpolicy="strict-origin-when-cross-origin"
    allowfullscreen>
  </iframe>
</div>

<blockquote>
  <p>&ldquo;Wardo, preciso de você (&hellip;) preciso do algoritmo usado para ranquear jogadores de xadrez&rdquo;</p>

</blockquote>
<p>E ele vai lá e escreve o seguinte na janela do dormitório:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/569/pasted-image-759.jpg" alt="Through the Looking Glass"  loading="lazy" /></p>
<p>Aqui a maioria das pessoas pensaria:</p>
<blockquote>
  <p>&ldquo;pff, mais uma fórmula sem sentido só pra mostrar que eles são gênios mirins, mas com certeza essa fórmula nem existe&rdquo;</p>

</blockquote>
<p>Só que ela existe. E essa é a única cena do filme que ficou martelando na minha cabeça porque eu já tinha visto isso antes. Pra ajudar, vamos inverter a imagem espelhada:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/568/pasted-image-761.jpg" alt="The Algorithm"  loading="lazy" /></p>
<p>E esse é o &ldquo;algoritmo&rdquo;.</p>
<p>Como falei no artigo anterior, a maioria dos desenvolvedores criaria um site tipo Facemesh adicionando campos inteiros na tabela de competidores com a contagem de upvotes, downvotes e fariam alguma bobagem do tipo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">score</span> <span class="o">=</span> <span class="n">upvotes</span> <span class="o">-</span> <span class="n">downvotes</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ou algo ainda mais bobo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">score</span> <span class="o">=</span> <span class="n">upvotes</span> <span class="o">/</span> <span class="p">(</span><span class="n">upvotes</span> <span class="o">+</span> <span class="n">downvotes</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E não funciona desse jeito, você vai obter rankings completamente errados.</p>
<h3>A Demonstração<span class="hx:absolute hx:-mt-20" id="a-demonstração"></span>
    <a href="#a-demonstra%c3%a7%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pra mostrar o quão errado, criei um projeto simples de demonstração chamado <a href="https://github.com/akitaonrails/elo_demo"target="_blank" rel="noopener">elo_demo</a> que você pode clonar e rodar.</p>
<p>Ele cria 2.000 partidas aleatórias entre 10 jogadores. Esse vai ser o resultado ordenado se usarmos o método errado de subtrair derrotas das vitórias e ordenar por esse resultado:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points (wins - losses)
 1 Kong          217   117    100     17
 2 Samus         211   110    101      9
 3 Wario         197   102     95      7
 4 Luigi         186    95     91      4
 5 Zelda         160    81     79      2
 6 Pikachu       209   105    104      1
 7 Yoshi         223   112    111      1
 8 Mario         203   101    102     -1
 9 Fox           208    95    113    -18
10 Bowser        186    82    104    -22</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora, vamos fazer com que a Samus (2º lugar) vença 10 vezes seguidas o Wario (3º lugar). Esse é o novo ranking:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points
 1 Samus         221   120    101     19
 2 Kong          217   117    100     17
 3 Luigi         186    95     91      4
 4 Zelda         160    81     79      2
 5 Pikachu       209   105    104      1
 6 Yoshi         223   112    111      1
 7 Mario         203   101    102     -1
 8 Wario         207   102    105     -3
 9 Fox           208    95    113    -18
10 Bowser        186    82    104    -22</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Parece justo, Samus pula pro 1º lugar e Wario desce pro 8º.</p>
<p>Agora, e se fizermos o fraquinho Bowser (10º lugar) vencer 10 vezes contra o atual 2º lugar, Kong?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points
 1 Samus         221   120    101     19
 2 Kong          227   117    110      7
 3 Luigi         186    95     91      4
 4 Zelda         160    81     79      2
 5 Pikachu       209   105    104      1
 6 Yoshi         223   112    111      1
 7 Mario         203   101    102     -1
 8 Wario         207   102    105     -3
 9 Bowser        196    92    104    -12
10 Fox           208    95    113    -18</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É aqui que dá pra ver o quão <strong>errado</strong> esse método é. Mesmo perdendo 10 vezes contra o jogador mais fraco, Kong continua reinando absoluto no 2º lugar. E o pobre Bowser, apesar de todo seu esforço e dedicação, sobe apenas 1 mísera posição, do 10º pro 9º.</p>
<p>Isso é muito frustrante e parece injusto porque é injusto mesmo. Esse tipo de cálculo é <strong>errado</strong>.</p>
<h3>Do Xadrez ao League of Legends<span class="hx:absolute hx:-mt-20" id="do-xadrez-ao-league-of-legends"></span>
    <a href="#do-xadrez-ao-league-of-legends" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Se você já jogou League of Legends, provavelmente está familiarizado com algo chamado &ldquo;ELO Boost&rdquo;.</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/570/Screen_Shot_2016-11-01_at_10.19.42.png" alt="Elo Boost"  loading="lazy" /></p>
<p>É uma forma de subir de nível sua conta pagando dinheiro. Recomendo fortemente evitar isso se você pretende competir profissionalmente porque é contra as regras da Riot.</p>
<p>De qualquer forma, você já se perguntou por que se chama &ldquo;ELO&rdquo;?</p>
<p>É uma homenagem ao professor austro-húngaro <strong>Arpad Emmerich Elo</strong>. Ele é mais conhecido pelo seu sistema de rating de jogadores de xadrez. Citando a Wikipedia:</p>
<blockquote>
  <p>O sistema original de rating de xadrez foi desenvolvido em 1950 por Kenneth Harkness (&hellip;). Em 1960, usando os dados desenvolvidos pelo Sistema de Rating de Harkness, Elo desenvolveu sua própria fórmula que tinha uma <strong>base estatística sólida</strong> e constituía uma melhoria sobre o Sistema Harkness. O novo sistema de rating foi aprovado em uma reunião da Federação de Xadrez dos Estados Unidos em St. Louis em 1960.</p>

</blockquote>
<blockquote>
  <p>Em 1970, a FIDE, a Federação Mundial de Xadrez, concordou em adotar o Sistema de Rating Elo. Daquele momento até meados dos anos 80, o próprio Elo fazia os cálculos de rating. Naquela época, a tarefa computacional era relativamente fácil porque menos de 2000 jogadores eram ranqueados pela FIDE.</p>

</blockquote>
<p>O sistema dele foi refinado e evoluiu pra tornar os leaderboards de torneios realmente justos e competitivos. Uma dessas evoluções é o <a href="https://www.microsoft.com/en-us/research/project/trueskill-ranking-system/"target="_blank" rel="noopener">TrueSkill Ranking System</a> da Microsoft, usado em todos os jogos do Xbox Live.</p>
<p>Aquele &ldquo;algoritmo&rdquo; que Eduardo Saverin escreve na janela do dormitório de Harvard? É o <strong>Sistema de Rating ELO</strong>!!</p>
<p>Não sei se Zuckerberg de fato implementou as equações do sistema ELO. Se implementou, foi a escolha <strong>correta</strong>. Mas a história toda do Eduardo escrevendo as equações na janela provavelmente não aconteceu desse jeito porque seria muito mais fácil dar um Google :-)</p>
<h3>Demonstração do Sistema de Rating ELO<span class="hx:absolute hx:-mt-20" id="demonstração-do-sistema-de-rating-elo"></span>
    <a href="#demonstra%c3%a7%c3%a3o-do-sistema-de-rating-elo" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Meu projetinho de demonstração também calcula esse exato score ELO. Os cálculos são feitos pela rubygem <a href="https://github.com/iain/elo"target="_blank" rel="noopener">elo</a>.</p>
<p>A ideia é calcular a probabilidade que um jogador tem de vencer o outro. Então, se um jogador forte joga contra um fraco, espera-se que ele vença, e se esse for o resultado, ele vai pontuar pouco e o perdedor também não vai cair muito. Mas se acontecer o inesperado e o forte perder, espera-se que ele caia bastante e que o jogador &ldquo;mais fraco&rdquo; suba bastante.</p>
<p>Isso torna os torneios mais competitivos, motiva os novos jogadores a enfrentar os mais fortes, e força os mais fortes a jogar pesado pra manter suas posições.</p>
<p>Pela documentação da gem elo, é assim que se usa:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">kong</span>  <span class="o">=</span> <span class="no">Elo</span><span class="o">::</span><span class="no">Player</span><span class="o">.</span><span class="n">new</span>
</span></span><span class="line"><span class="cl"><span class="n">bowser</span> <span class="o">=</span> <span class="no">Elo</span><span class="o">::</span><span class="no">Player</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:rating</span> <span class="o">=&gt;</span> <span class="mi">1500</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game1</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">wins_from</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game2</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">loses_from</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game3</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">plays_draw</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game4</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game4</span><span class="o">.</span><span class="n">winner</span> <span class="o">=</span> <span class="n">bowser</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game5</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game5</span><span class="o">.</span><span class="n">loser</span> <span class="o">=</span> <span class="n">bowser</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game6</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game6</span><span class="o">.</span><span class="n">draw</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game7</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">game7</span><span class="o">.</span><span class="n">result</span> <span class="o">=</span> <span class="mi">1</span> <span class="c1"># resultado é na perspectiva do kong, então kong vence</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">game8</span> <span class="o">=</span> <span class="n">kong</span><span class="o">.</span><span class="n">versus</span><span class="p">(</span><span class="n">bowser</span><span class="p">,</span> <span class="ss">:result</span> <span class="o">=&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="c1"># bowser vence</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E é assim que se acessa os resultados:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">rating</span>       <span class="c1"># =&gt; 1080</span>
</span></span><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">pro?</span>         <span class="c1"># =&gt; false</span>
</span></span><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">starter?</span>     <span class="c1"># =&gt; true</span>
</span></span><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">games_played</span> <span class="c1"># =&gt; 8</span>
</span></span><span class="line"><span class="cl"><span class="n">kong</span><span class="o">.</span><span class="n">games</span>        <span class="c1"># =&gt; [ game1, game2, ... game8 ]</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A gem tem mais ajustes além do algoritmo original, como o K-factor pra recompensar novos jogadores. Esses tipos de ajustes são o que torna as partidas mais competitivas hoje e como você evolui pra níveis de TrueSkill, mas isso foge do escopo deste artigo.</p>
<p>Vamos ver o ranking errado de novo:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points (wins - losses)
 1 Kong          217   117    100     17
 2 Samus         211   110    101      9
 3 Wario         197   102     95      7
 4 Luigi         186    95     91      4
 5 Zelda         160    81     79      2
 6 Pikachu       209   105    104      1
 7 Yoshi         223   112    111      1
 8 Mario         203   101    102     -1
 9 Fox           208    95    113    -18
10 Bowser        186    82    104    -22</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora vamos ver como fica o ranking <strong>correto</strong> calculando o score Elo usando exatamente as mesmas 2.000 partidas:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Losses Points  Elo Rating
 1 Pikachu       209   105    104      1         851
 2 Zelda         160    81     79      2         847
 3 Samus         211   110    101      9         842
 4 Luigi         186    95     91      4         841
 5 Wario         197   102     95      7         824
 6 Mario         203   101    102     -1         820
 7 Yoshi         223   112    111      1         803
 8 Kong          217   117    100     17         802
 9 Bowser        186    82    104    -22         785
10 Fox           208    95    113    -18         754</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Viu como é diferente? No ranking errado, Kong é considerado o mais forte, mas no ranking Elo ele está apenas em 8º lugar. E a razão é que mesmo sendo quem venceu mais partidas (217), também perdeu pra caramba (117). Alguém com menos vitórias, como a Zelda em 2º lugar (160 vitórias), perdeu muito menos (81), e é por isso que ela está mais alta no ranking.</p>
<p>Agora, se fizermos ela vencer 10 partidas seguidas contra a Samus (3º lugar), esse é o novo ranking:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Loses  Points  Elo Rating
 1 Zelda         170    91     79     12         904
 2 Pikachu       209   105    104      1         851
 3 Luigi         186    95     91      4         841
 4 Wario         197   102     95      7         824
 5 Mario         203   101    102     -1         820
 6 Yoshi         223   112    111      1         803
 7 Kong          217   117    100     17         802
 8 Bowser        186    82    104    -22         785
 9 Samus         221   110    111     -1         775
10 Fox           208    95    113    -18         754</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De novo, Zelda pula do 2º pro 1º lugar e Samus cai do 3º pro 9º. Até aqui, tudo certo. Mas e o cenário onde fazemos o forte Pikachu (2º lugar) jogar contra o muito mais fraco Fox McCloud (10º lugar)?</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>   Name        Games  Wins  Loses  Points  Elo Rating
 1 Zelda         170    91     79     12         904
 2 Luigi         186    95     91      4         841
 3 Fox           218   105    113     -8         829
 4 Wario         197   102     95      7         824
 5 Mario         203   101    102     -1         820
 6 Yoshi         223   112    111      1         803
 7 Kong          217   117    100     17         802
 8 Bowser        186    82    104    -22         785
 9 Samus         221   110    111     -1         775
10 Pikachu       219   105    114     -9         766</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Agora sim, isso é justiça: Pikachu deveria ter vencido, mas perder 10 vezes seguidas contra alguém considerado muito mais fraco faz ele cair do 2º lugar lá pro último. E o novato Fox, tendo vencido 10 vezes contra um oponente muito mais forte, merece pular lá pro 3º lugar.</p>
<p>Esse é o tipo de dinâmica que torna partidas e jogos competitivos, e é exatamente por isso que todo leaderboard online e torneio profissional usa esse tipo de algoritmo.</p>
<p>E tudo começou no xadrez, usando matemática conhecida desde o final dos anos 40!!</p>
<blockquote>
  <p>Esse é o ponto deste post e do anterior: <strong>a matemática não é nova</strong>.</p>

</blockquote>
<p>Desenvolvedores perdem uma quantidade enorme de tempo em discussões idiotas sobre qual linguagem ou ferramenta é &ldquo;mais brilhante&rdquo;, mas ignoram a matemática adequada e entregam resultados errados. E essa matemática existe há décadas, em alguns casos há mais de um século já!</p>
<p>Deveríamos lutar pra merecer o título de <em>Cientistas</em> da Computação. Falta muita <strong>ciência</strong> na computação hoje em dia. Discussões inflamadas não tornam ninguém um bom programador.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>games</category></item><item><title>Implementação em Ruby on Rails de um sistema de Ranking/Popularidade (do jeito certo)</title><link>https://akitaonrails.github.io/2016/10/31/implementacao-ruby-on-rails-sistema-ranking-popularidade-correto/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/10/31/implementacao-ruby-on-rails-sistema-ranking-popularidade-correto/</guid><pubDate>Mon, 31 Oct 2016 16:51:00 -0200</pubDate><description>&lt;p&gt;Estava lendo um post publicado recentemente, intitulado &lt;a href="http://naturaily.com/blog/post/ruby-on-rails-implementation-of-a-ranking-system-using-postgresql-window-functions"target="_blank" rel="noopener"&gt;&amp;ldquo;Ruby on Rails implementation of a ranking system using PostgreSQL window functions&amp;rdquo;&lt;/a&gt; e, para ser justo, o objetivo do post era apresentar a &lt;a href="https://www.postgresql.org/docs/8.4/static/functions-window.html"target="_blank" rel="noopener"&gt;função de janela &amp;ldquo;ntile&amp;rdquo; do PostgreSQL&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Acontece que, no caminho, o autor cometeu o mesmo erro que eu vejo se repetir o tempo todo.&lt;/p&gt;
&lt;p&gt;Vamos supor que você tem um projeto com recursos que precisa listar por &amp;ldquo;popularidade&amp;rdquo;. Pode ser um site estilo Reddit, onde as pessoas curtem ou descurtem posts e comentários. Pode ser um e-commerce onde as pessoas curtem ou descurtem produtos.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Estava lendo um post publicado recentemente, intitulado <a href="http://naturaily.com/blog/post/ruby-on-rails-implementation-of-a-ranking-system-using-postgresql-window-functions"target="_blank" rel="noopener">&ldquo;Ruby on Rails implementation of a ranking system using PostgreSQL window functions&rdquo;</a> e, para ser justo, o objetivo do post era apresentar a <a href="https://www.postgresql.org/docs/8.4/static/functions-window.html"target="_blank" rel="noopener">função de janela &ldquo;ntile&rdquo; do PostgreSQL</a>.</p>
<p>Acontece que, no caminho, o autor cometeu o mesmo erro que eu vejo se repetir o tempo todo.</p>
<p>Vamos supor que você tem um projeto com recursos que precisa listar por &ldquo;popularidade&rdquo;. Pode ser um site estilo Reddit, onde as pessoas curtem ou descurtem posts e comentários. Pode ser um e-commerce onde as pessoas curtem ou descurtem produtos.</p>
<p>Pode ser qualquer coisa em que as pessoas curtem ou descurtem algo.</p>
<p>O maior erro que se comete é considerar um score simplório assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">popularity</span> <span class="o">=</span> <span class="n">positive_votes</span> <span class="o">-</span> <span class="n">negative_votes</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Existe um artigo antigo chamado <a href="http://www.evanmiller.org/how-not-to-sort-by-average-rating.html"target="_blank" rel="noopener">&ldquo;How Not To Sort By Average Rating&rdquo;</a> e cito:</p>
<blockquote>
  <p>&ldquo;<em>Por que está errado:</em> Suponha que um item tenha 600 votos positivos e 400 negativos: 60% positivo. Suponha que o item dois tenha 5.500 votos positivos e 4.500 negativos: 55% positivo. Esse algoritmo coloca o item dois (score = 1000, mas só 55% positivo) acima do item um (score = 200, com 60% positivo). ERRADO.&rdquo;</p>

</blockquote>
<p>Aí você pode pensar: já sei como consertar:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Score</span> <span class="o">=</span> <span class="n">average_rating</span> <span class="o">=</span> <span class="n">positive_votes</span> <span class="o">/</span> <span class="n">total_votes</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>De novo, está errado, e cito mais uma vez:</p>
<blockquote>
  <p>&ldquo;<em>Por que está errado:</em> A média funciona bem quando sempre se tem uma tonelada de votos, mas suponha que o item 1 tenha 2 votos positivos e 0 negativos. Suponha que o item 2 tenha 100 votos positivos e 1 negativo. Esse algoritmo coloca o item dois (toneladas de votos positivos) abaixo do item um (pouquíssimos votos positivos). ERRADO.&rdquo;</p>

</blockquote>
<h3>Solução Correta: Limite Inferior do Intervalo de Confiança de Wilson para uma Bernoulli<span class="hx:absolute hx:-mt-20" id="solução-correta-limite-inferior-do-intervalo-de-confiança-de-wilson-para-uma-bernoulli"></span>
    <a href="#solu%c3%a7%c3%a3o-correta-limite-inferior-do-intervalo-de-confian%c3%a7a-de-wilson-para-uma-bernoulli" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>E cito de novo:</p>
<blockquote>
  <p>&ldquo;<em>Como assim:</em> Precisamos balancear a proporção de votos positivos com a incerteza de um número pequeno de observações. Felizmente, a matemática para isso já tinha sido resolvida em 1927 por Edwin B. Wilson. O que queremos perguntar é: dadas as avaliações que tenho, existe 95% de chance de que a fração &lsquo;real&rsquo; de avaliações positivas seja, no mínimo, quanto? Wilson dá a resposta. Considerando apenas votos positivos e negativos (ou seja, não uma escala de 5 estrelas), o limite inferior da proporção de avaliações positivas é dado por:&rdquo;</p>

</blockquote>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/565/rating-equation.png" alt="Equação do Limite Inferior de Bernoulli"  loading="lazy" /></p>
<p>Recomendo que você leia o <a href="http://www.evanmiller.org/how-not-to-sort-by-average-rating.html"target="_blank" rel="noopener">artigo original</a>, mas vamos ao que interessa. Seguindo o post original que linkei no começo, eu tenho uma app Rails simples de blog posts, só que em vez de um campo <code>visits_count</code> preciso adicionar campos <code>positive:integer</code> e <code>negative:integer</code>, além da interface para registrar os votos.</p>
<p>E vou substituir a classe <code>PostWithPopularityQuery</code> pelo seguinte:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">PostWithPopularityQuery</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">call</span>
</span></span><span class="line"><span class="cl">    <span class="no">Post</span><span class="o">.</span><span class="n">find_by_sql</span> <span class="o">[</span><span class="s1">&#39;SELECT id, title, body, positive, negative,
</span></span></span><span class="line"><span class="cl"><span class="s1">        ((positive + 1.9208) / (positive + negative) -
</span></span></span><span class="line"><span class="cl"><span class="s1">        1.96 * SQRT((positive * negative) / (positive + negative) + 0.9604) /
</span></span></span><span class="line"><span class="cl"><span class="s1">        (positive + negative)) / (1 + 3.8416 / (positive + negative))
</span></span></span><span class="line"><span class="cl"><span class="s1">        AS ci_lower_bound
</span></span></span><span class="line"><span class="cl"><span class="s1">      FROM posts 
</span></span></span><span class="line"><span class="cl"><span class="s1">      WHERE positive + negative &gt; 0
</span></span></span><span class="line"><span class="cl"><span class="s1">      ORDER BY ci_lower_bound DESC&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E isto é o que eu espero ver num scaffold simples de <code>index.html.erb</code>:</p>
<p><img src="https://akitaonrails.s3.amazonaws.com/assets/image_asset/image/564/Screen_Shot_2016-10-31_at_16.43.50.png" alt="Página index de Posts"  loading="lazy" /></p>
<p>Eu iria além e recomendaria que <code>ci_lower_bound</code> fosse um campo float na tabela, com um ActiveJob assíncrono atualizando esse valor em intervalos maiores (a cada 5 minutos, por exemplo). Aí a action <code>PostsController#index</code> faria um <code>SELECT</code> direto, ordenando por um campo real e indexado <code>ci_lower_bound DESC</code>, sem refazer as contas a <strong>cada</strong> query.</p>
<p>Agora <strong>ESTE</strong> é o jeito correto de implementar um sistema simples e ingênuo de ranking de popularidade que realmente funciona como deveria.</p>
<p>E não é a única forma. Há dezenas de boas discussões sobre algoritmos online. Todo serviço que depende de popularidade de conteúdo vem refinando algoritmos desse tipo há anos. O Facebook tinha um algoritmo chamado &ldquo;EdgeRank&rdquo;, que dependia de variáveis como Afinidade, Peso, Decaimento Temporal, e parece que evoluiu tanto que hoje calcula popularidade contra mais de <a href="http://marketingland.com/edgerank-is-dead-facebooks-news-feed-algorithm-now-has-close-to-100k-weight-factors-55908"target="_blank" rel="noopener">100 mil variáveis</a>!!</p>
<p>Mas, independente do serviço online, eu garanto que <strong>nenhum</strong> ordena por contagem simples de visitas ou pela média simples dos votos. Isso seria simplesmente errado.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>rails</category><category>ruby</category></item><item><title>Tentando igualar o Fast Blank em C usando Crystal</title><link>https://akitaonrails.github.io/2016/07/06/tentando-igualar-fast-blank-em-c-usando-crystal/</link><guid isPermaLink="true">https://akitaonrails.github.io/2016/07/06/tentando-igualar-fast-blank-em-c-usando-crystal/</guid><pubDate>Wed, 06 Jul 2016 17:10:00 -0300</pubDate><description>&lt;p&gt;Na minha visão, Crystal pode se tornar a solução ideal para deixar nossas queridas gems de Ruby mais rápidas. Até agora, temos usado extensões em C para acelerar código CPU-bound em Ruby. Nokogiri, por exemplo, é um wrapper que oferece uma API agradável em cima da libxml, que é uma biblioteca enorme escrita em C.&lt;/p&gt;
&lt;p&gt;Mas existem várias oportunidades para acelerar aplicações Rails também. Por exemplo, acabamos de ver o lançamento da &lt;a href="https://github.com/danielpclark/faster_path"target="_blank" rel="noopener"&gt;gem &amp;ldquo;faster_path&amp;rdquo;&lt;/a&gt;, dessa vez escrita em Rust e conectada via FFI (Foreign Function Interface). O autor afirma que o Sprockets precisa calcular muitos paths e fazer essa biblioteca, compilada nativamente e otimizada com Rust, trouxe uma melhora enorme na tarefa do asset pipeline.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Na minha visão, Crystal pode se tornar a solução ideal para deixar nossas queridas gems de Ruby mais rápidas. Até agora, temos usado extensões em C para acelerar código CPU-bound em Ruby. Nokogiri, por exemplo, é um wrapper que oferece uma API agradável em cima da libxml, que é uma biblioteca enorme escrita em C.</p>
<p>Mas existem várias oportunidades para acelerar aplicações Rails também. Por exemplo, acabamos de ver o lançamento da <a href="https://github.com/danielpclark/faster_path"target="_blank" rel="noopener">gem &ldquo;faster_path&rdquo;</a>, dessa vez escrita em Rust e conectada via FFI (Foreign Function Interface). O autor afirma que o Sprockets precisa calcular muitos paths e fazer essa biblioteca, compilada nativamente e otimizada com Rust, trouxe uma melhora enorme na tarefa do asset pipeline.</p>
<p>Sam Saffron, do Discourse, também construiu uma gem bem pequena chamada <a href="https://github.com/SamSaffron/fast_blank"target="_blank" rel="noopener">&ldquo;fast_blank&rdquo;</a>, uma minúscula biblioteca em C que reimplementa o método <code>String#blank?</code> do ActiveSupport para ser até 9x mais rápido. Como o Rails digere volumes de strings, checando se elas são &ldquo;blank&rdquo; o tempo todo, isso adiciona alguma performance (depende da sua aplicação, claro).</p>
<p>O Santo Graal da performance em nível nativo é conseguir escrever código próximo de Ruby ao invés de ter que mexer em C de baixo nível ou enfrentar a curva de aprendizado alta de uma linguagem como Rust. Mais que isso, eu gostaria de evitar usar FFI. Eu não sou um expert em FFI, mas lembro de ter entendido que ele adiciona overhead nas bindings.</p>
<p>Aliás, é importante deixar claro logo de cara: eu estou bem longe de ser um expert em C. Tenho pouquíssima experiência lidando com desenvolvimento hardcore em C. O que é justamente o motivo de essa possibilidade de escrever em Crystal ser tão atraente para mim. Então, se você é um expert em C e percebe alguma bobagem que estou falando, por favor me avise nos comentários abaixo.</p>
<p>Meu exercício é reescrever a gem Fast Blank, originalmente em C, em Crystal, adicioná-la na mesma gem para compilar usando Crystal se estiver disponível ou cair no fallback de C, e fazer os specs passarem para que seja uma transição transparente para o usuário.</p>
<p>Para conseguir isso eu tive que:</p>
<ul>
<li>Estender o <code>extconf.rb</code> da gem para gerar Makefiles diferentes (para C e Crystal) capazes de compilar em OS X ou Linux (Ubuntu pelo menos) - OK</li>
<li>Fazer os specs passarem na versão Crystal - Quase (está ok para a maior parte, exceto um edge case)</li>
<li>Fazer a performance ser mais rápida que Ruby e próxima de C - Ainda não tanto (no OS X a performance é bem boa, mas no Ubuntu não escala tão bem para strings grandes)</li>
</ul>
<p>Você pode conferir os resultados até agora no <a href="https://github.com/akitaonrails/fast_blank/tree/crystal_version"target="_blank" rel="noopener">meu fork no Github</a> e acompanhar a <a href="https://github.com/SamSaffron/fast_blank/pull/20"target="_blank" rel="noopener">discussão do Pull Request também</a>.</p>
<h3>Comparando C e Crystal<span class="hx:absolute hx:-mt-20" id="comparando-c-e-crystal"></span>
    <a href="#comparando-c-e-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Só para começar, vamos ver um trecho da versão original em C do Sam:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">VALUE</span>
</span></span><span class="line"><span class="cl"><span class="nf">rb_str_blank</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">rb_encoding</span> <span class="o">*</span><span class="n">enc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="o">*</span><span class="n">s</span><span class="p">,</span> <span class="o">*</span><span class="n">e</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">enc</span> <span class="o">=</span> <span class="nf">STR_ENC_GET</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span> <span class="o">=</span> <span class="nf">RSTRING_PTR</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">s</span> <span class="o">||</span> <span class="nf">RSTRING_LEN</span><span class="p">(</span><span class="n">str</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">Qtrue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">e</span> <span class="o">=</span> <span class="nf">RSTRING_END</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="k">while</span> <span class="p">(</span><span class="n">s</span> <span class="o">&lt;</span> <span class="n">e</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">cc</span> <span class="o">=</span> <span class="nf">rb_enc_codepoint_len</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">n</span><span class="p">,</span> <span class="n">enc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nf">rb_isspace</span><span class="p">(</span><span class="n">cc</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">cc</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">)</span> <span class="k">return</span> <span class="n">Qfalse</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">s</span> <span class="o">+=</span> <span class="n">n</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="n">Qtrue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Sim, bem assustador, eu sei. Agora vamos ver a versão em Crystal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">struct</span> <span class="no">Char</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># mesma forma como o C Ruby implementa</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">is_blank</span>
</span></span><span class="line"><span class="cl">    <span class="nb">self</span> <span class="o">==</span> <span class="s1">&#39; &#39;</span> <span class="o">||</span> <span class="p">(</span><span class="s1">&#39;\t&#39;</span> <span class="o">&lt;=</span> <span class="nb">self</span> <span class="o">&lt;=</span> <span class="s1">&#39;\r&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">String</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">blank?</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span> <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">nil?</span> <span class="o">||</span> <span class="nb">self</span><span class="o">.</span><span class="n">size</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">each_char</span> <span class="p">{</span> <span class="o">|</span><span class="n">char</span><span class="o">|</span> <span class="k">return</span> <span class="kp">false</span> <span class="k">if</span> <span class="o">!</span><span class="n">char</span><span class="o">.</span><span class="n">is_blank</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Caraca! Se você é rubyista, aposto que entende 100% do snippet acima. Não é &ldquo;exatamente&rdquo; a mesma coisa (porque os specs ainda não estão passando completamente), mas chega bem perto.</p>
<h3>A busca por um Makefile para Crystal<span class="hx:absolute hx:-mt-20" id="a-busca-por-um-makefile-para-crystal"></span>
    <a href="#a-busca-por-um-makefile-para-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Pesquisei vários <a href="https://github.com/akitaonrails/ruby_ext_in_crystal_math"target="_blank" rel="noopener">repositórios experimentais no Github</a> e Gists por aí. Mas não achei nenhum que tivesse tudo, então decidi adaptar o que encontrei até chegar a essa versão:</p>
<p>Obs: de novo, eu não sou expert em C. Se você tem experiência com Makefiles, sei que esse aqui dá para refatorar para algo mais bonito. Me avise nos comentários.</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="n">ifeq</span> <span class="s">&#34;$(PLATFORM)&#34;</span> <span class="s">&#34;&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nl">PLATFORM</span> <span class="p">:</span><span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">uname</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">endif</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ifeq</span> <span class="s">&#34;$(PLATFORM)&#34;</span> <span class="s">&#34;Linux&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">UNAME</span> <span class="o">=</span> <span class="s">&#34;$(shell llvm-config --host-target)&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">CRYSTAL_BIN</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">readlink</span> <span class="o">-</span><span class="n">f</span> <span class="err">`</span><span class="n">which</span> <span class="n">crystal</span><span class="err">`</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBRARY_PATH</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">dirname</span> <span class="err">$</span><span class="p">(</span><span class="n">CRYSTAL_BIN</span><span class="p">))</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">embedded</span><span class="o">/</span><span class="n">lib</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBCRYSTAL</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">dirname</span> <span class="err">$</span><span class="p">(</span><span class="n">CRYSTAL_BIN</span><span class="p">)</span> <span class="p">)</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">ext</span><span class="o">/</span><span class="n">libcrystal</span><span class="p">.</span><span class="n">a</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBRUBY</span> <span class="o">=</span> <span class="err">$</span><span class="p">(</span><span class="n">shell</span> <span class="n">ruby</span> <span class="o">-</span><span class="n">e</span> <span class="s">&#34;puts RbConfig::CONFIG[&#39;libdir&#39;]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">LIBS</span> <span class="o">=</span> <span class="o">-</span><span class="n">lpcre</span> <span class="o">-</span><span class="n">lgc</span> <span class="o">-</span><span class="n">lpthread</span> <span class="o">-</span><span class="n">levent</span> <span class="o">-</span><span class="n">lrt</span> <span class="o">-</span><span class="n">ldl</span>
</span></span><span class="line"><span class="cl"><span class="n">LDFLAGS</span> <span class="o">=</span> <span class="o">-</span><span class="n">rdynamic</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">install</span><span class="p">:</span> <span class="n">all</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">all</span><span class="p">:</span> <span class="n">fast_blank</span><span class="p">.</span><span class="n">so</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fast_blank</span><span class="p">.</span><span class="nl">so</span><span class="p">:</span> <span class="n">fast_blank</span><span class="p">.</span><span class="n">o</span>
</span></span><span class="line"><span class="cl">  <span class="err">$</span><span class="p">(</span><span class="n">CC</span><span class="p">)</span> <span class="o">-</span><span class="n">shared</span> <span class="err">$</span><span class="o">^</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span> <span class="err">$</span><span class="p">(</span><span class="n">LIBCRYSTAL</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">LDFLAGS</span><span class="p">)</span> <span class="err">$</span><span class="p">(</span><span class="n">LIBS</span><span class="p">)</span> <span class="o">-</span><span class="n">L</span><span class="err">$</span><span class="p">(</span><span class="n">LIBRARY_PATH</span><span class="p">)</span> <span class="o">-</span><span class="n">L</span><span class="err">$</span><span class="p">(</span><span class="n">LIBRUBY</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fast_blank</span><span class="p">.</span><span class="nl">o</span><span class="p">:</span> <span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">ext</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">fast_blank</span><span class="p">.</span><span class="n">cr</span>
</span></span><span class="line"><span class="cl">  <span class="n">crystal</span> <span class="n">build</span> <span class="o">--</span><span class="n">cross</span><span class="o">-</span><span class="n">compile</span> <span class="o">--</span><span class="n">release</span> <span class="o">--</span><span class="n">target</span> <span class="err">$</span><span class="p">(</span><span class="n">UNAME</span><span class="p">)</span> <span class="err">$</span><span class="o">&lt;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">.</span><span class="nl">PHONY</span><span class="p">:</span> <span class="n">clean</span>
</span></span><span class="line"><span class="cl"><span class="nl">clean</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="n">bc_flags</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">o</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">so</span>
</span></span><span class="line"><span class="cl"><span class="n">endif</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">ifeq</span> <span class="s">&#34;$(PLATFORM)&#34;</span> <span class="s">&#34;Darwin&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">CRYSTAL_FLAGS</span> <span class="o">=</span> <span class="o">-</span><span class="n">dynamic</span> <span class="o">-</span><span class="n">bundle</span> <span class="o">-</span><span class="n">Wl</span><span class="p">,</span><span class="o">-</span><span class="n">undefined</span><span class="p">,</span><span class="n">dynamic_lookup</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">install</span><span class="p">:</span> <span class="n">all</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">all</span><span class="p">:</span> <span class="n">fast_blank</span><span class="p">.</span><span class="n">bundle</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fast_blank</span><span class="p">.</span><span class="nl">bundle</span><span class="p">:</span> <span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="p">..</span><span class="o">/</span><span class="n">ext</span><span class="o">/</span><span class="n">src</span><span class="o">/</span><span class="n">fast_blank</span><span class="p">.</span><span class="n">cr</span>
</span></span><span class="line"><span class="cl">  <span class="n">crystal</span> <span class="err">$</span><span class="o">^</span> <span class="o">--</span><span class="n">release</span> <span class="o">--</span><span class="n">link</span><span class="o">-</span><span class="n">flags</span> <span class="s">&#34;$(CRYSTAL_FLAGS)&#34;</span> <span class="o">-</span><span class="n">o</span> <span class="err">$@</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nl">clean</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">log</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">o</span>
</span></span><span class="line"><span class="cl">  <span class="n">rm</span> <span class="o">-</span><span class="n">f</span> <span class="o">*</span><span class="p">.</span><span class="n">bundle</span>
</span></span><span class="line"><span class="cl"><span class="n">endif</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A maioria das pessoas usando Crystal está em OS X, incluindo os criadores de Crystal. LLVM está sob o guarda-chuva da Apple, e todo o ecossistema deles depende muito de LLVM. Eles passaram muitos anos migrando primeiro o front-end de C, depois o back-end de C, do GCC padrão da GNU para o Clang. E conseguiram fazer tanto Objective-C quanto Swift compilarem para o IR do LLVM, e é por isso que ambos conseguem interagir nativamente.</p>
<p>Depois, eles melhoraram o suporte ao backend ARM, e é assim que eles conseguem ter um &ldquo;Simulador&rdquo; iOS inteiro (não um emulador lento como cachorro tipo o de Android), onde os apps iOS são compilados nativamente para rodar em processador Intel x86_64 durante o desenvolvimento e depois rapidamente recompilam para ARM quando vão empacotar para a App Store.</p>
<p>Dessa forma você consegue rodar nativamente, testar rapidamente, sem a lentidão de um ambiente emulado. A propósito, vou dizer isso uma vez: o maior erro do Google é não dar suporte a LLVM como deveria e ficar reinventando a roda. Se tivessem feito isso, Go já poderia ser usado para implementar para Android e Chromebooks, além de servidores baseados em x86, e poderiam jogar fora todo o imbróglio com Java/Oracle.</p>
<p>Mas estou divagando.</p>
<p>No OS X você pode passar uma link-flag &ldquo;<code>-bundle</code>&rdquo; para o crystal e ele provavelmente vai usar clang por baixo dos panos para gerar o bundle binário.</p>
<p>No Ubuntu, o crystal apenas compila para um arquivo objeto (.o) e você tem que invocar o GCC manualmente com a opção &ldquo;<code>-shared</code>&rdquo; para criar um shared object. Para fazer isso temos que usar o <a href="https://crystal-lang.org/docs/syntax_and_semantics/cross-compilation.html"target="_blank" rel="noopener">&quot;&ndash;cross-compile&quot;</a> e passar um triplet de target do LLVM para gerar o .o (isso requer a ferramenta <code>llvm-config</code>).</p>
<p>Shared Libraries (.so) e Loadable Modules (.bundle) são bichos diferentes, dê uma olhada <a href="http://docstore.mik.ua/orelly/unix3/mac/ch05_03.htm"target="_blank" rel="noopener">nessa documentação</a> para mais detalhes.</p>
<p>Tenha em mente que benchmarkar binários compilados com compiladores diferentes pode fazer diferença. Não sou expert mas, por puro empirismo, acredito que Ruby sob RVM no OS X é compilado usando o Clang padrão do OS X. No Ubuntu é compilado com GCC. Isso parece deixar Ruby no OS X &ldquo;levemente&rdquo; ineficiente em benchmarks sintéticos.</p>
<p>Por outro lado, binários Crystal linkados com GCC parecem &ldquo;levemente&rdquo; ineficientes no Ubuntu, enquanto Ruby no Ubuntu parece um pouco mais rápido, tendo sido compilado e linkado com GCC.</p>
<p>Então, quando comparamos Fast Blank/OS X/um pouco mais rápido com Ruby/OS X/mais lento contra Fast Blank/Ubuntu/um pouco mais lento com Ruby/Ubuntu/um pouco mais rápido, parece dar uma vantagem maior para a comparação no OS X em relação ao benchmark do Ubuntu, mesmo que os tempos individuais de computação não estejam tão longe entre si.</p>
<p>Vou voltar a esse ponto na seção de benchmarks.</p>
<p>Por fim, toda vez que você tem uma rubygem com extensão nativa, vai encontrar este trecho nos arquivos gemspec:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="no">Gem</span><span class="o">::</span><span class="no">Specification</span><span class="o">.</span><span class="n">new</span> <span class="k">do</span> <span class="o">|</span><span class="n">s</span><span class="o">|</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span><span class="o">.</span><span class="n">name</span> <span class="o">=</span> <span class="s1">&#39;fast_blank&#39;</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span><span class="o">.</span><span class="n">extensions</span> <span class="o">=</span> <span class="o">[</span><span class="s1">&#39;ext/fast_blank/extconf.rb&#39;</span><span class="o">]</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando a gem é instalada via <code>gem install</code> ou <code>bundle install</code>, ela vai rodar esse script para gerar um <code>Makefile</code> adequado. Em uma extensão pura em C, ele usa a biblioteca embutida &ldquo;mkmf&rdquo; para gerar.</p>
<p>No nosso caso, se temos Crystal instalado, queremos usar a versão Crystal, então adaptei o <code>extconf.rb</code> para ficar assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s1">&#39;mkmf&#39;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="no">ENV</span><span class="o">[</span><span class="s1">&#39;VERSION&#39;</span><span class="o">]</span> <span class="o">!=</span> <span class="s2">&#34;C&#34;</span> <span class="o">&amp;&amp;</span> <span class="n">find_executable</span><span class="p">(</span><span class="s1">&#39;crystal&#39;</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">find_executable</span><span class="p">(</span><span class="s1">&#39;llvm-config&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># Patch bem porco</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">create_makefile</span><span class="p">(</span><span class="n">target</span><span class="p">,</span> <span class="n">srcprefix</span> <span class="o">=</span> <span class="kp">nil</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mfile</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;Makefile&#34;</span><span class="p">,</span> <span class="s2">&#34;wb&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">cr_makefile</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="no">File</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="bp">__FILE__</span><span class="p">),</span> <span class="s2">&#34;../src/Makefile&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">mfile</span><span class="o">.</span><span class="n">print</span> <span class="no">File</span><span class="o">.</span><span class="n">read</span><span class="p">(</span><span class="n">cr_makefile</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">ensure</span>
</span></span><span class="line"><span class="cl">    <span class="n">mfile</span><span class="o">.</span><span class="n">close</span> <span class="k">if</span> <span class="n">mfile</span>
</span></span><span class="line"><span class="cl">    <span class="nb">puts</span> <span class="s2">&#34;Crystal version of the Makefile copied&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">create_makefile</span> <span class="s1">&#39;fast_blank&#39;</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Então, se ele encontrar <code>crystal</code> e <code>llvm-config</code> (que no OS X você precisa adicionar o path apropriado assim: <code>export PATH=$(brew --prefix llvm)/bin:$PATH</code>).</p>
<p>O <code>Rakefile</code> desse projeto declara a task padrão <code>:compile</code> como a primeira a rodar, e ela vai executar o <code>extconf.rb</code>, que vai gerar o <code>Makefile</code> apropriado e rodar o comando <code>make</code> para compilar e linkar a biblioteca correta no path <code>lib/</code>.</p>
<p>Então acabamos com <code>lib/fast_blank.bundle</code> no OS X e <code>lib/fast_blank.so</code> no Ubuntu. A partir daí podemos simplesmente fazer <code>require &quot;fast_blank&quot;</code> em qualquer arquivo Ruby da gem e ele terá acesso aos mapeamentos das funções C exportadas publicamente da biblioteca Crystal.</p>
<h3>Mapeando C-Ruby para Crystal<span class="hx:absolute hx:-mt-20" id="mapeando-c-ruby-para-crystal"></span>
    <a href="#mapeando-c-ruby-para-crystal" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Bom, qualquer extensão direta em C - sem FFI, fiddle ou outras &ldquo;pontes&rdquo; - vai SEMPRE ter uma vantagem muito maior.</p>
<p>A razão é que você literalmente tem que &ldquo;copiar&rdquo; dados de C-Ruby para Crystal/Rust/Go ou qualquer outra linguagem que você esteja fazendo binding. Já com uma extensão em C você consegue operar diretamente no espaço de memória com os dados, sem ter que mover ou copiar para outro lugar.</p>
<p>Por exemplo. Primeiro, você precisa fazer o binding das funções C de C-Ruby para Crystal. E fazemos isso com os mapeamentos do <a href="https://github.com/phoffer/crystalized_ruby/blob/master/src/lib_ruby.cr"target="_blank" rel="noopener">Crystalized Ruby do Paul Hoffer</a>. É um repositório experimental que ajudei a limpar um pouco para que ele depois extraísse essa biblioteca de mapeamento como uma Shard própria (shards são o mesmo que gems para Crystal). Por enquanto, eu simplesmente copiei o arquivo para o meu Fast Blank.</p>
<p>Algumas das partes relevantes são assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">lib</span> <span class="no">LibRuby</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="no">VALUE</span> <span class="o">=</span> <span class="no">Void</span><span class="o">*</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="no">METHOD_FUNC</span> <span class="o">=</span> <span class="no">VALUE</span> <span class="o">-&gt;</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">type</span> <span class="no">ID</span> <span class="o">=</span> <span class="no">Void</span><span class="o">*</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># strings</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_str_to_str</span><span class="p">(</span><span class="n">value</span> <span class="p">:</span> <span class="no">VALUE</span><span class="p">)</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_string_value_cstr</span><span class="p">(</span><span class="n">value_ptr</span> <span class="p">:</span> <span class="no">VALUE</span><span class="o">*</span><span class="p">)</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_str_new_cstr</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">)</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_utf8_encoding</span><span class="p">()</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_enc_str_new_cstr</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">,</span> <span class="n">enc</span> <span class="p">:</span> <span class="no">VALUE</span><span class="p">)</span> <span class="p">:</span> <span class="no">VALUE</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># tratamento de exceções</span>
</span></span><span class="line"><span class="cl">  <span class="n">fun</span> <span class="n">rb_rescue</span><span class="p">(</span><span class="n">func</span> <span class="p">:</span> <span class="no">VALUE</span> <span class="o">-&gt;</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">,</span> <span class="n">args</span> <span class="p">:</span> <span class="no">VALUE</span><span class="p">,</span> <span class="ss">callback</span><span class="p">:</span> <span class="no">VALUE</span> <span class="o">-&gt;</span> <span class="no">UInt8</span><span class="o">*</span><span class="p">,</span> <span class="ss">value</span><span class="p">:</span> <span class="no">VALUE</span><span class="p">)</span> <span class="p">:</span> <span class="no">UInt8</span><span class="o">*</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">String</span>
</span></span><span class="line"><span class="cl">  <span class="no">RUBY_UTF</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_utf8_encoding</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">to_ruby</span>
</span></span><span class="line"><span class="cl">    <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_enc_str_new_cstr</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="no">RUBY_UTF</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">from_ruby</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">c_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_rescue</span><span class="p">(</span><span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">cr_str_from_rb_cstr</span><span class="p">,</span> <span class="n">str</span><span class="p">,</span> <span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">return_empty_string</span><span class="p">,</span> <span class="mi">0</span><span class="o">.</span><span class="n">to_ruby</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># FIXME ainda existe um problema não tratado: quando recebemos \u0000 do Ruby, ele lança &#34;string contains null bytes&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># então capturamos com rb_rescue, mas aí não conseguimos gerar um Pointer(UInt8) que represente o unicode 0, ao invés disso retornamos uma string em branco</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># mas aí os specs falham</span>
</span></span><span class="line"><span class="cl">    <span class="kp">new</span><span class="p">(</span><span class="n">c_str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">ensure</span>
</span></span><span class="line"><span class="cl">    <span class="s2">&#34;&#34;</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">cr_str_from_rb_cstr</span><span class="p">(</span><span class="n">str</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">rb_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_to_str</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">c_str</span>  <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_string_value_cstr</span><span class="p">(</span><span class="n">pointerof</span><span class="p">(</span><span class="n">rb_str</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">return_empty_string</span><span class="p">(</span><span class="n">arg</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="n">_u8</span>
</span></span><span class="line"><span class="cl">    <span class="n">pointerof</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Aí posso usar esses mapeamentos e helpers para construir uma classe &ldquo;Wrapper&rdquo; em Crystal:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;./lib_ruby&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;./string_extension&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">module</span> <span class="nn">StringExtensionWrapper</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">blank?</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span> <span class="k">if</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_length</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span><span class="o">.</span><span class="n">blank?</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">rescue</span>
</span></span><span class="line"><span class="cl">    <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">blank_as?</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span> <span class="k">if</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_length</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span><span class="o">.</span><span class="n">blank_as?</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">rescue</span>
</span></span><span class="line"><span class="cl">    <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">crystal_value</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">str</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>E esse &ldquo;Wrapper&rdquo; depende da própria biblioteca &ldquo;pura&rdquo; em Crystal, como nos snippets das extensões da struct Char e da classe String que mostrei na primeira seção do artigo.</p>
<p>Por fim, tenho um arquivo principal &ldquo;fast_blank.cr&rdquo; que faz o extern dessas funções do Wrapper para que o C-Ruby possa enxergá-las como métodos comuns de String:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="nb">require</span> <span class="s2">&#34;./string_extension_wrapper.cr&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">fun</span> <span class="n">init</span> <span class="o">=</span> <span class="no">Init_fast_blank</span>
</span></span><span class="line"><span class="cl">  <span class="no">GC</span><span class="o">.</span><span class="n">init</span>
</span></span><span class="line"><span class="cl">  <span class="no">LibCrystalMain</span><span class="o">.</span><span class="n">__crystal_main</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="no">Pointer</span><span class="p">(</span><span class="no">Pointer</span><span class="p">(</span><span class="no">UInt8</span><span class="p">))</span><span class="o">.</span><span class="n">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">string</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_define_class</span><span class="p">(</span><span class="s2">&#34;String&#34;</span><span class="p">,</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_cObject</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_define_method</span><span class="p">(</span><span class="n">string</span><span class="p">,</span> <span class="s2">&#34;blank?&#34;</span><span class="p">,</span> <span class="o">-&gt;</span><span class="no">StringExtensionWrapper</span><span class="o">.</span><span class="n">blank?</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_define_method</span><span class="p">(</span><span class="n">string</span><span class="p">,</span> <span class="s2">&#34;blank_as?&#34;</span><span class="p">,</span> <span class="o">-&gt;</span><span class="no">StringExtensionWrapper</span><span class="o">.</span><span class="n">blank_as?</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="o">...</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Isso é basicamente boilerplate. Mas agora veja o que estou tendo que fazer no wrapper, neste snippet específico:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">blank?</span><span class="p">(</span><span class="nb">self</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="k">return</span> <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span> <span class="k">if</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_length</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">  <span class="n">str</span> <span class="o">=</span> <span class="nb">String</span><span class="o">.</span><span class="n">from_ruby</span><span class="p">(</span><span class="nb">self</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">str</span><span class="o">.</span><span class="n">blank?</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl"><span class="k">rescue</span>
</span></span><span class="line"><span class="cl">  <span class="kp">true</span><span class="o">.</span><span class="n">to_ruby</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Estou recebendo uma String do C-Ruby tipada como ponteiro (VALUE), depois passo pelos mapeamentos do lib_ruby.cr para pegar os dados da string do C-Ruby e copiar para uma nova instância da representação interna de String do Crystal. Então, em qualquer momento eu tenho 2 cópias da mesma string, uma no espaço de memória do C-Ruby e outra no espaço de memória do Crystal.</p>
<p>Isso acontece com todas as extensões tipo FFI, mas não acontece com a implementação pura em C. Na implementação em C do Sam Saffron, ela trabalha diretamente com o mesmo endereço no espaço de memória do C-Ruby:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-C" data-lang="C"><span class="line"><span class="cl"><span class="k">static</span> <span class="n">VALUE</span>
</span></span><span class="line"><span class="cl"><span class="nf">rb_str_blank</span><span class="p">(</span><span class="n">VALUE</span> <span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">rb_encoding</span> <span class="o">*</span><span class="n">enc</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">  <span class="kt">char</span> <span class="o">*</span><span class="n">s</span><span class="p">,</span> <span class="o">*</span><span class="n">e</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="n">enc</span> <span class="o">=</span> <span class="nf">STR_ENC_GET</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="n">s</span> <span class="o">=</span> <span class="nf">RSTRING_PTR</span><span class="p">(</span><span class="n">str</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">  <span class="p">...</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Ela recebe um ponteiro (endereço de memória direto) e segue. E essa é uma vantagem enorme para a versão em C. Quando você tem um grande volume de strings de tamanho médio a grande sendo copiadas de C-Ruby para Crystal, isso adiciona um overhead perceptível que não dá para remover.</p>
<h3>Caveat do mapeamento de String<span class="hx:absolute hx:-mt-20" id="caveat-do-mapeamento-de-string"></span>
    <a href="#caveat-do-mapeamento-de-string" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>Ainda tenho um problema. Existe um edge case que não consegui superar ainda (ajuda é mais que bem-vinda). Quando o C-Ruby passa um unicode <code>&quot;\u0000&quot;</code>, eu não consigo criar o mesmo caractere em Crystal e acabo passando apenas uma string vazia (&quot;&quot;), o que não é a mesma coisa.</p>
<p>A maneira de lidar com isso é receber uma String Ruby (VALUE) e pegar a C-String dela assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">rb_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_str_to_str</span><span class="p">(</span><span class="n">str</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">c_str</span>  <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_string_value_cstr</span><span class="p">(</span><span class="n">pointerof</span><span class="p">(</span><span class="n">rb_str</span><span class="p">))</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Se a &ldquo;str&rdquo; for o &ldquo;\u0000&rdquo; (no Ruby 2.2.5 pelo menos), o C-Ruby lança uma exceção &ldquo;string contains null bytes&rdquo;. É por isso que eu faço rescue dessa exceção assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="n">c_str</span> <span class="o">=</span> <span class="no">LibRuby</span><span class="o">.</span><span class="n">rb_rescue</span><span class="p">(</span><span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">cr_str_from_rb_cstr</span><span class="p">,</span> <span class="n">str</span><span class="p">,</span> <span class="o">-&gt;</span><span class="nb">String</span><span class="o">.</span><span class="n">return_empty_string</span><span class="p">,</span> <span class="mi">0</span><span class="o">.</span><span class="n">to_ruby</span><span class="p">)</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Quando uma exceção é disparada, tenho que passar o ponteiro para outra função para fazer o rescue:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">return_empty_string</span><span class="p">(</span><span class="n">arg</span> <span class="p">:</span> <span class="no">LibRuby</span><span class="o">::</span><span class="no">VALUE</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">  <span class="n">a</span> <span class="o">=</span> <span class="mi">0</span><span class="n">_u8</span>
</span></span><span class="line"><span class="cl">  <span class="n">pointerof</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Mas isso não está correto, estou apenas passando o ponteiro para um caractere &ldquo;0&rdquo;, que é &ldquo;vazio&rdquo;. Por isso, os specs não estão passando corretamente:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>Failures:

  1) String provides a parity with active support function
     Failure/Error: expect(&#34;#{i.to_s(16)} #{c.blank_as?}&#34;).to eq(&#34;#{i.to_s(16)} #{c.blank2?}&#34;)

       expected: &#34;0 false&#34;
            got: &#34;0 true&#34;

       (compared using ==)
     # ./spec/fast_blank_spec.rb:22:in `block (3 levels) in &lt;top (required)&gt;&#39;
     # ./spec/fast_blank_spec.rb:19:in `times&#39;
     # ./spec/fast_blank_spec.rb:19:in `block (2 levels) in &lt;top (required)&gt;&#39;

  2) String treats  correctly
     Failure/Error: expect(&#34;\u0000&#34;.blank_as?).to be_falsey
       expected: falsey value
            got: true
     # ./spec/fast_blank_spec.rb:47:in `block (2 levels) in &lt;top (required)&gt;&#39;</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>O Ary deu uma dica simples depois, vou adicionar na conclusão abaixo.</p>
<h3>Os benchmarks sintéticos (cuidado em como interpretá-los!)<span class="hx:absolute hx:-mt-20" id="os-benchmarks-sintéticos-cuidado-em-como-interpretá-los"></span>
    <a href="#os-benchmarks-sint%c3%a9ticos-cuidado-em-como-interpret%c3%a1-los" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A <a href="https://github.com/rails/rails/blob/2a371368c91789a4d689d6a84eb20b238c37678a/activesupport/lib/active_support/core_ext/object/blank.rb#L101"target="_blank" rel="noopener">implementação original de String#blank? do ActiveSupport do Rails</a> é assim:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ruby" data-lang="ruby"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">String</span>
</span></span><span class="line"><span class="cl">  <span class="c1"># 0x3000: fullwidth whitespace</span>
</span></span><span class="line"><span class="cl">  <span class="no">NON_WHITESPACE_REGEXP</span> <span class="o">=</span> <span class="s">%r![^\s#{[0x3000].pack(&#34;U&#34;)}]!</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">  <span class="c1"># Uma string é blank se for vazia ou contiver só whitespace:</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34;&#34;.blank?                 # =&gt; true</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34;   &#34;.blank?              # =&gt; true</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34;　&#34;.blank?               # =&gt; true</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#   &#34; something here &#34;.blank? # =&gt; false</span>
</span></span><span class="line"><span class="cl">  <span class="c1">#</span>
</span></span><span class="line"><span class="cl">  <span class="k">def</span> <span class="nf">blank?</span>
</span></span><span class="line"><span class="cl">    <span class="c1"># 1.8 não trata [:space:] direito</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="n">encoding_aware?</span>
</span></span><span class="line"><span class="cl">      <span class="nb">self</span> <span class="o">!~</span> <span class="sr">/[^[:space:]]/</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">      <span class="nb">self</span> <span class="o">!~</span> <span class="no">NON_WHITESPACE_REGEXP</span>
</span></span><span class="line"><span class="cl">    <span class="k">end</span>
</span></span><span class="line"><span class="cl">  <span class="k">end</span>
</span></span><span class="line"><span class="cl"><span class="k">end</span></span></span></code></pre></div></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>É basicamente uma comparação por expressão regular, o que pode ser meio lento. A versão do Sam é um loop mais direto pela string para comparar cada caractere com o que é considerado &ldquo;blank&rdquo;. Existem vários codepoints unicode considerados blank, alguns não, e por isso as versões em C e Crystal são parecidas, mas diferentes da versão do Rails.</p>
<p>Na gem Fast Blank existe um script Ruby <code>benchmark</code> para comparar a extensão em C contra a implementação baseada em Regex do Rails.</p>
<p>A implementação Regex é chamada de <strong>&ldquo;Slow Blank&rdquo;</strong>. Ela é particularmente lenta se você passa uma String realmente vazia, então no benchmark o Sam adicionou uma <strong>&ldquo;New Slow Blank&rdquo;</strong> que checa primeiro com <code>String#empty?</code>, e essa versão é mais rápida nesse edge case.</p>
<p>A versão rápida em C se chama <strong>&ldquo;Fast Blank&rdquo;</strong>, mas mesmo que você possa considerá-la &ldquo;correta&rdquo;, ela não é compatível com todos os edge cases do Rails. Então ele implementou um <code>String#blank_as?</code> que é compatível com Rails. O Sam chama de <strong>&ldquo;Fast Activesupport&rdquo;</strong>.</p>
<p>Na minha versão em Crystal eu fiz a mesma coisa, tendo tanto <code>String#blank?</code> quanto <code>String#blank_as?</code>.</p>
<p>Então, sem mais delongas, aqui está o benchmark da <strong>versão em C no OS X</strong> para strings vazias, e exercitamos cada função muitas vezes em poucos segundos para ter resultados mais precisos (confira o <a href="https://github.com/evanphx/benchmark-ips"target="_blank" rel="noopener">&ldquo;benchmark/ips&rdquo;</a> do Evan Phoenix para entender a metodologia de &ldquo;iteration per second&rdquo;).</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 0 ==================
Warming up _______________________________________
          Fast Blank   191.708k i/100ms
  Fast ActiveSupport   209.628k i/100ms
          Slow Blank    61.487k i/100ms
      New Slow Blank   203.165k i/100ms
Calculating _______________________________________
          Fast Blank     20.479M (± 9.3%) i/s -    101.414M in   5.001177s
  Fast ActiveSupport     21.883M (± 9.4%) i/s -    108.378M in   5.004350s
          Slow Blank      1.060M (± 4.7%) i/s -      5.288M in   5.001365s
      New Slow Blank     18.883M (± 6.9%) i/s -     94.065M in   5.008899s

Comparison:
  Fast ActiveSupport: 21882711.5 i/s
          Fast Blank: 20478961.5 i/s - same-ish: difference falls within error
      New Slow Blank: 18883442.2 i/s - same-ish: difference falls within error
          Slow Blank:  1059692.6 i/s - 20.65x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Está super rápido. A versão do Rails é 20x mais lenta na minha máquina.</p>
<p>Agora, <strong>versão Crystal no OS X</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 0 ==================
Warming up _______________________________________
          Fast Blank   174.349k i/100ms
  Fast ActiveSupport   174.035k i/100ms
          Slow Blank    64.684k i/100ms
      New Slow Blank   215.164k i/100ms
Calculating _______________________________________
          Fast Blank      8.647M (± 1.6%) i/s -     43.239M in   5.001530s
  Fast ActiveSupport      8.580M (± 1.3%) i/s -     42.987M in   5.010759s
          Slow Blank      1.047M (± 3.7%) i/s -      5.239M in   5.008907s
      New Slow Blank     19.090M (± 9.3%) i/s -     94.672M in   5.009057s

Comparison:
      New Slow Blank: 19090034.8 i/s
          Fast Blank:  8647459.7 i/s - 2.21x slower
  Fast ActiveSupport:  8580487.9 i/s - 2.22x slower
          Slow Blank:  1047465.3 i/s - 18.22x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Como expliquei antes, mesmo checando strings vazias, a versão Crystal é mais lenta que o check de Ruby por <code>String#empty?</code> (New Slow Blank), porque tenho a rotina de cópia de string nos mapeamentos do Wrapper. Isso adiciona overhead perceptível ao longo de muitas iterações. Continua sendo 18x mais rápido que Rails, mas perde para C-Ruby.</p>
<p>Por fim, <strong>versão Crystal no Ubuntu</strong></p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 0 ==================
Warming up _______________________________________
          Fast Blank   255.883k i/100ms
  Fast ActiveSupport   260.915k i/100ms
          Slow Blank   105.424k i/100ms
      New Slow Blank   284.670k i/100ms
Calculating _______________________________________
          Fast Blank      8.895M (± 9.8%) i/s -     44.268M in   5.037761s
  Fast ActiveSupport      8.647M (± 8.2%) i/s -     43.051M in   5.020125s
          Slow Blank      1.736M (± 3.9%) i/s -      8.750M in   5.048253s
      New Slow Blank     22.170M (± 6.2%) i/s -    110.452M in   5.004909s

Comparison:
      New Slow Blank: 22170031.0 i/s
          Fast Blank:  8895113.3 i/s - 2.49x slower
  Fast ActiveSupport:  8646940.8 i/s - 2.56x slower
          Slow Blank:  1736071.0 i/s - 12.77x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Note que está na mesma faixa, mas a versão Rails no Ubuntu roda quase duas vezes mais rápido comparada à equivalente no OS X, o que faz a comparação contra a biblioteca Crystal cair de 18x para 12x.</p>
<p>O benchmark continua comparando contra strings cada vez maiores, de 6, para 14, para 24, até 136 caracteres.</p>
<p>Vamos pegar só o último teste, com 136 caracteres. Primeiro com a <strong>versão em C no OS X</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 136 ==================
Warming up _______________________________________
          Fast Blank   177.521k i/100ms
  Fast ActiveSupport   193.559k i/100ms
          Slow Blank    89.378k i/100ms
      New Slow Blank    60.639k i/100ms
Calculating _______________________________________
          Fast Blank     10.727M (± 8.7%) i/s -     53.256M in   5.006538s
  Fast ActiveSupport     11.600M (± 8.3%) i/s -     57.681M in   5.009692s
          Slow Blank      1.872M (± 5.7%) i/s -      9.385M in   5.029243s
      New Slow Blank      1.017M (± 5.3%) i/s -      5.094M in   5.022994s

Comparison:
  Fast ActiveSupport: 11600112.2 i/s
          Fast Blank: 10726792.8 i/s - same-ish: difference falls within error
          Slow Blank:  1872262.5 i/s - 6.20x slower
      New Slow Blank:  1016926.7 i/s - 11.41x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>A versão em C é consistentemente muito mais rápida em todos os casos de teste e nos 136 caracteres ainda está 11x mais rápida que o Rails em Ruby puro.</p>
<p>Agora a <strong>versão Crystal no OS X</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 136 ==================
Warming up _______________________________________
          Fast Blank   127.749k i/100ms
  Fast ActiveSupport   126.538k i/100ms
          Slow Blank    94.390k i/100ms
      New Slow Blank    60.594k i/100ms
Calculating _______________________________________
          Fast Blank      3.283M (± 1.8%) i/s -     16.480M in   5.021364s
  Fast ActiveSupport      3.235M (± 1.3%) i/s -     16.197M in   5.008315s
          Slow Blank      1.888M (± 4.4%) i/s -      9.439M in   5.009458s
      New Slow Blank    967.950k (± 4.7%) i/s -      4.848M in   5.018946s

Comparison:
          Fast Blank:  3283025.1 i/s
  Fast ActiveSupport:  3234586.5 i/s - same-ish: difference falls within error
          Slow Blank:  1887800.5 i/s - 1.74x slower
      New Slow Blank:   967950.2 i/s - 3.39x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Também é mais rápida, mas só por 2 a 3 vezes comparada ao Ruby puro, bem longe do 11x. Mas minha hipótese é que o mapeamento e cópia de tantas strings adiciona um overhead grande que a versão em C não tem.</p>
<p>E a <strong>versão Crystal no Ubuntu</strong>:</p>
<div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">

<div><pre><code>================== Test String Length: 136 ==================
Warming up _______________________________________
          Fast Blank   186.810k i/100ms
  Fast ActiveSupport   187.306k i/100ms
          Slow Blank   143.439k i/100ms
      New Slow Blank    98.308k i/100ms
Calculating _______________________________________
          Fast Blank      3.517M (± 3.9%) i/s -     17.560M in   5.000791s
  Fast ActiveSupport      3.485M (± 3.8%) i/s -     17.419M in   5.006427s
          Slow Blank      2.755M (± 4.2%) i/s -     13.770M in   5.008490s
      New Slow Blank      1.551M (± 4.3%) i/s -      7.766M in   5.017853s

Comparison:
          Fast Blank:  3516960.7 i/s
  Fast ActiveSupport:  3484575.5 i/s - same-ish: difference falls within error
          Slow Blank:  2754669.0 i/s - 1.28x slower
      New Slow Blank:  1550815.2 i/s - 2.27x slower</code></pre></div><div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0">
  <button
    class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
    title="Copy code"
  >
    <div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
  </button>
</div>
</div>
<p>Novamente, as versões Ubuntu tanto da biblioteca Crystal quanto do binário Ruby rodam mais rápido, e a comparação mostra no máximo o dobro de velocidade. E o <code>String#empty?</code> do Ruby puro está na mesma faixa que a versão Crystal.</p>
<h3>Conclusão<span class="hx:absolute hx:-mt-20" id="conclusão"></span>
    <a href="#conclus%c3%a3o" class="subheading-anchor" aria-label="Permalink for this section"></a></h3><p>A conclusão mais óbvia é que provavelmente errei ao escolher Fast Blank como minha primeira prova de conceito. O algoritmo é trivial demais e um simples check de <code>String#empty?</code> em Ruby puro é ordens de magnitude mais rápido que o overhead adicionado de mapear e copiar strings para Crystal.</p>
<p>Além disso, qualquer caso de uso onde você tem uma quantidade enorme de pequenos pedaços de dados sendo transferidos de C-Ruby para Crystal, ou qualquer extensão baseada em FFI, vai ter o overhead da cópia de dados, que uma versão pura em C não tem. É por isso que Fast Blank é melhor feito em C.</p>
<p>Outros casos de uso, onde você tem menos dados, ou dados que podem ser transferidos em lote (menos chamadas do C-Ruby para a extensão, com argumentos de tamanho maior, e com processamento mais custoso), são candidatos melhores para se beneficiar de extensões.</p>
<p>Mais uma vez, nem tudo fica automaticamente mais rápido, sempre temos que entender os cenários de uso primeiro. Mas como é muito mais fácil escrever em Crystal e fazer benchmark, podemos fazer provas de conceito mais rapidamente e descartar a ideia se as medições provarem que não vamos nos beneficiar tanto.</p>
<p>A documentação do Crystal recebeu recentemente um <a href="https://crystal-lang.org/docs/guides/performance.html"target="_blank" rel="noopener">&ldquo;Performance Guide&rdquo;</a>. É bem útil para você evitar armadilhas comuns que prejudicam a performance geral. Mesmo que LLVM seja bem competente em otimização pesada, ele não pode fazer tudo. Então leia tudo para melhorar suas habilidades gerais em Crystal.</p>
<p>Dito isso, ainda acredito que esse exercício valeu muito a pena. Provavelmente vou fazer mais alguns. Eu gostaria muito de agradecer ao Ary (criador do Crystal) e ao Paul Hoffer pela paciência em me ajudar com muitas das peculiaridades que encontrei pelo caminho.</p>
<p>Enquanto eu finalizava esse post, <a href="https://github.com/SamSaffron/fast_blank/pull/20#issuecomment-230875300"target="_blank" rel="noopener">o Ary apontou</a> que eu poderia provavelmente abandonar Strings completamente e trabalhar diretamente com um array de bytes, o que é uma boa ideia e provavelmente vou tentar. Acho que já deixei claro que toda a cópia de String adiciona um overhead bem perceptível, como vimos nos benchmarks acima. Me avise se alguém quiser contribuir também. Com mais alguns ajustes, acredito que dá para ter uma versão Crystal que pelo menos compita com a versão em C, sendo também mais legível e fácil de manter para a maioria dos rubyistas, que é o meu objetivo.</p>
<p>Espero que os códigos que publiquei aqui sirvam como exemplos de boilerplate para mais extensões Ruby baseadas em Crystal no futuro!</p>
]]></content:encoded><category>crystal</category><category>performance</category><category>algoritmos-e-estruturas-de-dados</category></item><item><title>[Casa do Código] Livro: Introdução à Computação: Da Lógica aos jogos com Ruby</title><link>https://akitaonrails.github.io/2015/06/27/casa-do-codigo-livro-introducao-a-computacao-da-logica-aos-jogos-com-ruby/</link><guid isPermaLink="true">https://akitaonrails.github.io/2015/06/27/casa-do-codigo-livro-introducao-a-computacao-da-logica-aos-jogos-com-ruby/</guid><pubDate>Sat, 27 Jun 2015 11:23:00 -0300</pubDate><description>&lt;p&gt;Estou um pouco atrasado com meu review mas antes tarde do que nunca. Alguns dias atrás a &lt;a href="http://www.casadocodigo.com.br/products/livro-aprendendo-computacao-ruby"target="_blank" rel="noopener"&gt;Casa do Código lançou um novo livro&lt;/a&gt; para iniciantes que querem começar no mundo da programação.&lt;/p&gt;
&lt;p&gt;Hoje existem muitos tutoriais na Web para aprender todo tipo de truque de programação. Para muitos, programar se tornou um processo de:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;pesquisar no Google, Stackoverflow ou outros fórums&lt;/li&gt;
&lt;li&gt;copiar o trecho de código e colar no seu próprio código&lt;/li&gt;
&lt;li&gt;lucrar&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Isso é pouco, é muito pouco, é um enorme desserviço ao mercado de programação. Não há nada errado em copiar e colar trechos de código, até mesmo programadores realmente experientes fazem isso por conveniência muitas vezes. Mas é muito errado quando o programador faz isso sem ter consciência do que está realmente fazendo. E faça isso muitos anos, isso não o torna &amp;ldquo;experiente&amp;rdquo; em programação, meramente o torna uma máquina velha de copiar e colar.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Estou um pouco atrasado com meu review mas antes tarde do que nunca. Alguns dias atrás a <a href="http://www.casadocodigo.com.br/products/livro-aprendendo-computacao-ruby"target="_blank" rel="noopener">Casa do Código lançou um novo livro</a> para iniciantes que querem começar no mundo da programação.</p>
<p>Hoje existem muitos tutoriais na Web para aprender todo tipo de truque de programação. Para muitos, programar se tornou um processo de:</p>
<ol>
<li>pesquisar no Google, Stackoverflow ou outros fórums</li>
<li>copiar o trecho de código e colar no seu próprio código</li>
<li>lucrar</li>
</ol>
<p>Isso é pouco, é muito pouco, é um enorme desserviço ao mercado de programação. Não há nada errado em copiar e colar trechos de código, até mesmo programadores realmente experientes fazem isso por conveniência muitas vezes. Mas é muito errado quando o programador faz isso sem ter consciência do que está realmente fazendo. E faça isso muitos anos, isso não o torna &ldquo;experiente&rdquo; em programação, meramente o torna uma máquina velha de copiar e colar.</p>
<p>Programação tem fundamentos, como qualquer profissão de prática. Derramar tinta na parede não o torna um pintor impressionista, apenas um péssimo pintor de paredes. Meramente copiar e colar código não o tornar um programador, apenas um digitador. Não há valor num profissional desse tipo, pois ele é imediatamente substituível.</p>
<p>Livros e tutoriais que ensinam os famosos &ldquo;procedimentos&rdquo; de baixar, instalar, digitar hello world e dizer que isso é o fundamental servem para quem já tem base de programação, para todos os outros não agrega em nada.</p>
<p>Um livro como o que o <a href="https://twitter.com/guilhermecaelum/status/605847398997311488"target="_blank" rel="noopener">Guilherme Silveira</a> escreveu tem a audácia de fazer o que a grande maioria dos autores não tem coragem: dar um passo pra trás. Ensinar fundamentos não é nem de perto tão &ldquo;glamuroso&rdquo; como escrever um tutorial de uma nova biblioteca ou nova linguagem. Porém é o que a maioria esmagadora das pessoas que se auto-intitula &ldquo;programadores&rdquo; realmente precisa: conhecimento fundamental.</p>
<p>Esse tipo de conhecimento indepente da linguagem que está usando. Ela é universal. Mais do que isso, ela forma a linha de raciocínio sobre o qual você vai conseguir assimilar novos conhecimentos mais rápido e com mais eficácia, por isso é tão importante.</p>
<p>Livros desse tipo tendem a ser extremamente chatos por ser um conteúdo bastante teórico. A idéia do Guilherme para atingir esse objetivo sem ser chato foi escolher uma parte desse fundamento que é o mais essencial, criar exercícios que levam a pequenos jogos e com uma narrativa mais próxima aos tais tutoriais de Web que mencionei, conseguir explicar um conhecimento fundamental.</p>
<p>E como conhecimento fundamental, o que o livro do Guilherme oferece pode ser claramente entendido já pelo índice:</p>
<ul>
<li>
<p>Jogo da adivinhação, onde ele explica conceitos fundamentais como entrada e saída, funções, variáveis e atribuições. Os capitulos seguintes vão incrementar este jogo.</p>
</li>
<li>
<p>Controle de Fluxo, onde entendemos como o computador toma decisões baseado no que ele sabe, o simples &ldquo;if&rdquo; e loops.</p>
</li>
<li>
<p>Funções, algo que toda linguagem tem, uma forma de reusar pedaços de código em vez de copiar e colar sem consciência.</p>
</li>
<li>
<p>Arrays, uma das estrutura de dados mais fundamentais da computação, saber o que é uma lista, ou o caso particular de Arrays, que são Strings. Poucos programadores amadores entendem porque eles são relacionado.</p>
</li>
<li>
<p>Pontos e matemática, e novamente um dos pontos que a maioria aceita sem entender. Computadores foram feitos para lidar com números inteiros. Como ele realmente lida com números quebrados, ou com ponto flutuante?</p>
</li>
<li>
<p>Binário. Hoje em dia a programação tem tantas abstrações que esquecemos que internamente o computador consegue entender apenas 0s e 1s. Todos falam em 32bits, 64bits, mas quase ninguém sabe o que isso significa. E isso leva a diversos programas ineficientes e abstrações mal feitas.</p>
</li>
<li>
<p>Nível de dificuldade e o case. Este capítulo extende o anterior de &ldquo;Controle de Fluxo&rdquo; introduzindo mais algumas facilidades como &ldquo;case&rdquo; e &ldquo;while&rdquo;.</p>
</li>
<li>
<p>Arte Ascii, aqui o Guilherme resolve dar uma volta ao passado mostrando um pouco de como fazíamos &ldquo;interfaces&rdquo; para o usuário sem interfaces gráficas.</p>
</li>
<li>
<p>Jogo da forca. Depois do Jogo da adivinhação agora começa o Jogo da Forca onde os próximos capítulos também vão incrementar este novo jogo. Aqui você vai começar a exercitar o que realmente significa &ldquo;resolver um problema&rdquo;, como é pensar em &ldquo;algoritmos&rdquo; e não ser somente um &ldquo;copy e paster&rdquo;.</p>
</li>
<li>
<p>Responsabilidades. Ao falar de copy e pasters, aqui vamos começar a pensar em algo diferente que um programador amador raramente pensa: separação de responsabilidades, principalmente entre &ldquo;interface do usuário&rdquo; e &ldquo;lógica de negócios&rdquo;. Um amador tende a misturar as duas coisas, um profissional deve aprender a separá-las.</p>
</li>
<li>
<p>Entrada e saída de arquivo: palavras aleatórias e o top player. Neste capítulo vamos a outras maneiras de carregador dados que não seja apenas o que o usuário digitar, mas o que está em arquivos.</p>
</li>
<li>
<p>Foge Foge, um jogo baseado no Pacman. Finalmente, chegamos ao terceiro jogo. Agora o Guilherme vai subir um pouco mais a dificuldade, usar tudo que foi ensinado até agora e fazer um jogo, que é bem simples na verdade, mas é um bom desafio para quem está começando a aprender. Aqui vamos ter que cuidar do estado dos personagens do jogo dentro de um mapa. Neste capítulo ele também vai introduzir conceitos importantes como &ldquo;Refatoração&rdquo; e outros específicos de linguagens dinâmicas como Ruby, como &ldquo;duck typing&rdquo;.</p>
</li>
<li>
<p>Botando os fantasmas para correr: arrays associativos, duck typing e outros. O capítulo anterior é bem extenso pois teve que lidar com o mapa, movimentos, colisão e este capítulo é uma extensão para adicionar os fantasmas e seu comportamento dentro do mapa. Muita atenção para não se perder até aqui.</p>
</li>
<li>
<p>Matrizes e memória. Aqui temos mais um exercício avançado de pensamento em estruturas de dados, ao mapear o que seria o mapa do Foge Foge num &ldquo;array de arrays&rdquo; (ou matriz) e agora controlar o que existe em cada posição dessa matriz para guardar o estado do jogo a cada movimento. Um exercício importante que continua sendo um desafio até mesmo para alguns programadores considerados &ldquo;experientes&rdquo;.</p>
</li>
<li>
<p>Estruturas e classes: uma introdução a Orientação a Objetos. Para quem já teve aula na faculdade ou algo assim, este capítulo não vai voltar a velha teoria de explicar &ldquo;encapsulamento, herança e polimorfismo&rdquo;, coisa que é bem antiquada já. O Guilherme vai direto ao ponto, pegar o jogo do Foge Foge, que até aqui foi feito de forma procedural e imperativa e criar abstrações sobre a mesma estrutura e lógica para demonstrar algumas maneiras de se fazer essa modelagem em objetos.</p>
</li>
<li>
<p>Destruindo os fantasmas: o mundo da recursão. Outra técnica importante é recursão, uma forma de transitar por uma estrutura de dados como listas ou matrizes e processar cada elemento onde o processamento seguinte depende do anterior. É uma técnica muito poderosa e ao mesmo tempo uma das maneiras mais fáceis de atirar no próprio pé. Neste capítulo o Guilherme vai explicar como usar essa técnica de maneira segura.</p>
</li>
</ul>
<p>No fim o Guilherme adiciona instruções para instalar o Ruby e algumas dicas de como continuar estudando para evoluir além do conteúdo do livro.</p>
<p>O livro é bem simples de seguir, a idéia de usar pequenos jogos que vão aumentando de dificuldade para explicar conceitos fundamentais torna o estudo muito mais interessante e se você estudar e seguir até o final pode ter certeza que vai estar melhor equipado para se tornar um programador melhor.</p>
<p>Continue estudando!</p>
]]></content:encoded><category>aprendizado</category><category>algoritmos-e-estruturas-de-dados</category><category>ruby</category></item><item><title>Aguri: A Estrutura de Dados mais Legal que Você Nunca Ouviu Falar</title><link>https://akitaonrails.github.io/2008/02/21/aguri-a-estrutura-de-dados-mais-legal-que-voc-nunca-ouviu-falar/</link><guid isPermaLink="true">https://akitaonrails.github.io/2008/02/21/aguri-a-estrutura-de-dados-mais-legal-que-voc-nunca-ouviu-falar/</guid><pubDate>Thu, 21 Feb 2008 22:03:00 -0300</pubDate><description>&lt;p&gt;Fazia &lt;strong&gt;muito&lt;/strong&gt; tempo que eu não via um bom artigo sobre Estruturas de Dados. Na verdade, acho que desde o tempo da faculdade eu não lia um texto tão legal. Este achado veio do blog &lt;a href="http://www.matasano.com/log/1009/aguri-coolest-data-structure-youve-never-heard-of/"target="_blank" rel="noopener"&gt;matasano chargen&lt;/a&gt; e resolvi traduzir para que todos possam apreciar a idéia. Mas avisando: um mínimo de ciências da computação é necessário para entender este texto.&lt;/p&gt;
&lt;p&gt;O tema é &lt;strong&gt;Binary Radix Trie&lt;/strong&gt;. Segue a tradução:&lt;/p&gt;
&lt;h1&gt;1&lt;/h1&gt;&lt;p&gt;Um pouco de remédio de ciências da computação para os auditores PCIs na minha audiência.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Fazia <strong>muito</strong> tempo que eu não via um bom artigo sobre Estruturas de Dados. Na verdade, acho que desde o tempo da faculdade eu não lia um texto tão legal. Este achado veio do blog <a href="http://www.matasano.com/log/1009/aguri-coolest-data-structure-youve-never-heard-of/"target="_blank" rel="noopener">matasano chargen</a> e resolvi traduzir para que todos possam apreciar a idéia. Mas avisando: um mínimo de ciências da computação é necessário para entender este texto.</p>
<p>O tema é <strong>Binary Radix Trie</strong>. Segue a tradução:</p>
<h1>1</h1><p>Um pouco de remédio de ciências da computação para os auditores PCIs na minha audiência.</p>
<p>Eu lhe dou um array com números inteiros aleatórios. Como você pode me dizer se o número 3 está nele?</p>
<p>Bem, aqui vai a maneira óbvia: cheque os números sequencialmente até encontrar o “3” ou terminar o array. Procura Linear. Dados 10 números, você precisa assumir que isso pode levar 10 passos; N números, N passos.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/22/Picture_1.png" alt=""  loading="lazy" /></p>
<p>Procura linear é ruim. É difícil fazer pior do que linear. Vamos melhorar isso. Ordene o array.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/22/Picture_2.png" alt=""  loading="lazy" /></p>
<p>Um array ordenado sugere uma estratégia diferente: pule direto para o meio do array e veja se o valor que está procurando é menor (à esquerda) ou maior (à direita). Repita, cortando o array no meio a cada vez, até encontrar o número.</p>
<p>Procura Binária. Dados 10 números, levará não mais do que 3 passos – log 10 (base 2) – para encontrar um deles num array ordenado. Procuras O(log n) são incríveis. Se tiver 65 mil elementos, levará somente 16 passos para encontrar. Dobre o número de elementos, e levará apenas 17 passos.</p>
<p>Mas arrays ordenados são uma porcaria; ordená-los custa mais caro do que procura binária. Então nós não usamos muito procura binária; em vez disso, usamos árvores binárias.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/22/Picture_3.png" alt=""  loading="lazy" /></p>
<p>Para procurar numa árvore binária, você começa pelo topo, e se pergunta <em>“minha chave é menor (esquerda) ou maior (direita) do que o nó atual”</em> e repete até ok, ok, ok você já sabe. Mas essa árvore é bonita, não é?</p>
<p>Procura com uma árvore binária (balanceada) é O(log n), como procura binária, variando com o número de elementos na árvore. Árvores Binárias são incríveis: você consegue navega rapidamente de maneira ordenada, uma coisa que não se consegue de uma tabela Hash. Árvores Binárias são uma implementação default para tabelas melhor do que tabelas Hash.</p>
<h1>2</h1><p>Mas Árvored Binárias não é o único mecanismo de procura em estrutura de árvore. ‘Binary Radix <a href="http://en.wikipedia.org/wiki/Trie"target="_blank" rel="noopener">Tries</a>’, também chamados árvores PATRICIA, trabalham como árvores binárias com uma diferença fundamental. Em vez de comparar maior/menor em cada nó, você checa para ver se um bit está configurado, indo para a direita se estiver, ou para a esquerda se não estiver.</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/22/Picture_4.png" alt=""  loading="lazy" /></p>
<p>Estou deixando fora muita coisa sobre como binary radix tries trabalham. É uma vergonha, porque radix tries são notoriamente mal documentadas – <a href="http://en.wikipedia.org/wiki/Robert_Sedgewick_%28computer_scientist%29"target="_blank" rel="noopener">Sedgewick</a> os destruiu em “Algorithms”, e a <a href="http://en.wikipedia.org/wiki/Radix_tree"target="_blank" rel="noopener">página na Wikipedia</a> também é uma droga. As pessoas ainda argumentam sobre como chamá-los! Em vez de uma explicação de backlinks e cantos etiquetados com posição de bits, <a href="http://www.matasano.com/log/basic-uncommented-crappy-binary-radix-trie/"target="_blank" rel="noopener">aqui vai uma pequena implementação em Ruby</a></p>
<p>Eis porque radix tries são legais:</p>
<ul>
<li>
<p>A performance de procura varia com o <em>tamanho da chave</em>, não o número de elementos na árvore. Com chaves de 16-bits, você garantidamente tem 16 passos independente do número de elementos na árvore, sem balanceamento.</p>
</li>
<li>
<p>Mais importante, radix tries lhe dá <em>igualdade lexicográfica</em>, que é uma maneira de dizer “procura com wildcards” ou “procura parecida com auto-complete de linha de comando”. Em um radix tree, você pode rapidamente procurar por “ro*” e conseguir “rome” e “romulous” e “roswell”.</p>
</li>
</ul>
<h1>3</h1><p>Eu o perdi.</p>
<p>Vamos colocar isso em contexto. Tries são uma estrutura de dados crucial para roteamento de Internet. O problema de roteamento é mais ou menos assim:</p>
<ul>
<li>
<p>Você tem uma tabela de roteamento com entradas para “10.0.1.20/32 → a” e “10.0.0.0/16 → b”</p>
</li>
<li>
<p>Você precisa que pacotes para 10.0.1.20 vão para “a”</p>
</li>
<li>
<p>Você precisa que pacotes para 10.0.1.21 vão para “b”</p>
</li>
</ul>
<p>Esse é um problema difícil de resolver com uma árvore binária básica, mas com uma radix trie, você está apenas perguntando por “1010.0000.0000.0000.0000.0001.0100” (para 10.0.1.20) e “1010”. (para 10.0.0.0). Procura Lexicográfica lhe dá o “melhor acerto” para roteamento. Você pode tentar no código Ruby acima; adicione *&ldquo;10.0.0.0&rdquo;.to_ip na trie, e procure por “10.0.0.1”.to_ip.</p>
<p>A correspondência entre roteamento e radix tries é tão forte que a mais popular biblioteca de uso geral de radix trie (aquele da CPAN) é na realidade roubado da GateD. É uma bagunça, aliás, não use isso.</p>
<p>Se você entende como um trie funciona, você entende como regular expressions trabalham. Tries são um caso especial de autômato finito determinístico (DFAs), onde galhos são baseados exclusivamente em comparações de bits e sempre se divide para frente. Um bom engine de regex apenas manipula DFAs com mais “funcionalidades”. Se minha figura faz sentido para você, as imagens nesse <a href="http://swtch.com/~rsc/regexp/regexp1.html"target="_blank" rel="noopener">excelente artigo</a> sobre o algoritmo de redução NFA-DFA de Thompson também vai, e esse artigo o fará mais esperto.</p>
<h1>4</h1><p>Você é um operador de rede no backbone de um provedor. Seu mundo consiste em boa parte de “prefixos” – pares de IPs de rede/netmasks. Os netmasks nesses prefixos são muito importantes para você. Por exemplo, 121/8 <a href="http://www.okean.com/koreacidr.txt"target="_blank" rel="noopener">pertence à Coréia</a>; 121.128/10 pertence à Korea Telecom, 121.128.10/24 pertence à um cliente da KT, e 121.128.10.53 é um computador desse cliente. Se estive rastreando um botnet ou uma operação de spam or propagação de worm, o número de netmask é bem importante para você.</p>
<p>Infelizmente, mesmo sendo importantes, em nenhum lugar no pacote IP está estampado um “netmask” – netmasks são completamente um detalhe de configuração. Então, quando você está olhando tráfego, você essencialmente tem esss dados para trabalhar:</p>
<p><a href="http://www.matasano.com/data.html"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/22/ips.png" alt=""  loading="lazy" /></a></p>
<p>Surpreendentemente, dados pacotes o suficiente para olhar, isso é informação suficiente para adivinhar netmasks. Quando trabalhava na Sony, <a href="http://www.sonycsl.co.jp/~kjc/"target="_blank" rel="noopener">Kenjiro Cho</a> veio com uma maneira realmente elegante para isso, baseado em tries. Eis como:</p>
<p>Pegue uma binary radix trie básica, como os usados por roteadores em software. Mas fixe o número de nós na árvore, digamos, 10 mil. Em um link de backbone, gravando endereços de cabeçalhos de IP, você vai encher 10 mil nós em um instante.</p>
<p>Armazene a lista de nós em uma lista, ordenada em ordem LRU. Em outras palavras, quando bater um endereço IP com um nó, “toque” o nó, colocando-o no topo da lista. Gradualmente, frequentemente vendo endereços borbulhando para o topo, e infrequentemente vendo nós afundando para baixo.</p>
<p><a href="http://www.matasano.com/log/wp-content/uploads/2008/01/Picture_8.png"target="_blank" rel="noopener"><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/22/Picture_6.png" alt=""  loading="lazy" /></a></p>
<p>Agora o truque. Quando acabarem os nós e precisar de uma nova, reclame de baixo da lista. Mas quando fizer isso, role os dados do nó para cima passando pelo seu pai, desta maneira:</p>
<p><img src="http://s3.amazonaws.com/akitaonrails/assets/2008/2/22/Picture_5.png" alt=""  loading="lazy" /></p>
<p>10.0.1.2 e 10.0.1.3 são irmãos /32s, as duas metades de 10.0.1.2/31. Para reclamá-los, misture-os em 10.0.1.2/31. Se precisar reclamar 10.0.1.2/31, você pode misturá-la com 10.0.1.0/31 para formar 10.0.1.0/30.</p>
<p>Faça isso por, digamos, um minuto, e as fontes mais fortes vão defender suas posições na árvore ficando no topo da lista LRU, enquanto barulhos ambientes /32 borbulham para /0. Para a lista crua de IP’s acima, com 100 nós, você tem <a href="http://www.matasano.com/data.aguri.txt"target="_blank" rel="noopener">isso</a></p>
<p>Cho chama essa heurística de Aguri.</p>
<h1>5</h1><p>Aguri tem licença BSD. Você <a href="http://www.sonycsl.co.jp/~kjc/software.html"target="_blank" rel="noopener">pode fazer download dele</a> e um programa driver que observa pacotes via pcap, da antiga homepage de Cho.</p>
<h1>6</h1><p>Eu vou a algum lugar com isso, mas estou com mais de 1.300 palavras nesse post agora, e se você for uma pessoa de algoritmos, já deve estar cansado de mim agora, e se não for, já deve estar cansado de mim agora. Então, deixe o Aguri afundar, e eu lhe darei alguma coisa legal e inútil para fazer com isso na semana que vem.</p>
]]></content:encoded><category>algoritmos-e-estruturas-de-dados</category><category>redes</category></item></channel></rss>