Elmord's Magic Valley

Software, lingüística, mitologia nórdica e rock'n'roll

Coisas de Viamão #2

2014-09-25 18:45 -0300. Tags: random, img

[Pichação 'fuck this' sobre rosa]

[Pichação 'fuck this' sobre rosa]

2 comentários

Título de capitalização como garantia de aluguel?

2014-09-18 21:33 -0300. Tags: life, home, worldly

Meus amigos, falcatrua é uma arte.

Ao invés de exigir fiador, algumas imobiliárias permitem o uso de um título de capitalização como garantia de aluguel. A idéia é: você compra à vista um titulo de capitalização em um valor pré-combinado (normalmente cerca de 12 vezes o valor do aluguel), que fica servindo como garantia caso você não pague o aluguel (sim, você continua tendo que pagar o aluguel todo mês, mesmo já tendo pago o valor de um ano inteiro no título). No final de 12 meses, você pode resgatar "100% da reserva de capitalização corrigida mensalmente pela poupança mais TR" (fonte: folheto do PortoCap Aluguel; outras seguradoras/bancos oferecem condições análogas), e ainda concorre a prêmios e descontos em serviços residenciais.

Parece (meio que) uma boa, não? Afinal o rendimento é o mesmo da poupança, então dá na mesma deixar o dinheiro parado na poupança ou em um título. O truque está na expressão "100% da reserva de capitalização". Se você for parar para ler as condições gerais do PortoCap Aluguel, descobrirá que a reserva de capitalização é 94,1910% do valor do título; o restante é tomado como custos de sorteio (0,023354%) e despesas administrativas (5,785646%). A taxa de juros de capitalização é de 0,5% (basicamente a mesma da poupança). Assim, o rendimento do dinheiro que você recebe ao final dos 12 meses é:

That's right, as taxas de administração e sorteio são calculadas precisamente para que no final o dinheiro não renda absolutamente nada; você recebe a mesma coisa que depositou. Ou seja, se enquanto com R$ 6000 na poupança eu teria cerca de R$ 6370,06 um ano depois, com o título eu teria os mesmos R$ 6000. Se você resgatar o valor do título antes de um ano, você ainda perde dinheiro.

No site linkado pode-se ver que, além do título de 12 meses, existe um de 15. "Ah, esse rende mais", certo? Errado: a reserva de capitalização do de 15 meses é 92,7919% do valor do título, o que dá um rendimento de 0,927919 · 1,00515 = 1,0000022, i.e., 0,00% também.

Conclusão: pelo menos você "não está" perdendo dinheiro, mas a propaganda de que o título dá os mesmos rendimentos da poupança é misleading (o folheto não explica o que é nem quanto é a reserva de capitalização; o carinha da imobiliária ainda teve a cara de me dizer que o título "é muito melhor que a poupança"). "Não está" entre aspas, porque no tempo em que esse dinheiro ficou parado ele poderia ter rendido juros se fosse aplicado na poupança ou algum outro tipo de investimento. Seria mais honesto simplesmente dizer que o dinheiro é devolvido no final com correção pela TR, sem dizer que é corrigido pela poupança, ao invés de oferecer correção pela poupança e tirar exatamente a mesma quantia em taxas. (Dou-me conta agora, todavia, de que isso provavelmente é parte do mecanismo que faz você sair com menos dinheiro se tirar o dinheiro antes de um ano, i.e., quando o dinheiro ainda não "rendeu" até voltar à quantia original.)

That's #capitalfinanceiro for you.

(By the way, eu mencionei que se você conseguir tirar algum lucro nessa brincadeira (i.e., o pouquinho que a TR rende), há incidência de imposto de renda sobre ele, enquanto a poupança é isenta de imposto de renda até R$ 50 000?)

3 comentários

My very brief affair with Btrfs

2014-09-14 01:38 -0300. Tags: comp, unix, mundane, ramble

Meia dúzia de dias atrás eu migrei meu / para Btrfs. Hoje eu reformatei a partição como ext4 e recuperei meu backup do / da semana passada.

O causo foi assim. Para usar o Btrfs, eu atualizei meu kernel para o 3.16, já que diversas melhorias foram realizadas no suporte a Btrfs nessa versão. Porém, o driver da minha placa de rede wireless (o broadcom-sta) andava não se comportando muito bem, o iwconfig hoje resolveu não listar nenhuma rede, e eu resolvi bootar com o meu kernel 3.14 anterior para ver se a situação melhorava. (Na verdade, com a atualização do kernel 3.2 para 3.14, que eu fiz para poder usar o Btrfs, eu tive que substituir o broadcom-sta da stable pelo da testing, e desde então ele já andava com uns comportamentos desagradáveis (tais como emitir um trace sempre que a wi-fi era iniciada), mas aparentemente a wi-fi estava funcionando corretamente mesmo assim.) Até aí, tudo transcorreu normalmente. Kernel 3.14 bootado, wi-fi funcionando, todos comemora.

Eis que eu fui abrir o aptitude (já não lembro mais por que motivo), e o módulo do Btrfs capota, emitindo algum erro sobre quotas/qgroups. Reiniciei a máquina com o kernel 3.14, fui abrir o aptitude de novo, mesmo erro. Agora não lembro mais a seqüência exata das ações, mas em algum momento eu desativei o suporte a quotas (btrfs quota disable /), abri o aptitude de novo, e dessa vez ele abriu. Porém, turns out que, no piripaque do filesystem, meu /var/lib/dpkg/status virou um arquivo vazio, e o aptitude abriu me mostrando nenhum pacote instalado e me oferecendo para baixar 3GB de pacotes (i.e., todos os pacotes que eu tinha na máquina). Nesse momento eu me disse "well, fuck", reformatei o / como ext4 e recuperei o backup que eu tinha feito quando fui migrar para Btrfs (que por sorte eu ainda não tinha apagado).

Moral da história: Talvez se eu tivesse me mantido usando o kernel 3.16 eu não tivesse tido esse problema. Porém, depois dessa experiência, e dado que na atual conjuntura eu deveria estar me preocupando com o mestrado e não com a saúde do meu filesystem, eu prefiro esperar mais uns meses para ver se o Btrfs fica mais estável e experimentá-lo de novo. Enquanto isso, eu voltei para o kernel 3.2 da stable, que pode não ser new and shiny, mas é sólido como uma rocha, forte como um touro e pesado como uma porpeta.

3 comentários

Partição de sistema Btrfs no Debian

2014-09-12 03:03 -0300. Tags: comp, unix, mundane

Btrfs é um sistema de arquivos relativamente "recente" (o desenvolvimento começou em 2008) com um bocado de features interessantes. Neste post, falarei uma porção de coisas sobre como usar uma partição Btrfs como sistema de arquivos raiz no Debian. O Btrfs ainda é considerado experimental (embora seja bastante estável em kernels recentes), e eu não confiaria meus arquivos pessoais a ele no momento (meu /home é uma partição ext4), mas como sistema de arquivos raiz de uma máquina de uso pessoal (que se eu perder é só reinstalar), acredito que os benefícios compensam os riscos.

[Update: Aparentemente ele não é tão estável assim.]

E que benefícios são esses?

A principal razão pela qual eu migrei meu / para Btrfs foi para usar sua feature de snapshots, que permite criar uma "duplicata" do estado do sistema de arquivos em um dado momento. A criação de um snapshot não duplica os arquivos; ao invés disso, os arquivos são compartilhados entre as duas "versões" do sistema de arquivos, e só são copiados à medida em que são modificados, o que torna a criação do snapshot praticamente instantânea e não consome espaço desnecessariamente. Com isso você pode, por exemplo, tirar um snapshot do /, atualizar/bagunçar o sistema e, se alguma coisa der errado, voltar ao estado anterior são e salvo.

