O gerenciamento de memória na programação está se tornando uma preocupação irrelevante?


38

Antecedentes
Revisitei um site antigo (mas ótimo) que não conhecia há muito tempo - o Alioth Language Shootout ( http://benchmarksgame.alioth.debian.org/ ).

Comecei a programar em C / C ++ há vários anos, mas desde então tenho trabalhado quase exclusivamente em Java devido a restrições de linguagem nos projetos nos quais participei. Não lembrando das figuras, eu queria ver, aproximadamente, quão bem Java comparado ao C / C ++ em termos de uso de recursos.

Os tempos de execução ainda eram relativamente bons, com o Java, na pior das hipóteses, executando 4x mais lento que o C / C ++, mas em média cerca de (ou abaixo) 2x. Devido à natureza da implementação do Java, isso não foi uma surpresa, e o tempo de desempenho foi realmente menor do que o esperado.

O verdadeiro problema foi a alocação de memória - na pior das hipóteses, o Java alocado:

  • uma impressionante 52x mais memória que C
  • e 25x mais que C ++.

52x a memória ... Absolutamente desagradável, certo? ... ou é? A memória é comparativamente barata agora.

Pergunta:
Se não falamos em termos de plataformas de destino com limites estritos na memória de trabalho (isto é, sistemas embarcados e similares), o uso da memória deve ser uma preocupação ao escolher um idioma de uso geral hoje?

Estou perguntando em parte porque estou pensando em migrar para o Scala como meu idioma principal. Gosto muito dos aspectos funcionais, mas pelo que vejo, é ainda mais caro em termos de memória que o Java. No entanto, como a memória parece estar ficando mais rápida, mais barata e mais abundante a cada ano (parece cada vez mais difícil encontrar um laptop para consumidor sem pelo menos 4 GB de RAM DDR3), não se pode argumentar que o gerenciamento de recursos está se tornando cada vez mais irrelevante em comparação com (possivelmente caro em termos de implementação) recursos de linguagem de alto nível que permitem a construção mais rápida de soluções mais legíveis?


32
Não se esqueça que, apenas porque o Java aloca 52x mais memória que C para um pequeno benchmark, isso não significa que ele usará 52x mais memória para um aplicativo grande. A maior parte dessa memória será uma quantia fixa exigida pela JVM e, quanto maior o seu aplicativo, menos significativa será a parcela.
precisa saber é o seguinte

4
Se o desenvolvimento móvel é irrelevante, então sim.
91313 JeffO

3
A questão é quão ruim é o benchmark Java vs. C / C ++ e o que isso significa em termos de escolha entre as duas linguagens. Eu vejo isso como sendo no tópico, relevante para todos os programadores, claro, focado e capaz de ser respondido razoavelmente em sua forma atual. Eu votei para reabrir.
precisa saber é o seguinte

A maioria dos problemas de desempenho é causada e corrigida no nível do projeto, não no nível da ferramenta. Alguns problemas precisam de granularidade de 1ms e, portanto, requerem C / C ++. Se você tem margem de manobra, como 10ms, talvez Scala ou Java seja uma boa opção. A maioria dos controladores de entrada para jogos opera no nível de 50 a 100ms. Hoje, muitas pessoas escrevem seções críticas em um idioma e o restante de um programa em outro.
GlenPeterson

4
Ao analisar "25x mais que C ++" neste teste, é necessário levar em consideração a adição constante do tempo de execução (cerca de 13 Mb). À medida que o problema aumenta, o requisito de memória de tempo de execução se torna menor como uma porcentagem de todo o programa. Onde o uso da memória C ++ for menor que 1 MB, se você subtrair o uso da memória C ++ do uso da Memória Java, obterá um valor bastante constante.

Respostas:


34

O gerenciamento de memória é totalmente relevante, pois governa a rapidez com que algo aparece, mesmo que ele tenha muita memória. O melhor e mais canônico exemplo são os jogos com título AAA, como Call of Duty ou Bioshock. São aplicativos efetivamente em tempo real que exigem grandes quantidades de controle em termos de otimização e uso. Não é o uso em si que é o problema, mas a gestão.

Tudo se resume a duas palavras: Coleta de Lixo. Os algoritmos de Coleta de Lixo podem causar leves soluços no desempenho ou até causar o travamento do aplicativo por um ou dois segundos. Principalmente inofensivo em um aplicativo de contabilidade, mas potencialmente prejudicial em termos de experiência do usuário em um jogo de Call of Duty. Assim, em aplicativos onde o tempo importa, os idiomas coletados para o lixo podem ser extremamente problemáticos. É um dos objetivos de design do Squirrel, por exemplo, que procura remediar o problema que Lua tem com seu GC usando a contagem de referência.

É mais uma dor de cabeça? Claro, mas se você precisar de um controle preciso, você o suporta.


14
-1 "... literalmente letal em um jogo ..." - Meu trabalho diário é um sistema crítico de segurança, como na segurança da vida. O pior que acontece no software de jogos é o escritor falir porque é ruim e ninguém o compra. Essa é uma diferença que não deve ser trivializada.
mattnz

4
@mattnz Má escolha de palavras da minha parte. Foi consertado. Não era minha intenção banalizar nada.
World Engineer

19
@ Mattnz: Se você conhece jogos, ele obviamente significa que pode ser letal para o seu personagem , o que é uma afirmação completamente verdadeira.
Mason Wheeler

8
+1 porque o atendedor tem um diamante; portanto, a resposta deve estar correta.
psr

8
Os coletores de lixo em tempo real existem há muito tempo.
Jörg W Mittag

30

O ponto principal era a alocação de memória - na pior das hipóteses, o Java alocava 52x mais memória que C e 25x mais que C ++.

Você entende os números em que baseia sua pergunta?

  • Quanta memória foi alocada?
  • O que os programas estavam fazendo?

Quando há uma grande disparidade entre esses programas Java e C, é principalmente a alocação de memória JVM padrão versus o que for necessário para a libc:


  • programa Java n-body 13,996KB :: programa C 320KB :: Pascal 8KB grátis

Veja as tarefas que exigem que a memória seja alocada (ou use buffers adicionais para acumular resultados de programas com vários núcleos):

  • mandelbrot
    programa Java 67 , 880KB :: programa C 30 , 444KB


  • programa Java k-nucleotide 494 , 040KB :: programa C 153 , 452KB


  • programa Java de complemento reverso 511 , 484KB :: programa C 248 , 632KB


  • programa Java regex-dna 557 , 080KB :: programa C 289 , 088KB


  • programa em Java de árvores binárias 506 , 592KB :: programa C 99 , 448KB

... o uso da memória deve ser uma preocupação ao escolher um idioma de uso geral hoje?

Depende se o uso específico , para sua abordagem específica para resolver os problemas específicos que você precisa resolver, será restringido pelos limites específicos de memória disponível na plataforma específica que será usada.


3
Seu ponto de vista sobre os números é válido, e esse site certamente tem algumas isenções de responsabilidade em torno de seus testes. Sua resposta seria reforçada ao abordar diretamente a questão principal, que é "o uso da memória deve ser uma preocupação?"

1
Resposta excelente que recuperou uma pergunta relativamente ruim (o benchmark vagamente especificado é ainda pior que a otimização prematura :). Os dados que apóiam a análise são bem apresentados, concretos e são um excelente alimento para reflexão. Definitivamente vale uma recompensa de "resposta exemplar" .
gnat

17

Como em todas as coisas, é uma troca.

Se você estiver criando um aplicativo que será executado em uma área de trabalho de usuário único e possa razoavelmente controlar uma grande fração da RAM dessa máquina, pode valer a pena sacrificar o uso de memória pela velocidade de implementação. Se você está mirando a mesma máquina, mas está construindo um pequeno utilitário que estará competindo com vários outros aplicativos que consomem muita memória e que estão sendo executados simultaneamente, convém ter mais cuidado com essa troca. Um usuário pode se dar bem com um jogo que deseja toda a memória quando está em execução (embora, como o Engenheiro Mundial aponte, eles ' ficarão preocupados se o coletor de lixo decidir pausar a ação periodicamente para fazer uma varredura) - eles provavelmente ficarão muito menos entusiasmados se o music player que eles executam em segundo plano enquanto fazem outras coisas decidir devorar uma tonelada de memória e interfere com sua capacidade de trabalhar. Se você estiver criando um aplicativo baseado na Web, qualquer memória usada nos servidores limitará sua capacidade de expansão, forçando você a gastar mais dinheiro em mais servidores de aplicativos para oferecer suporte ao mesmo conjunto de usuários. Isso pode ter um grande impacto na economia da empresa, portanto, você deve ser muito cauteloso ao fazer essa troca. qualquer memória usada nos servidores limita sua capacidade de expansão, forçando você a gastar mais dinheiro em mais servidores de aplicativos para oferecer suporte ao mesmo conjunto de usuários. Isso pode ter um grande impacto na economia da empresa, portanto, você deve ser muito cauteloso ao fazer essa troca. qualquer memória usada nos servidores limita sua capacidade de expansão, forçando você a gastar mais dinheiro em mais servidores de aplicativos para oferecer suporte ao mesmo conjunto de usuários. Isso pode ter um grande impacto na economia da empresa, portanto, você deve ser muito cauteloso ao fazer essa troca.


8

Depende de vários fatores, especialmente a escala em que você está trabalhando.

Apenas por uma questão de argumento, vamos assumir uma diferença de 30x na memória e 2x no uso da CPU.

Se você estiver lidando com um programa interativo que ocuparia 10 megabytes de memória e 1 milissegundo de CPU se gravado em C, é praticamente inconseqüente - 300 megabytes de memória e 2 milissegundos para executar são normalmente totalmente irrelevantes em uma área de trabalho típica, e é improvável que signifique muito mesmo em um telefone ou tablet.

A diferença entre a necessidade de cerca de metade dos recursos de um servidor e a necessidade de 15 servidores é uma etapa muito maior - especialmente porque a expansão para 15 servidores provavelmente requer muito trabalho extra para se desenvolver, em vez de menos. No que diz respeito à expansão futura, os mesmos fatores que você menciona tendem a sugerir que, a menos que sua base de clientes sofra um crescimento maciço , que se ela for executada em um servidor agora, é muito provável que, quando você superar esse servidor, você capaz de substituir isso por um servidor mais novo sem nenhum problema.

O outro fator que você realmente precisa considerar é exatamente quanta diferença no custo de desenvolvimento você verá na sua tarefa específica. No momento, você está basicamente olhando para um lado de uma equação. Para ter uma boa idéia de custos x benefícios, você (obviamente) precisa olhar para os custos e benefícios, não apenas um isoladamente. A verdadeira questão é basicamente: "é x maior que y?" - mas você não pode determinar isso olhando apenas para x. Você claramente precisa olhar para você também.


2
+1 para observar a escala. Dê uma olhada neste artigo para realmente avaliar o gerenciamento de recursos em larga escala.
precisa

6

O gerenciamento de memória é absolutamente relevante no mundo de hoje. No entanto, não da maneira que você poderia esperar. Mesmo nos idiomas de coleta de lixo, você deve garantir que não tenha um vazamento de referência

Você está fazendo algo errado se este é o seu código:

static List<string> Cache;

...
Cache.Add(foo); //and then never remove anything from Cache

A coleta de lixo não pode magicamente sabe que nunca vai usar alguma referência novamente a menos que você fazê-lo de modo que você não pode usá-lo novamente, ou seja, fazendo Cache=null, você efetivamente alertar o coletor de lixo que "hey eu não vou ser capaz de acesse mais. Faça o que quiser com ele "

É mais complicado do que isso, mas os vazamentos de referência são tão, se não mais, prejudiciais que os vazamentos de memória tradicionais.

Existem também alguns lugares onde você não pode colocar um coletor de lixo. Por exemplo, o ATTiny84 é um microcontrolador com 512 bytes de código ROM e 32 bytes de RAM. Boa sorte! Isso é extremo, e provavelmente não seria programado em nada além de montagem, mas ainda assim. Outros casos, você pode ter 1M de memória. Claro, você pode instalar um coletor de lixo, mas se o processador estiver muito lento (por limitações ou para preservar a bateria), não será necessário usar um coletor de lixo, pois é muito caro rastrear o que um programador poderia saber .

Também fica significativamente mais difícil usar a coleta de lixo quando você precisa de tempos de resposta garantidos. Por exemplo, se você tem um monitor cardíaco ou algo assim e quando recebe um 1em alguma porta, você precisa garantir que pode responder a ele com um sinal adequado ou algo dentro de 10ms. Se, no meio da rotina de resposta, o coletor de lixo precisar fazer uma aprovação e acabar levando 100ms para responder, pode ser que alguém esteja morto. A coleta de lixo é muito difícil, se não impossível, de usar quando os requisitos de tempo precisam ser garantidos.

E, é claro, mesmo em hardware moderno, há alguns casos em que você precisa desses 2% extras de desempenho sem se preocupar com a sobrecarga de um coletor de lixo.


3

Como Donald Knuth disse, a otimização prematura é a raiz de todo mal. A menos que você tenha um motivo para acreditar que a memória será o gargalo, não se preocupe. E, como a lei de Moore ainda está fornecendo maior capacidade de memória (embora não tenhamos um código de thread único mais rápido), há todos os motivos para acreditar que, no futuro, estaremos ainda menos restritos à memória do que nós. são hoje.

Dito isto, se a otimização não for prematura, faça-o de qualquer maneira. Pessoalmente, estou trabalhando em um projeto no momento em que entendo detalhadamente o uso da memória, preciso de um controle preciso e uma varredura de lixo me mataria. Portanto, estou fazendo este projeto em C ++. Mas essa escolha parece ser um evento uma vez a cada vários anos para mim. (Esperamos que em algumas semanas eu não toque em C ++ novamente por mais alguns anos.)


4
Essa é a maneira como acabamos com o software corporativo inchado em computadores incrivelmente lentos que mantêm a paginação. Todo mundo diz 'Claro que meu aplicativo ocupa mais memória, mas quem se importa, é praticamente grátis!' e você acaba com uma pilha completa de aplicativos que consomem muita memória que fazem com que uma máquina com 4 GB de RAM seja mais lenta do que uma máquina com 512 MB de RAM há 10 anos.
MrFox 7/03

@ MrFox Na verdade, o problema com o software corporativo é que as pessoas que decidem usá-lo não são as que sofrem com ele. Consulte lists.canonical.org/pipermail/kragen-tol/2005-April/000772.html para obter uma excelente descrição do motivo pelo qual está quebrado. Quanto ao resto, você sentiu falta de salientar que às vezes é necessário se preocupar com o uso da memória?
btilly

3

Para as pessoas que lidam com o gerenciamento de memória de "big data" ainda é um grande problema. Programas em astronomia, física, bioinformática, aprendizado de máquina, etc., todos têm que lidar com conjuntos de dados de vários gigabytes, e os programas rodam muito mais rapidamente se as partes relevantes podem ser mantidas na memória. Mesmo rodando em uma máquina com 128 GB de RAM não resolve o problema.

Há também a questão de tirar proveito da GPU, embora talvez você a classifique como um sistema incorporado. A maior parte do pensamento difícil sobre o uso de CUDA ou OpenCL se resume a problemas de gerenciamento de memória na transferência de dados da memória principal para a memória da GPU.


1

Para ser justo, muitos Java por aí se entregam a alguns padrões verdadeiramente explosivos e sem sentido de classe que apenas matam desempenho e memória de porco, mas eu me pergunto quanto dessa memória é apenas a JVM que, em teoria (heh), você executa o mesmo aplicativo em vários ambientes sem precisar reescrever completamente os novos. Portanto, a questão do tradeoff de design se resume a: "Quanto da memória dos usuários vale uma vantagem de desenvolvimento para você?"

Isto é, a IMO é uma compensação perfeitamente válida e razoável a considerar. O que me irrita é a noção de que, como os PCs modernos são tão poderosos e a memória é tão barata, podemos ignorar completamente essas preocupações e recursos incômodos e códigos inchados e ter preguiça de fazer escolhas a ponto de parecer um monte de coisas Eu faço em um PC com Windows agora, leva apenas o tempo que fez no Windows '95. Sério, Word? Quanta porcaria nova que 80% de sua base de usuários realmente precisa poderia ter acrescentado em 18 anos? Certeza que tivemos pré-janelas de verificação ortográfica, certo? Mas estávamos conversando sobre memória que não é necessariamente rápida, se você tem bastante, por isso discordo.

Mas é claro que, se você puder fazer o aplicativo em 2 semanas, custando talvez alguns megabytes extras em vez de 2 anos para obter a versão de apenas um K, é necessário considerar como alguns megas se comparam a ( Acho) 4-12 shows na máquina de usuários comuns antes de zombar da idéia de ser tão desleixado.

Mas o que isso tem a ver com Scala além da questão da troca? Só porque é uma coleta de lixo, não significa que você não deve sempre tentar pensar no fluxo de dados em termos do que há em escopos e fechamentos, e se deve ser deixado parado ou usado de forma que seja possível. desalocado pelo GC quando não for mais necessário. Isso é algo que até nós, desenvolvedores da Web de interface do usuário do JavaScript, tivemos que pensar e, esperamos, continuará a se espalhar para outros domínios problemáticos, como o câncer mais experiente (que todos vocês deveriam ter matado com Flash ou Applets ou algo que tivesse chance) que nós somos.


0

O gerenciamento de memória na programação está se tornando uma preocupação irrelevante?

Gerenciamento de memória (ou controle) é realmente o principal motivo pelo qual estou usando C e C ++.

A memória é comparativamente barata agora.

Memória não rápida. Ainda estamos analisando um pequeno número de registros, algo como cache de dados de 32 KB para L1 no i7, 256 KB para L2 e 2 MB para L3 / core. Dito isto:

Se não falamos em termos de plataformas de destino com limites estritos de memória de trabalho (isto é, sistemas embarcados e similares), o uso de memória deve ser uma preocupação ao escolher hoje uma linguagem de uso geral?

Uso de memória em um nível geral, talvez não. Sou um pouco impraticável, pois não gosto da idéia de um bloco de notas que consiga, digamos, 50 megabytes de DRAM e centenas de megabytes de espaço em disco rígido, mesmo que eu tenha isso para poupar e abundar mais. Eu já estou aqui há muito tempo e me parece estranho e meio nojento ver um aplicativo tão simples consumir tanta memória quanto o que deveria ser possível com kilobytes. Dito isto, talvez eu consiga viver comigo mesmo se encontrar algo assim, se ainda for agradável e receptivo.

A razão pela qual o gerenciamento de memória é importante para mim no meu campo é não reduzir tanto o uso de memória em geral. Centenas de megabytes de uso de memória não necessariamente diminuem a velocidade do aplicativo de forma não trivial se nada for acessado com frequência (por exemplo, apenas com o clique de um botão ou outra forma de entrada do usuário, que é extremamente pouco frequente, a menos que você estão falando de jogadores coreanos de Starcraft que podem clicar em um botão um milhão de vezes por segundo).

A razão pela qual é importante no meu campo é manter a memória estreita e próxima, que é acessada com muita frequência (por exemplo, sendo repetida em cada quadro) nesses caminhos críticos. Não queremos que o cache seja perdido toda vez que acessamos apenas um dentre um milhão de elementos que precisam ser acessados ​​em loop a cada quadro. Quando movemos a memória para baixo na hierarquia da memória lenta para a memória rápida em grandes blocos, digamos linhas de cache de 64 bytes, é realmente útil que esses 64 bytes contenham dados relevantes, se podemos encaixar vários elementos de valor nesses 64 bytes e se nossos padrões de acesso são tais que usamos tudo antes dos dados serem despejados.

Esses dados acessados ​​com freqüência para os milhões de elementos podem abranger apenas 20 megabytes, mesmo que tenhamos gigabytes. Ele ainda faz um mundo de diferença nas taxas de quadros em loop sobre esses dados, todos os quadros desenhados se a memória estiver estreita e fechada para minimizar falhas de cache, e é aí que o gerenciamento / controle de memória é tão útil. Exemplo visual simples em uma esfera com alguns milhões de vértices:

insira a descrição da imagem aqui

A descrição acima é realmente mais lenta que a minha versão mutável, uma vez que está testando uma representação persistente de uma estrutura de dados de uma malha, mas com isso de lado, eu costumava lutar para atingir essas taxas de quadros mesmo com metade desses dados (é certo que o hardware ficou mais rápido desde minhas lutas) ) porque não entendi como minimizar perdas de cache e uso de memória para dados de malha. As malhas são algumas das estruturas de dados mais complicadas com as quais lidei a esse respeito, porque armazenam tantos dados interdependentes que precisam permanecer sincronizados, como polígonos, arestas, vértices, tantos mapas de textura quanto o usuário deseja anexar, pesos ósseos, mapas de cores, conjuntos de seleção, alvos de metamorfose, pesos das arestas, materiais poligonais, etc. etc. etc.

Projetei e implementei vários sistemas de malha nas últimas duas décadas e a velocidade deles era muitas vezes proporcional ao uso de memória. Embora eu esteja trabalhando com muito mais memória do que quando comecei, meus novos sistemas de malha são 10 vezes mais rápidos que meu primeiro design (quase 20 anos atrás) e em grande parte porque usam cerca de 1/10 de a memória. A versão mais recente ainda usa compactação indexada para compor o máximo de dados possível e, apesar da sobrecarga de processamento da descompactação, a compactação melhorou o desempenho porque, novamente, temos tão pouca memória rápida preciosa. Agora posso ajustar um milhão de malhas poligonais com coordenadas de textura, vincagem de bordas, atribuições de material etc., juntamente com um índice espacial para ele em cerca de 30 megabytes.

Aqui está o protótipo mutável com mais de 8 milhões de quadrângulos e um esquema de subdivisão de multires em um i3 com uma GF 8400 (isso foi há alguns anos). É mais rápido que minha versão imutável, mas não é usado na produção, pois achei a versão imutável muito mais fácil de manter e o impacto no desempenho não é tão ruim. Observe que a estrutura de arame não indica facetas, mas patches (os fios são realmente curvas, caso contrário, toda a malha seria preta sólida), embora todos os pontos de uma faceta sejam modificados pelo pincel.

insira a descrição da imagem aqui

Enfim, eu só queria mostrar um pouco disso acima para mostrar alguns exemplos e áreas concretos em que o gerenciamento de memória é tão útil e também espero que as pessoas não pensem que eu estou apenas falando do meu rabo. Costumo ficar um pouco irritado quando as pessoas dizem que a memória é tão abundante e barata, porque isso é falar de memória lenta, como DRAM e discos rígidos. Ainda é tão pequeno e precioso quando falamos de memória rápida, e o desempenho de caminhos genuinamente críticos (por exemplo, caso comum, não para tudo) está relacionado à reprodução dessa pequena quantidade de memória rápida e à utilização da maneira mais eficaz possível. .

Para esse tipo de coisa, é realmente útil trabalhar com uma linguagem que permita projetar objetos de alto nível como C ++, por exemplo, enquanto ainda é possível armazenar esses objetos em uma ou mais matrizes contíguas, com a garantia de que a memória do todos esses objetos serão representados contiguamente e sem sobrecarga desnecessária de memória por objeto (por exemplo: nem todos os objetos precisam de reflexão ou despacho virtual). Quando você realmente passa por essas áreas críticas de desempenho, torna-se um aumento de produtividade ter esse controle de memória, digamos, brincando com conjuntos de objetos e usando tipos de dados primitivos para evitar sobrecarga de objeto, custos de GC e manter a memória acessada com frequência. juntos contíguos.

Portanto, o gerenciamento / controle da memória (ou a falta dela) é realmente uma razão dominante no meu caso para escolher qual idioma de maneira mais produtiva me permite resolver os problemas. Definitivamente, escrevo minha parte de código que não é crítica para o desempenho e, por isso, costumo usar Lua, que é muito fácil de incorporar a partir de C.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.