Requisitos mínimos

Kernel 3.14 ou superior. O suporte a Btrfs do kernel atual (3.2) do Debian stable é bastante precário (eu consegui derrubar ele com um for ((i=0; ; i++)); do mkdir $i; done em um filesystem recém criado). As opções são:

btrfs-tools 3.14 ou superior. Disponível tanto no repositório da backports quanto no da unstable.

Adaptando o initramfs

Para poder bootar a partir de um raiz Btrfs, você precisará adicionar suporte ao filesystem ao seu initramfs. Para isso, adicione as seguintes linhas ao /etc/initramfs-tools/modules:

crc32c
btrfs

e execute update-initramfs -u -k all.

A linha crc32c é particularmente relevante: Geralmente, o update-initramfs é esperto o suficiente para incluir junto com um módulo todas as suas dependências no initramfs. Porém, por um bug no módulo btrfs, ele não indica explicitamente sua dependência pelo módulo crc32c. Se o módulo crc32c não for incluído manualmente na lista, a carga do módulo btrfs falhará no boot, com uma mensagem do tipo can't load module btrfs (kernel/fs/btrfs/btrfs.ko): unknown symbol in module, or unknown parameter. (Isso me tomou uma boa hora de sofrimento.)

Migrando para Btrfs

Para transformar seu raiz em Btrfs, você precisará bootar com um outro sistema e seguir uma de duas rotas:

Antes de reiniciar

Antes de rebootar o sistema no filesystem recém criado, lembre-se de altarar o etc/fstab do sistema. Você poderá ter que trocar:

Se o seu /boot fica dentro na mesma partição que o raiz, você pode ter que fazer alguma mudança no seu gerenciador de boot para garantir que ele consiga ler o Btrfs. O GRUB 2 aparentemente consegue ler Btrfs sem problemas. (Eu ainda uso o bom e velho GRUB 0.97, mas meu /boot é uma partição ext2 separada.)

Subvolumes e snapshots

O Btrfs organiza o filesystem em subvolumes. Inicialmente, o filesystem contém apenas um "subvolume raiz", mas outros subvolumes podem ser criados com o comando btrfs subvolume create /caminho/novo-nome, onde /caminho é um diretório dentro do filesystem de interesse. btrfs subvolume list /caminho lista todos os subvolumes (e respectivos IDs) contidos em um filesystem.

O comando btrfs subvolume snapshot /caminho-subvol-origem /caminho-subvol-destino duplica um subvolume, i.e., cria um "snapshot". Depois da duplicação, os dois subvolumes são independentes: alterações em um não se refletem no outro.

Um filesystem Btrfs possui um subvolume padrão, i.e., o subvolume que é usado como raiz do filesystem se nenhum outro for especificado. Inicialmente, o "subvolume raiz" é o padrão, mas você pode usar o comando btrfs subvolume set-default ID /caminho (onde ID é o ID mostrado por btrfs subvolume list) para escolher outro. Com isso você pode setar o subvolume padrão para um snapshot com um estado anterior do sistema, por exemplo, reiniciar a máquina, e magicamente seu sistema volta a ser o que era no momento do snapshot.

Você pode montar um subvolume diferente do padrão passando a opção -o subvolid=ID para o comando mount. -o subvolid=0 monta o subvolume raiz original. Você pode montar a mesma partição mais de uma vez.

Como eu mencionei antes, ao invés de copiar o backup do sistema diretamente para o raiz da nova partição, pode ser mais conveniente criar um subvolume primeiro, especialmente se você pretende usar a feature de snapshots com freqüência. Por exemplo, ao criar o filesystem:

# mount /dev/xxx /mnt/sistema
# cp -avx /mnt/sistema /algum/lugar/backup
# umount /mnt/sistema
# mkfs.btrfs -f /dev/xxx
# mount /dev/xxx /mnt/sistema
# btrfs subvolume create /mnt/sistema/current
# btrfs subvolume list /mnt/sistema
ID 264 gen 142 top level 5 path current
# btrfs subvolume set-default 264 /mnt/sistema
# cp -avx /algum/lugar/backup/* /mnt/sistema/current
# (realize as adaptações pré-boot adequadas)

Com o subvolume padrão setado, você pode reiniciar e bootar o sistema novo normalmente. Quando você quiser fazer um snapshot, basta montar o raiz original em algum lugar (e.g., /mnt/root) e fazer as operações de interesse:

# mount -o subvolid=0 /dev/xxx /mnt/root
# btrfs subvolume snapshot /mnt/root/current /mnt/root/snapshot-20120912
# umount /mnt/root

Feito isso, você pode bagunçar com seu sistema à vontade e, se quiser voltar atrás, pode executar:

btrfs subvolume set-default ID-do-snapshot

e reiniciar a partir do snapshot. Ou, se preferir não alterar o snapshot, você pode duplicá-lo primeiro e reiniciar pela cópia:

# mount -o subvolid=0 /dev/xxx /mnt/root
# btrfs subvolume snapshot /mnt/root/snapshot-20120912 /mnt/root/current2
# btrfs subvolume list /mnt/root
ID 264 gen 142 top level 5 path current
ID 266 gen 142 top level 5 path snapshot-20120912
ID 268 gen 142 top level 5 path current2
# btrfs subvolume set-default 268 /mnt/root
# reboot

Lembre-se de depois apagar o current antigo (com btrfs subvolume delete /mnt/root/current), caso não queira que ele fique ocupando espaço para sempre.

A vantagem de criar um subvolume primeiro antes de copiar o sistema é deixar o subvolume raiz original contendo apenas subvolumes, evitando misturar subvolumes e arquivos de sistema no mesmo nível. Isso permite manipular todos os subvolumes/snapshots da mesma maneira (qualquer versão do sistema pode ser apagada facilmente, por exemplo; com o sistema no raiz original isso não seria possível).

Observações sortidas

O utilitário btrfs permite abreviar comandos, desde que as abreviações não sejam ambíguas. Por exemplo, btrfs subvolume list / pode ser escrito como btrfs sub l /.

Espaço usado e livre em Btrfs são conceitos um tanto quanto curiosos, devido ao mecanismo de copy-on-write, suporte a compressão e outras peculiaridades do Btrfs. O comando btrfs filesystem df /caminho pode dar um resultado mais preciso do que o df -h.

Descobrir o espaço utilizado por cada subvolume é uma questão mais complicada (até porque subvolumes podem compartilhar dados). Se o mecanismo de quotas for habilitado no subvolume raiz original (com o comando btrfs quota enable /mnt/root), é possível usar o comando btrfs qgroup show /, que lista a quantidade de dados apontada por cada subvolume e a quantidade de dados não compartilhada com nenhum outro subvolume (e que portanto seria liberada se o subvolume fosse apagado). Para mais informações, dê uma olhada neste artigo.

É possível criar um snapshot somente-leitura passando a opção -r para o comando btrfs subvolume snapshot. A vantagem é que é mais difícil de destruir um backup do sistema assim. A desvantagem é que não é mais possível bootar pelo snapshot como se fosse um sistema comum.

2 comentários

Orthographica II: ortografia etimológica, o latim e o proto-indo-europeu

2014-08-23 00:05 -0300. Tags: lang, rant

No último post eu falei um pouco sobre a questão da reforma ortográfica do português e argumentei contra a idéia de que a reforma seria um "emburrecimento da língua". Talvez você queira lê-lo antes de continuar. Neste post, apresento mais alguns pensamentos que tive sobre o assunto.

A idéia de simplificar a ortografia suscita uma tremenda oposição, como os comentários na notícia linkada no último post servem para exemplificar. Num nível mais superficial, há a oposição à mudança puramente por ser uma mudança. Não há muito o que dizer aqui além do que eu já disse no último post; orthographia não se escreve mais assim desde 1943 e ninguém está sentindo falta desses hs. Da mesma forma, acredito que ninguém ache ruim escrever bife e futebol ao inves de beef e football, e no entanto assim foi há nem tantas décadas assim.

Um argumento mais elaborado é de que a grafia das palavras deve ser mantida como está para refletir a etimologia das palavras. Por exemplo, exibir se escreve com x porque provém da palavra latina exhibere, que se escreve com x (em latim, esse x tem de fato som de ks). Manter essa ortografia é bom porque evidencia o prefixo ex-, que é comum a tantas outras palavras e tem um significado mais ou menos bem-definido. Similarmente, escrever biologia ao invés de biolojia é bom porque evidencia a relação dessa palavra com a palavra biólogo. Se escrevêssemos tudo como falamos, essas relações entre as palavras, que são visíveis em suas formas gregas e latinas originais, seriam obscurecidas.

Acontece, entretanto, que quando os romanos começaram a escrever o latim, eles o escreveram exatamente como o falavam: em latim clássico, c sempre tem som de [k] (Cicero se lê "quíquero"), g tem sempre som de [g] (magis se lê "máguis"), x sempre tem som de [ks], as consoantes duplas (como em anno) realmente eram pronunciadas mais longas, e assim por diante. Em geral, quando uma língua recém adota um sistema de escrita [alfabético], ele tende a refletir bastante fielmente a fala; é só com o tempo que, à medida em que a língua vai mudando, a escrita resiste a acompanhar essas mudanças, e perde-se a relação direta entre a grafia e a pronúncia.

Ok, mas em latim não tem problema escrever como se fala, porque latim é latim e, sendo uma língua mais "pura" e menos "corroída" pela ação do tempo, essas relações que para nós só são visíveis na ortografia eram parte da língua latina viva, e portanto sua escrita baseada na fala as conserva, right?

Well, não é bem assim. O latim não brotou do nada. Assim como o português, o espanhol, o italiano, o francês, etc. derivam de uma língua anterior (o latim), e assim formam a família das línguas latinas ou românicas, também o latim, o grego, o sânscrito, o germânico, o eslávico, etc. derivam de uma língua comum, e formam assim a família das línguas indo-européias. O problema é que essa língua é tão antiga que nunca chegou a ser escrita; porém, é possível observar as similaridades e diferenças das línguas descendentes que ela deixou e reconstruir com alguma segurança como era essa língua original. A essa língua reconstruída denomina-se proto-indo-europeu.

E assim como o português é uma versão "corroída" de latim, o latim é uma versão "corroída" de proto-indo-europeu, e algumas relações entre palavras que eram visíveis em proto-indo-europeu ou em proto-latim não o são em latim. Voltando ao nosso exemplo clássico, exibir (pronunciado "ezibir") em latim é exhibere (pronunciado "eks-híbere", sem nenhuma letra muda nem irregularidade), mostrando claramente na pronúncia e na escrita o prefixo ex-. Mas e o que fazer do -hibere? Pois acontece que hibere nada mais é do que uma versão "corrompida" do verbo habere, que dá origem ao nosso haver. Essa mudança habere → hibere não é de uma natureza muito diferente da pronúncia de comi como "cumi" ou escola como "iscola" em português brasileiro moderno. E no entanto, essa "corrupção" que ocorreu entre o proto-latim e o latim foi tomada como forma oficial em latim; em latim clássico, se escreve como se fala.

Outro exemplo: as sequências de sons /ts/ e /ds/ do proto-indo-europeu se perderam em latim. Assim, "potentes" (no plural) em latim é potentes, mas no singular é potens, pois o t da forma original (*potents) se perdeu. Da mesma forma, a conjugação do verbo "ser" (esse) em latim é sum, es, est (de onde vem o nosso sou, és, é), mas a conjugação do verbo "poder", que em latim é derivado da expressão potis esse ("ser capaz") é possum, potes, potest, pois o t do *potsum original se perdeu, e essa perda é refletida na escrita.

E quanto ao próprio verbo "ser"? Sum, es, est, sumus, estis, sunt não é exatamente um primor de regularidade. Ok, o verbo "ser" é irregular em tudo que é língua, especialmente nas línguas indo-européias... interessante essa última observação, não? Acontece que a forma reconstruída desse verbo em proto-indo-europeu é h₁ésmi, h₁ési, h₁ésti, h₁sm̥ós, h₁sté, h₁sénti, que tem uma cara bem mais regular; até dá para distinguir um radical: h₁es-. (Em proto-indo-europeu, é comum os radicais terem a forma consoante-vogal-consoante, onde a vogal alterna entre e, o e ausência de vogal ao longo da conjugação ou declinação.) Agora o que é esse h₁? Trata-se de uma das chamadas consoantes laringeais do PIE. As laringeais não são nada mais, nada menos do que sons similares a h, que se perderam em todas as línguas descendentes exceto na família do hitita, deixando apenas vestígios de sua anterior existência (normalmente na forma de vogais ou efeitos sobre vogais adjacentes) na maioria das línguas indo-européias. That's right, os hs do PIE já tinham ficado mudos 3 mil anos atrás e os romanos nem chegaram a tomar conhecimento de sua existência, e portanto eles não são refletidos na escrita do latim. (Os hs do latim provêm de outros fonemas do PIE, tais como /gʰ/, que se "corromperam" e transformaram-se em h com o tempo.) Se os falantes de proto-indo-europeu estivessem vivos e dotados de escrita nessa época, eles teriam tido a mesma reação diante da escrita do latim que nós temos diante de propostas de reforma ortográfica em português.


I disapprove of your orthographic developments!

O ponto onde eu quero chegar é: defende-se a manutenção da ortografia atual para conservar as relações etimológicas com o latim, língua-origem do português, mas o próprio latim não preserva em sua ortografia diversas relações etimológicas com a sua língua-origem. Se os falantes de latim clássico podiam escrever como falavam, por que nós não podemos?

Disclaimer

Não, embora possa parecer, eu não estou promulgando a reforma; no momento não expresso nem apoio nem oposição. Meu intento foi unicamente demonstrar uma fraqueza do argumento pró grafia etimológica. Certamente há outros argumentos possíveis em favor da grafia etimológica (por exemplo, que manter uma proximidade com a ortografia do latim facilita nossa vida para aprender outras línguas latinas e o inglês), bem como contra (escrevemos amigo e amizade (do latim amicus e amicitate-) e nem por isso deixamos de perceber a relação entre essas palavras). Decida por si mesmo. Ou não.

2 comentários

Orthographica

2014-08-20 01:31 -0300. Tags: lang, rant, ramble

A notícia de que estão querendo propor outra reforma ortográfica me trouxe à mente umas cousas de que eu gostaria de falar.

Da inconsistência da ortografia vigente

Certa feita, estávamos eu e familiares percorrendo as verdejantes terras de Viamãoheimr, quando um dos indivíduos exclamou algo do tipo: "Olha lá, a placa escrito 'ezibida' com 'z'. E aposto que a pessoa não escreveu assim pra ser diferente, não sabia mesmo." O primeiro pensamento que me veio à mente foi:

  1. Será que, ao invés de zoar da ignorância do povo, nós não devêssemos ficar é preocupados com o estado da educação neste país?

Mas logo depois me ocorreu outro:

  1. Não é fantástico que hoje em dia praticamente todo o mundo saiba ler e escrever, ainda que imperfeitamente, coisa que não era verdade dois séculos atrás nem durante a maior parte da história da escrita?

E, finalmente:

  1. "Exibida" ainda se escreve com "x", really?

É quanto a este último que eu pretendo falar.

A nossa ortografia mantém um bocado de irregularidades (no sentido de que nem toda letra ou seqüência de letras corresponde a um único som e vice-versa), em nome de conservar a etimologia das palavras. Por exemplo, exibir se escreve com x para manter a ortografia similar à palavra latina exhibere que lhe dá origem. Seria um argumento válido para a manutenção da ortografia vigente, se não fosse pelo fato de que esse princípio é seguido de maneira bastante inconsistente.

Por exemplo, diversas palavras que contêm os prefixos/preposições latinos ex- e extra- são escritas com x em português, tais como extrair, extraditar, etc. Mas estranho (do latim extraneus) se escreve com s, sem nenhum bom motivo (que me conste). Em um caso extremo, extensão se escreve com x, mas estender se escreve com s.

O h etimológico é mantido em início de palavra, mas não em outras posições: habitar se escreve com h, mas desabitar não. Que princípio justifica a manutenção em um caso e não no outro?

C tem som de [k] ou de [s] dependendo do contexto, o que ajuda a manter a ortografia de palavras como pouco e paucidade consistente, mesmo já fazendo uns mil e tantos anos que esses dois cs não têm mais o mesmo som. Por outro lado, o passado de fico é fiquei, uma mudança de c para qu que não ocorre por nenhum motivo etimológico, mas tão somente para contornar a irregularidade da pronúncia da letra c em português.

"Estão emburrecendo o português"

Na notícia que me linkaram sobre o assunto, pode-se encontrar um bocado de comentários dizendo que a proposta "emburrece o português", "é a legalização da burrice", "emburrecer a gramática", "E são regras, pelo amor de Deus! Isso não pode ser aprovado, são várias gerações que aprenderam a escrever seguindo uma regra", etc., etc. Eu não perdi muito tempo na referida seção de comentários porque a minha tolerância a essas coisas hoje em dia é muito pequena, mas deu para dar uma idéia. (Também havia uma boa dose de comentários pró-reforma no texto. Curiosamente, dei uma olhada na versão de papel da Zero Hora de hoje ontem, na seção de mensagens dos leitores, e haviam publicado uns quatro comentários recebidos via Facebook sobre o assunto, nenhum pró-reforma. Chamamos isso de imparcialidade.)

Esses comentários me corroem o fígado por duas razões. Em primeiro lugar: reformar a ortografia "emburrece a língua" (tanto quanto isso faz sentido)? Então o que fazer das reformas que eliminaram o ph, o h de exhibido, o y de pyrâmide, o ch de chaos, a consoante dupla de innovar? Elas "emburreceram a língua"? Não são tantas gerações assim que aprenderam a escrever em qualquer dada ortografia no Brasil; a última reforma foi aprovada em 2009, a anterior em 1971 e a anterior a essa em 1943.

Em segundo lugar: a ortografia não é a língua; estritamente, a ortografia não é sequer parte da gramática. Mesmo que o português deixasse de ser escrito, ou que fosse escrito em alfabeto cirílico, continuaria sendo a mesma língua, com as mesmas regras gramaticais. Evidentemente, as línguas costumam ter um sistema de escrita oficial associado a si, mas a língua é independente do sistema de escrita usado para escrevê-la (há inclusive línguas que possuem múltiplos sistemas de escrita oficiais).

Então tu defende a reforma?

Não. Muito embora eu discorde da noção de que a reforma "emburrece a língua", nem por isso eu aprovo a tal reforma. Na verdade eu sequer aprovo ou (tanto quanto me é possível) adoto a última reforma aprovada (como alguns leitores hão de ter notado pelo meu uso da grafia "idéia"). Na verdade eu tenho cá para mim sérias ressalvas quanto à existência de um órgão regulador da língua e da regulação da norma ortográfica por lei; o inglês vive sem um órgão regulador e ninguém sofreu danos deletérios por conta disso. (Ok, talvez a ortografia do inglês possa ser considerada um dano deletério.) Especialmente quando reformas são promulgadas sem qualquer consulta à população (isso é um problema geral da democracia representativa, but I digress).

Voltando especificamente à última reforma proposta: este pessoal do R7 resolveu falar com El Hombre Marcos Bagno, que levanta alguns pontos interessantes com os quais qualquer reforma que pretenda aproximar a escrita da fala tem que lidar. Por exemplo, em mestre o s tem som de "s", mas em mesmo tem som de "z"; deveremos escrever mezmo? Em alguns lugares, o s de mestre tem som de "s", em outros tem som de "x"; deveremos aceitar tanto mestre quanto mextre como grafias válidas?

De qualquer forma, a proposta tal como está sendo apresentada parece não ter sido muito bem pensada. Em particular, pelo menos na reforma tal como os jornais a estão apresentando, tanto c quanto q seriam mantidos com som de [k], e o som inicial de "rato" continuaria sendo escrito com "r" no início de palavra e "rr" no meio. Se é para fazer uma reforma radical como a que se está propondo, então que pelo menos adotem uma ortografia realmente lógica, e que as exceções, se houverem, sejam justificadas por algum guiding principle.

Alguns dos proponentes da reforma dizem que "A simplificação ortográfica é a porta para a eliminação do analfabetismo". Será? O Japão usa um sistema de escrita absurdamente complicado, que consiste de dois silabários e um conjunto de mais ou menos dois mil ideogramas, cuja leitura pode ser drasticamente diferente dependendo do contexto (今 se lê "ima" e 日 se lê "hi", mas 今日 se lê "kyō"), e no entanto o Japão tem uma taxa de alfabetização de mais de oito mil 99% (fonte). Claro que, pode-se argumentar, quanto mais complexo o sistema de escrita, mais tempo se perde apredendo-o que poderia ser usado de maneira mais útil para outras coisas, e mais inacessível ele se torna a quem não tem esse tempo para dedicar a aprendê-lo (e.g., quem não termina o ensino fundamental). Por outro lado, não me é claro se haveria um ganho significativo em termos de alfabetização passando da ortografia atual do português (que já é bastante próxima da fala) para uma ortografia mais regular.

Então tu defende o quê?

Eu não defendo nada. Isso aqui é para terminar que nem um daqueles episódios do South Park que começam com uma questão polêmica e no final se fica com a impressão de que ambos os lados da discussão estão errados.

(Ok, eu pessoalmente defendo o status quo. Aliás, o status quo ante, de volta à ortografia anterior à última reforma. Mas neste post aqui eu não tento defender nada.)

Caveat commentator

Eu tenho uma experiência prévia ruim com posts que escapam para o mundo selvagem e atraem comentários menos-que-positivos. Na verdade a negatividade da experiência em questão foi amplificada pelo fato de eu ter sido pego absolutamente de surpresa na ocasião; hoje em dia eu já posto psicologicamente preparado para estar errado na Internet. De qualquer forma, dado o teor dos comentários na notícia da Zero Hora linkada, parece-me boa precaução deixar um recado a quem pretender comentar o post: caso pretenda deixar sua opinião sobre a reforma, faça-o apenas se for apresentar argumentos para defender sua posição. Comentários do tipo "eu aprovo", "eu desaprovo", "oh, o emburrecimento da língua", "meu, tu mal consegue escrever um parágrafo em português sem tacar um termo em inglês no meio ou conjugar a segunda pessoa errado, quem é tu pra falar de português?", etc., serão sumariamente eliminados, e os autores serão sumariamente insultados (vou mostrar a língua para eles).

Update: Assista ao próximo capítulo de Orthographica: uma batalha entre fonemas e grafemas.

9 comentários

A madman and a scholar

2014-07-06 20:41 -0300. Tags: life, mind, academia, mestrado, ramble

Recentemente, conversando com algumas pessoas sobre o mestrado, eu disse, with varying wording, que eu ando meio pirado. Não era figura de linguagem: I meant every word of it. Se eu ainda não perdi totalmente as estribeiras, eu provavelmente devo isso primariamente à Julie Fowlis; eu ainda sou capaz de pôr ela (e a Mari Boine) nos agradecimentos da dissertação.

Semana passada eu tive uma prova, na qual eu sabia não ter ido muito bem. Quinta-feira à noite as notas foram divulgadas por e-mail, e a professora (doravante Professora I) informou que as provas ficariam na secretaria da pós-graduação para quem quisesse pegar, e que só estaria de volta à universidade na segunda. A minha nota estava tão vergonhosa que eu não estava nem a fim de ir pegar a prova. Na sexta, a outra professora da disciplina (doravante Professora II) divulgou os conceitos da disciplina; eu estava de recuperação. Apesar de estar chateado e todo perturbado das idéias, eu resolvi ir pegar a prova na secretaria e dar uma olhada, just to make sure. Turns out que a Professora I somou errado as notas das questões e me deu 2.5 ao invés de 25 em uma questão que valia 25. Se a nota tivesse sido calculada corretamente, eu não teria ficado de recuperação.

Mandei um e-mail para a Professora I sexta ao meio-dia, com um scan da prova. Não recebi resposta até o momento.

Nos últimos dois dias, eu tive três sonhos. (Isso dá uma idéia de quão regulares andam meus padrões de sono ultimamente.) No primeiro, eu estava discutindo loucamente com a Professora I tentando convencê-la sem sucesso de que a nota tinha sido calculada errada. No segundo, eu estava discutindo louca e veementemente com outro professor que queria me rodar por FF a qualquer custo por eu ter faltado a um dia de apresentações. O terceiro foi uma variação de um sonho recorrente, embora infreqüente, em que eu descubro no final do curso que eu não assisti a nenhuma aula da cadeira de Álgebra Linear (da graduação) ou alguma outra coisa math-related e vou ter que refazer a cadeira. Dessa vez o sonho foi mais elaborado e eu tinha mais alguma coisa para fazer depois de apresentar o TCC, mas a essas alturas eu já não lembro mais dos detalhes concretos.

Eu estou meio que urgentemente precisando de umas férias, que eu não terei porque (1) um certo professor, por conta das aulas perdidas por conta da copa (o semestre já não tinha começado mais cedo para compensar isso?), viagens no meio do semestre, aulas canceladas por motivos pessoais e ausência generalizada de cronograma, vai dar aula até além do fim do semestre (pode isso? claro que não; faz diferença? claro que não); (2) bolsista não tem férias. Segundo o pessimamente mal-escrito regulamento do PPGC, o bolsista tem direito a um mês de férias (por ano? na vida? assumindo um mês por ano, dá para emendar o mês de férias de 2014 e o de 2015? por que escrever direito se podemos deixar ambíguo e interpretar como quisermos depois?), mas elas têm que ser combinadas com antecedência com o orientador, e o caso é difícil de defender, porque eu nem fiz grande coisa como bolsista so far. Em qualquer caso, não vale a pena pedir férias agora por conta da cadeira mencionada.

Faz algum tempo que me admira o fato de que essencialmente o mesmo cérebro que servia ao cidadão cavernícola de vinte mil anos atrás também é capaz de absorver e ser bombardeado por quilos e quilos de informação com variáveis graus de abstração numa proporção supostamente muito maior hoje em dia do que então. Hoje isso me levou a duas reflexões: (1) De um lado, uma apreciação do fato de que a vida intelectual do cidadão cavernícola talvez não fosse tão simples assim; aliás, deixo-vos a conjectura (sem qualquer argumento, qualificação ou conhecimento para fazê-la) de que viver naquela época era tão difícil que, tendo-nos livrado de parte dessa complexidade graças a avanços tecnológicos (agricultura e etc.), quilos de poder de processamento foram liberados para as atividades intelectuais mais "elevadas" que realizamos hoje. (2) De outro lado, óbvio que isso ia dar merda, e é por isso que tem tanta gente pirada pelo mundo.

Por que eu estou falando disso? Porque nos últimos tempos eu ando meio overwhelmed com idéias e coisas que seriam interessantes de desenvolver e coisas que eu gostaria de ler e coisas que eu não gostaria tanto assim de ler mas que eu preciso ler para o mestrado e trabalhos para fazer, e eu estou em um ponto em que eu já não foco direito em coisa nenhuma; uma hora estou lendo um paper sobre dependent types, aí não termino de ler e estou lendo fragmentos arbitrários da documentação do Chicken Scheme, aí depois estou lendo sobre phase separation em Lisps, e aí estou escrevendo um leitor de S-expressions em C. (Isso foi um resumo do meu dia de ontem-anteontem (dado meus anteriormente mencionados padrões de sono, eu já não sei quando começou um dia e terminou outro).) Eu quero criar uma linguagem de programação e uma conlang e um sistema de escrita e uma máquina virtual e pensar sobre o problema da minha dissertação e eu não consigo me concentrar em nenhuma dessas coisas por qualquer quantidade decente de tempo nem parar de pensar nelas. Talvez eu estivesse precisando ficar uns dias sem olhar para um computador ou para quaisquer coisas acadêmicas e curtir a vida de outras formas. Não me ocorre nenhuma possibilidade de outras formas, entretanto, e eu me pergunto se eu estou fazendo alguma coisa muito errada com a minha vida. Adicione-se a isso uma prova para a qual enquanto eu estudava e lia os exercícios e as respostas no livro eu me perguntava: "Como é que eu ia pensar nisso? Será que eu sou incompetente? E tu ainda pretende fazer o doutorado?" Adicione-se a isso mil outros problemas que estão fora do escopo do discutível neste blog.

Querida Professora I: Responda o bendito e-mail. Boa noite.

7 comentários

O que são capabilities e o que elas têm de tão mágico

2014-04-19 08:39 -0300. Tags: comp, prog, unix, security

Eu já falei de capabilities por aqui algumas vezes antes. Neste post tentarei explicar o que elas são e por que eu acho que elas são a panacéia universal (ok, não, mas por que eu acho que elas são um avanço em comparação com as permissões convencionais do Unix).

(Antes de mais nada, gostaria de ressaltar que as capabilities a que eu me refiro aqui não têm nada que ver com o que o Linux chama de capabilities, que são basicamente uma maneira de separar o tradicional balaio de poderes do root em unidades que podem ser atribuídas individualmente a processos (e.g., com isso é possível dar a um processo o poder de alterar o relógio do sistema sem conceder todos os outros poderes de root junto).)

Ok, que diabos são capabilities?

Uma capability é um objeto ou "token" que representa a habilidade de um processo de acessar um certo recurso, tal como um arquivo ou uma conexão de rede. Capabilities possuem três propriedades importantes:

Turns out que file descriptors no Unix possuem essas três propriedades. Ao abrir um arquivo no Unix, o processo recebe um número inteiro que é um índice na tabela de file descriptors do processo, que é acessível apenas pelo kernel. File descriptors abertos podem ser passados adiante para os filhos de um processo ou transferidos via sockets. Uma vez aberto o arquivo, as credenciais do processo são irrelevantes para o seu acesso: um processo pode, por exemplo, começar executando como root, abrir um recurso privilegiado (e.g., ouvir em uma porta menor que 1024), e depois trocar de credenciais para um usuário menos poderoso sem perder o acesso ao recurso privilegiado, pois a posse do file descriptor da conexão é suficiente para garantir-lhe acesso ao recurso. (Um file descriptor não é uma capability pura porque conserva outros dados além dos necessários ao acesso do recurso, tais como a posição do cursor no arquivo, o que dificulta seu uso compartilhado por outros processos depois de transmitido, mas em essência trata-se de uma capability.)

A mágica de um modelo de segurança baseado em capabilities, entretanto, é que todo acesso a recursos é feito por meio de capabilities, e um processo tem acesso apenas aos recursos representados pelas capabilities que lhe são entregues. No Unix, por outro lado, um processo recebe acesso implícito e mais ou menos inevitável a diversos recursos, tais como o filesystem e a habilidade de criar conexões de rede. É possível cercar o acesso a esses recursos, e.g., usando chroot para entregar um filesystem alternativo ao processo (mas não é possível não entregar filesystem nenhum ao processo) ou regras de firewall para bloquear o acesso do processo à rede (geralmente indiretamente, e.g., rodando o processo com outro usuário e bloqueando o usuário no iptables), mas há uma série de dificuldades e inconvenientes envolvidos:

A raiz do problema é que o modelo de segurança do Unix foi criado no contexto dos sistemas multi-usuário dos anos 1970, em que a preocupação primária era proteger os usuários uns dos outros e o sistema dos usuários. Hoje em dia as preocupações são outras: no caso de computadores pessoais, a maioria das máquinas roda com um único usuário, e queremos proteger o usuário de programas potencialmente mal-comportados (seja por conterem vulnerabilidades, seja por descuido do programador, seja porque o programa é intencionalmente malicioso) que o próprio usuário executa. No caso de servidores, queremos minimizar o potencial de desastre caso um serviço seja comprometido. Capabilities se encaixam melhor (acredito) com essas preocupações do que o modelo de segurança tradicional do Unix, pois permitem um controle maior de o que um processo é capaz de acessar. Ao invés de passarmos aos programas o acesso ao filesystem inteiro e os nomes de arquivos que queremos que o programa manipule, passamos capabilities aos arquivos de interesse, sem entregar o acesso a todo o resto do filesystem junto. Ao invés de chamar todos os programas com o poder de abrir conexões de rede, podemos passar esse poder apenas aos processos que realmente tenham que ter esse acesso.

E o browser?

A essas alturas você talvez esteja se perguntando: "Ok, meu filho, e como isso resolve o problema do browser? Eu não vou ter que entregar uma capability para acessar todos os meus arquivos para o caso de eu querer fazer upload de um deles? Hã? Hã?"

A solução é uma das coisas mais legais que se consegue fazer com capabilities. Lembre-se de que capabilities podem ser transmitidas entre processos. Isso significa que nós podemos ter um daemon (chamemo-lo fileopend) capaz de fornecer capabilities. Ao iniciarmos o browser, passamos a ele uma capability que é um canal de comunicação com o fileopend. Quando o usuário vai fazer upload de alguma coisa, ao invés de o browser abrir a janelinha de "Abrir arquivo", ele manda uma requisição de abertura de arquivo ao fileopend. O fileopend, então, mostra a janelinha de "Abrir arquivo" ao usuário. O usuário escolhe o arquivo, e então o fileopend o abre e envia a capability correspondente àquele arquivo específico para o browser. O browser, assim, só tem acesso a arquivos que o usuário tenha selecionado explicitamente na janela de "Abrir arquivo".

Genial, hã?

And we can do it right now!

Atualmente existe um projeto chamado Capsicum: practical capabilities for UNIX, que teve bastante progresso recentemente. Trata-se de uma implementação de capabilities no FreeBSD, que está sendo adaptada para o Linux. O projeto inclusive produziu uma versão do Chromium baseada em capabilities, usando uma idéia análoga à do fileopend (que eles chamam de "user angels") para abrir arquivos do usuário.

Mas teoricamente, seria possível implementar capabilities em user-space no Unix com uma pequena dose de faconice. No cenário mais simples, seria possível rodar cada processo com um usuário/grupo diferente (gerar um UID/GID para cada processo novo), em um chroot, com acesso à rede bloqueado no firewall, etc., apenas com um canal de comunicação com um daemon que intermediaria o acesso dos processos a todos os recursos, tais como arquivos, conexões de rede, etc. Esse daemon faria o papel do kernel em um sistema com suporte nativo a capabilities. O problema com essa abordagem é performance: todo acesso a recursos teria que passar pelo canal de comunicação entre os processos comuns e o daemon. Porém, uma vez que file descriptors podem ser transmitidos por sockets no Unix, seria possível usar o daemon apenas para criar e transmitir file descriptors (capabilities) para os processos. Uma vez de posse do file descriptor, o processo pode utilizar o recurso "nativamente". A perda de performance seria apenas na abertura de recursos, e talvez não fosse tão significativa. Anyway, graças ao Capsicum, estamos em vias de ter capabilities nativas no Linux (hopefully no kernel mainline) sem ter que apelar a gambiarras.

Unix is dead. Long live Unix.

Comentários

NSA operation ORCHESTRA, e alguns pensamentos sobre o estado atual da computação

2014-04-17 01:29 -0300. Tags: comp, prog, security, politics, ramble

No FOSDEM (Free and Open Source Developers' European Meeting) deste ano, Poul-Henning Kamp deu uma palestra muito interessante intitulada "NSA operation ORCHESTRA - Annual Status Report" (video, slides). A palestra, apresentada na forma de um report de um programa fictício da NSA, explora a idéia de o que a NSA poderia estar fazendo para coletar o máximo de informação com o menor custo possível. Possibilidades incluem:

Como diz nos slides da palestra, "A intenção foi fazer as pessoas rirem e pensarem, mas eu desafio qualquer um a provar que não é verdade."

A única coisa que não me agrada nessa palestra é a conclusão de que "this is a political problem" e de que é inútil fazer qualquer coisa do ponto de vista técnico. As ações da NSA são um problema político, mas: (1) isso não quer dizer que não possamos ou devamos buscar eliminar fontes de vulnerabilidades no software existente; (2) esse tipo de sabotagem poderia vir igualmente de uma organização privada com recursos suficientes, então o problema não é puramente político, no sentido de que mesmo nos livrando de governos maliciosos, o problema não desapareceu.

Ação política é importante, mas como desenvolvedores de software podemos tomar atitudes para mitigar o efeito de ações maliciosas sobre software. Para começar, podemos parar de usar ferramentas da idade da pedra, que facilitam a introdução (acidental ou deliberada) de falhas de segurança, como C/C++. O potencial de insegurança no C/C++ não é o mero descuido na hora de calcular os índices e o tamanho de um vetor (que por si só já é uma eterna fonte de vulnerabilidades). Em C/C++ existe o conceito de comportamento indefinido (undefined behavior), e cada versão nova do GCC/Clang/[insira seu compilador C/C++ favorito aqui] sai "melhor" em explorar comportamento indefinido para fins de otimização do que a anterior. A idéia básica é que se um programa realiza certas ações "proibidas" pelo standard da linguagem (e.g., acessar um elemento além do final de um vetor), o comportamento resultante do programa não é especificado pelo standard, então o compilador é livre para gerar um programa que faz qualquer coisa nesses casos. Por exemplo, suponha que você escreve algo como:

void foo(struct pessoa_t *pessoa) {
    int idade = pessoa->idade;

    if (pessoa == NULL)
        printf("Oops, pessoa inválida!\n");
    else
        printf("A idade da pessoa é %d.\n", idade);
}

pessoa é um ponteiro (potencialmente nulo) para uma estrutura de dados. Acessar o conteúdo apontado por um ponteiro nulo é um comportamento indefinido: um programa que faz isso é um programa incorreto. Logo, o compilador é livre para gerar código que não funciona no caso de o ponteiro ser nulo. Logo, o compilador pode assumir que pessoa não é um ponteiro nulo: se a hipótese do compilador for verdade, o programa estará correto, e se não for, tanto faz se o programa está correto. Mas se o ponteiro não é nulo (por hipótese), então o if na função é redundante (pois a condição é sempre falsa): o compilador pode descartar as linhas cor-de-burro-quando-foge do código resultante, como uma otimização. Foi exatamente uma otimização desse tipo que transformou um erro de programação no kernel do Linux em uma falha de segurança alguns anos atrás. Outras situações em que o compilador pode conspirar contra o programador incluem: remover verificações de overflow em operações aritméticas, pois signed overflow é indefinido em C/C++; reordenar acessos à memória, ignorando que outras threads podem depender do acesso em uma certa seqüência, se o programador não tomar o cuidado de forçar a ordem das operações; e inúmeras outras situações. Dado o grau de exploitação de comportamento indefinido nos compiladores C/C++ modernos, seja por avanços tecnológicos em análise estática, seja por influência da NSA/agentes soviéticos/illuminati/maçonaria, eu me sinto fortemente propenso a encarar o compilador C/C++ como um agente malicioso, e a idéia de minimizar o uso dessas linguagens parece cada vez mais appealing.

Outra medida técnica para reduzir a propensão dos sistemas computacionais a falhas de segurança é adotar modelos de segurança baseados em capabilities, em que o default é os processos não terem acesso a nada que não lhes seja explicitamente concedido, ao contrário dos modelos baseados em usuários, como o do Unix, em que é difícil ter certeza absoluta de quais são os poderes que um processo tem, e a grande maioria dos processos roda com mais permissões do que precisa (e.g., seu browser tem o poder de ler todos os seus arquivos pessoais, o tempo inteiro).

Falar é mais fácil do que fazer. Hoje em dia há uma falta de soluções práticas que nos permitam livrarmo-nos do C/C++ sem perder performance ou outras conveniências, ou sistemas baseados em capabilities que não sejam sistemas acadêmicos cuja adoção no mundo real é inviável. Estes são problemas nos quais eu pretendo trabalhar durante a minha existência neste mundo [aquela história de crowdfunding era só parcialmente brincadeira :)]; mais sobre isso em um post futuro, talvez. O ponto é que definitivamente há (muito) o que fazer do ponto de vista técnico para mitigar os efeitos de ações da NSA e outros agentes maliciosos.

5 comentários

Bounds checking elimination

2014-04-12 23:45 -0300. Tags: comp, prog, pldesign

Essa história de Heartbleed me lembrou de algumas coisas que eu tinha pensado meses atrás e não lembrava mais.

Para quem não sabe, o Heartbleed (concisamente explicado por este quadrinho do xkcd) é uma falha de segurança na OpenSSL (uma biblioteca que implementa os protocolos de comunicação segura SSL e TLS usada por basicamente todo o mundo) que permite a um atacante obter porções da memória do servidor potencialmente contendo dados como nomes de usuários, senhas, a chave privada do certificado servidor, etc.

Como 237% das falhas de segurança de software encontradas nos últimos 30 ou 40 anos, o Heartbleed é causado por um buffer overflow (tecnicamente "overread", pois trata-se de leitura e não escrita), e teria sido evitado se a OpenSSL tivesse sido escrita em uma linguagem que fizesse verificação de limites (bounds checking) antes de acessar uma posição de um vetor.

No fórum do xkcd, alguém escreveu:

Heartbleed is yet another example of why coding in C is a bad idea. A memcpy with an incorrect size caused all this because C compilers do no bounds checking. Heartbleed wouldn't have happened if OpenSSL had been written in, for example, Ada. Instead of an information leak that leaves no trace it would have been a denial of service at the worst.

Mais adiante na thread, alguém respondeu:

It's yet another example of why poorly written code is a bad idea. No amount of programming languages and frameworks is going to protect you from incompetent programmers.

A essa altura eu fechei a tab e fui ler outras coisas, porque se eu continuasse ali eu ia acabar respondendo com o equivalente virtual do soco na cabeça pra desentupir o cérebro. Isso é mais ou menos como ter um viaduto do qual diariamente caem carros há trinta anos, e se recusar a colocar um muro de proteção nas bordas, porque se alguém cai "a culpa é do motorista que foi incompetente".

But, but, but, bounds checking? Is it web-scale?

Se bounds checking é uma coisa tão mágica, por que não está todo o mundo usando linguagens que fazem bounds checking? A resposta, obviamente, é performance, a propriedade mais importante de qualquer software. Does it work? No, but it's fast! Ok, chega de comentários sarcásticos por ora. Eu já falei sobre a performance de bounds checking em um post anterior, onde fiz alguns benchmarks com código em C com e sem bounds checking (implementado manualmente com ifs no código testando se o índice está dentro dos limites do vetor e abortando a execução caso contrário). As conclusões no final foram que:

Do segundo item depreende-se que um acesso bounds-checked a um vetor é cerca de 25% mais lento do que um acesso direto. Assumindo que a maioria dos programas não consiste primariamente de acessos a vetores, esses 25% talvez não fizessem tanta diferença, e o benefício seria maior que o custo. (Disclaimer: talvez no caso geral o slowdown seja maior que 25%. Talvez eu faça mais uns benchmarks, só para não perder o costume, quando estiver mais disposto. Read on.)

O primeiro item é mais interessante: em algumas circunstâncias é possível provar que todos os acessos a um vetor estarão dentro dos limites, e nesses casos não é necessário fazer qualquer verificação em tempo de execução. Por exemplo (assumindo uma função hipotética length_of, que retorna o comprimento de um vetor), em um loop como:

for (i=0; i < length_of(vector); i++)
    printf("%d", vector[i]);

não é necessário verificar em tempo de execução se vector[i] está dentro dos limites do vetor, pois é possível ao compilador provar em tempo de compilação que i só adquire valores que são índices válidos do vetor. Para casos simples como esse, o gcc e outros compiladores já são capazes de fazer esse tipo de análise estática, como visto no post linkado; não se trata de uma tecnologia mítica e utópica. Os problemas começam a surgir quando temos coisas como:

int get(int vector[], int i) {
    return vector[i];
}

void foo() {
    ...
    for (i=0; i < length_of(vector); i++)
        printf("%d", get(vector, i));
}

pois a função get não sabe que será chamada com um índice válido. Se o compilador fizer inlining de get no corpo de foo, ele será capaz de eliminar o bounds checking, mas, no caso geral, não queremos sempre fazer inlining (get poderia ser uma função grande chamada em diversos pontos do código, por exemplo), e a função get (que poderia ter sido compilada separadamente) não pode assumir que quem a chamar lhe passará um índice válido.

Mas ela pode exigir. Imagine que pudéssemos escrever algo do tipo:

int get(int vector[n], int i)
    i>=0 && i<n;
{
    return vector[i];
}

i>=0 && i<n é parte da assinatura da função: além de ela exigir que o primeiro argumento seja um vetor de int e o segundo um int, ela também exige que a condição especificada seja satisfeita. Com isso: (1) a função pode assumir que a condição é verdadeira dentro do corpo, eliminando assim o bounds checking; e (2) o encargo de testar se a condição é verdadeira é passado para o chamador da função (foo, no nosso exemplo), onde há contexto suficiente para determinar se a condição é sempre verdadeira em tempo de compilação (por conta de ocorrer dentro do for, no nosso exemplo). Se esse for o caso, o bounds check pode ser eliminado do programa; caso contrário, o check é realizado em tempo de execução, garantindo que o acesso será seguro.

Mesmo em loops em que o range não está evidentemente nos limites do vetor é possível utilizar uma pequena dose de falcatrua para "amortizar" os checks. Por exemplo, em uma função como:

int sum(int vector[], int start, int end) {
    int i, total=0;
    for (i=start; i<=end; i++)
        total += vector[i];
    return sum;
}

não é possível eliminar completamente o checking, pois não sabemos de antemão se start e end é uma faixa válida de índices do vetor. Mas nem por isso precisamos fazer o checking dentro do loop. Ao invés disso, podemos transformar o código em:

int sum(int vector[], int start, int end) {
    int i, total=0;

    int length = length_of(vector);
    if (start < 0) out_of_bounds_exception();
    if (end >= length) out_of_bounds_exception();

    for (i=start; i<=end; i++)
        total += vector[i];
    return sum;
}

Se a execução passar dos ifs, então start e end são índices válidos no vetor, e não precisamos executar testes para cada acesso.

Só tem um pequeno problema na transformação acima: ela encerra o programa se end estiver além dos limites do vetor mesmo antes de vetor[end] ter sido acessado; basicamente uma exceção que ainda não aconteceu encerra o programa. Neste programa em particular isso não chega a ser um problema pois o comportamento observável do programa seria o mesmo, mas isso não é válido no caso geral. Por exemplo, poderia ser que eu soubesse de antemão que o vetor é encerrado por um valor 0, e escrevesse o código como:

int sum(int vector[], int start, int end) {
    int i, total=0;

    for (i=start; i<=end; i++) {
        if (vector[i] == 0) break;
        total += vector[i];
    }

    return sum;
}

Nesse caso, mesmo que eu passe um end inválido, pode ser que o meu programa termine com um resultado correto, desde que o vetor seja devidamente terminado com um 0. O compilador não tem dados suficientes para provar que o vetor terá o 0, entretanto, e portanto checks precisam ser inseridos. Ainda assim, é possível transformar o código em algo como:

int sum(int vector[], int start, int end) {
    int i, total=0;

    int length = length_of(vector);
    if (start < 0) out_of_bounds_exception();
    int bounded_end = min(end, length-1);

    for (i=start; i<=bounded_end; i++) {
        if (vector[i] == 0) break;
        total += vector[i];
    }

    if (end>bounded_end && i>bounded_end) out_of_bounds_exception();

    return sum;
}

que é menos trivial (e provavelmente pode ser escrito de maneira mais eficiente, mas menos clara para fins de exposição), mas preserva a semântica do programa (a prova é sugerida como exercício para o leitor).

Nem sempre os índices de vetores provêm de ranges seqüenciais. Um exemplo em que isso não ocorre é em uma busca binária, em que, para eliminar os checks, o compilador precisaria conseguir provar que (min+max)/2 está entre min e max*.

Outra situação é quando criamos um vetor de lookup reverso r que mapeia os valores de um vetor v aos índices correspondentes, i.e., se v[1] = 42, então r[42] = 1. Nesse caso, para eliminar os checks, o compilador precisa ter informação suficiente para saber que os valores de v são sempre índices válidos em r. O que pode ser viável se o tipo de v indicar qual é a faixa de valores válidos que o vetor pode conter. De qualquer forma, é interessante que esse tipo de assumption usualmente escondida sobre o comportamento do programa seja explicitamente expressível na linguagem, especialmente se tais declarações (1) não forem obrigatórias, e (2) forem usadas para melhorar performance. (Side-effect: as pessoas seriam incentivadas a documentarem melhor seus programas visando ganhar performance. Todos comemora.)

Caveats

Bounds checking é só um componente de memory-safety. Outro aspecto importante é garantir que os ponteiros/referências apontam de fato para objetos válidos em memória, e não para áreas que já foram desalocadas (ou pior, realocadas para outros objetos). A solução clássica para o problema é gerência automática de memória com garbage collection, mas há outras soluções possíveis.

O fato de que, com a introdução de pré-condições, os tipos das funções falam mais sobre o que a função faz, provavelmente implica que os tipos das funções mudam com mais freqüência quando uma função é alterada, efetivamente alterando sua interface, uma vez que cabe ao chamador da função garantir que as pré-condições são satisfeitas. Isso torna mais provável que uma alteração em uma biblioteca exija a recompilação de todo o mundo que depende dela. A solução que eu proponho é distribuir tudo como bytecode e (re)compilar para código nativo transparentemente as needed (o que tem inúmeras outras vantagens, tais como não fixar a ABI, permitir compilar o código com ou sem certs instruções (e.g., SSE) dependendo de sua disponibilidade no processador, permitir se aproveitar de mandingas brabas dependentes de uma versão da arquitetura (e.g., assumir que ponteiros têm efetivamente 48 bits e não 64 no amd64) sem se preocupar se daqui a 5 anos elas não vão mais funcionar, pois o ambiente pode simplesmente testar se uma assumption é válida e recompilar caso contrário, etc.). Uma solução alternativa é the C++ way: não fazer nada a respeito.

Conclusões

1. Bounds checking, galera. De uma vez por todas. Entre acidentes e talvez-nem-tão-acidentes, depois de 30 anos tá na hora de a gente aprender, não?

2. Bounds checking não necessariamente implica perda de performance, pois o compilador pode determinar que certos checks não são necessários em tempo de execução. Em uma linguagem sem bounds checking, o programador tem que ou inserir os checks manualmente anyway para garantir que não ocorrerá nenhum buffer overflow, ou concluir que o check não é necessário pois o índice está garantidamente dentro do vetor. No primeiro caso o check está lá anyway com ou sem bounds checking automático; com o check automático não há o risco de o programador esquecer de fazer o teste. No segundo caso o programador pode (idealmente) escrever explicitamente o raciocínio que permite concluir que o check é desnecessário, o que, além de menos error-prone (já que, se o compilador não for capaz de concluir que o raciocínio é válido, seja porque o raciocínio está errado ou porque o compilador não é suficientemente esperto, ele vai inserir o check dinâmico), é benéfico do ponto de vista de engenharia de software.

P.S.: Idéias similares às apresentadas neste post já foram inventadas e reinventadas mais de oito mil vezes sob os nomes de dependent types, design by contract, e sabe-se lá mais que outros (sinta-se à vontade para citar referências nos comentários). É por este motivo que, embora o tópico seja perfeitamente o tipo de coisa na qual eu gostaria de trabalhar, eu provavelmente não vou nem tentar empurrar o tema da minha dissertação de mestrado para esse caminho. Mais sobre isso em um post futuro, talvez.

_____

* Ou ser informado disso pelo programador, como um "axioma" sem prova. Nesse caso introduz-se uma fonte bastante perigosa de potenciais bugs, pois um axioma incorreto poderia levar a transformações de código incorretas em pontos arbitrários do programa. Uma solução semi-aceitável neste caso particular é ter uma função na biblioteca padrão da linguagem que calcula a média de dois números, acompanhada de um axioma sobre o resultado. O problema é que se a habilidade de declarar axiomas sem prova for introduzida na linguagem, é praticamente certo que alguém vá usá-la incorretamente e criar outro Heartbleed. Outra alternativa é introduzir um meio de o programador escrever a prova do axioma, que o compilador seria então capaz de verificar. Isto é nada mais, nada menos do que uma aplicação de proof-carrying code.

4 comentários

Main menu

Posts recentes

Tags

comp (74) unix (29) life (28) prog (26) random (23) mundane (20) lang (19) about (18) mind (14) web (13) rant (11) img (11) ramble (8) privacy (8) bash (7) esperanto (7) conlang (5) home (5) pldesign (5) lisp (4) freedom (4) book (4) misc (4) copyright (4) kbd (3) security (3) film (3) politics (3) academia (3) music (3) worldly (3) poem (2) network (2) android (2) wrong (2) cook (2) editor (2) physics (2) c (2) mestrado (2) php (2) pointless (1) shell (1) perl (1) audio (1) kindle (1)

Elsewhere

Quod vide


Copyright © 2012-2014 Vítor Bujés Ubatuba De Araújo
O conteúdo deste site, a menos que de outra forma especificado, pode ser utilizado livremente, com ou sem alterações, desde que seja mencionado o autor, preferivelmente com a URL do documento original.

Powered by Blognir.