Analisando o uso de memória: Java vs C ++ é insignificante?

9

Como o uso de memória de um objeto inteiro escrito em Java se compara \ com o uso de memória de um objeto inteiro escrito em C ++? A diferença é insignificante? Não faz diferença? Uma grande diferença? Eu estou supondo que é o mesmo porque um int é um int, independentemente do idioma (?)

A razão pela qual perguntei isso é porque estava lendo sobre a importância de saber quando os requisitos de memória de um programa impedirão que o programador resolva um determinado problema.

O que me fascinou é a quantidade de memória necessária para criar um único objeto Java. Tomemos, por exemplo, um objeto inteiro. Corrija-me se estiver errado, mas um objeto inteiro Java requer 24 bytes de memória:

  • 4 bytes para sua variável de instância int
  • 16 bytes de sobrecarga (referência à classe do objeto, informações de coleta de lixo e informações de sincronização)
  • 4 bytes de preenchimento

Como outro exemplo, uma matriz Java (que é implementada como um objeto) requer 48 + bytes:

  • 24 bytes de informações do cabeçalho
  • 16 bytes de sobrecarga de objeto
  • 4 bytes de comprimento
  • 4 bytes para preenchimento
  • mais a memória necessária para armazenar os valores

Como esses usos de memória se comparam ao mesmo código escrito em C ++?

Eu costumava ignorar o uso de memória dos programas C ++ e Java que escrevi, mas agora que estou começando a aprender sobre algoritmos, estou apreciando mais os recursos do computador.

Anthony
fonte
6
O que é um "objeto inteiro" em C ++? int? Nesse caso, você deve compará-lo com intJava, não Integer- desde que suas entradas C ++ sejam 32 bits.
18812 Mat
+1 Se eu criei uma classe C ++ que tinha apenas uma variável int, então instanciado-lo
Anthony
3
um int não é um int - é dependente de plataforma
11
Um C ++ com apenas um membro int normalmente não terá nenhuma sobrecarga. Ele usará exatamente o espaço que a plataforma usa para armazenar um int (normalmente 4 bytes nas plataformas de PC atuais).
quer tocar hoje
+1 para um programador Java curioso sobre memória. Mais do que qualquer outra coisa, a conscientização da memória é o fator mais importante na determinação do desempenho nas arquiteturas modernas.
precisa saber é o seguinte

Respostas:

15

Depende da plataforma e implementação.

O C ++ garante que o tamanho de charseja exatamente um byte e com pelo menos 8 bits de largura. Então, o tamanho de a short inté pelo menos 16 bits e não menor que char. O tamanho de um inté pelo menos tão grande quanto o tamanho de short int. O tamanho de long inté pelo menos 32 bits e não menor que int.

sizeof(char) == 1; sizeof(long int) >= sizeof(int) >= sizeof(short int) >= sizeof(bool) >= sizeof(char).

O modelo de memória real do C ++ é muito compacto e previsível . Por exemplo, não há metadados em objetos, matrizes ou ponteiros. Estruturas e classes são contíguas, assim como as matrizes, mas o preenchimento pode ser colocado onde necessário e necessário.

Francamente, porém, essa comparação é tola na melhor das hipóteses, pois o uso da memória Java depende mais da implementação do Java do que do código executado.

zxcdw
fonte
11
É verdade, mas, para fins de comparação, eu diria que devemos assumir tipos inteiros de tamanho equivalente (mesmo que estejam disponíveis apenas sob nomes diferentes). Afinal, tamanhos diferentes significam semânticas diferentes e, em muitas plataformas (não todas) comuns, os tamanhos são idênticos ou números inteiros do mesmo tamanho estão disponíveis sob nomes diferentes.
Nota para o OP: é melhor escolher o tamanho do número inteiro - se você deseja um int de 32 bits em C ++, pode usar int32_t.
precisa saber é o seguinte
9

A maioria das respostas parece estar ignorando alguns pontos bastante importantes.

Primeiro, em uma enorme quantidade de Java, você praticamente nunca vê uma matéria int- prima - quase todo o uso é útil Integer; portanto, o fato de que uma intpode ter (aproximadamente) o mesmo tamanho de uma intem C ou C ++ é quase irrelevante, exceto por isso ( na minha experiência, pequena) porcentagem de código que usa apenas em intvez de Integer.

Segundo, o tamanho dos objetos individuais não tem quase nada a ver com o espaço ocupado pela memória de um programa como um todo. Em Java, a pegada de memória do programa está relacionada principalmente à forma como o coletor de lixo foi ajustado. Na maioria dos casos, o GC é ajustado para maximizar a velocidade, o que (em grande parte) significa executá-lo com a menor frequência possível.

Não tenho um link à mão no momento, mas houve alguns testes mostrando que o Java pode rodar na mesma velocidade que o C, mas para isso, é necessário executar o GC raramente o suficiente para que ele use cerca de 7 vezes mais memória. Isso não ocorre porque objetos individuais são 7 vezes maiores, mas porque o GC pode ficar muito caro se você o fizer com muita frequência. Pior, o GC só pode liberar memória quando pode "provar" que não há mais nenhuma maneira de acessar um objeto, e não simplesmente quando você sabe que acabou de usá-lo. Isso significa que, mesmo que você execute o GC com mais frequência para minimizar o uso de memória, provavelmente ainda poderá planejar que um programa típico tenha um espaço de memória maior. Em um caso como esse, você pode reduzir o fator para 2 ou 3 em vez de 7. Mesmo que você exagere drasticamente, no entanto, não1 .

Dependendo da situação, há outro fator que pode ou não ser significativo: a memória ocupada pela própria JVM. Isso é mais ou menos fixo, portanto, como uma porcentagem, pode ser enorme se o aplicativo não precisar de muito armazenamento próprio ou pode ser minúsculo se o aplicativo precisar armazenar muito. Pelo menos na minha máquina, até o aplicativo Java mais trivial parece ocupar algo entre 20 e 25 megabytes (pode ser superior a 1000x para programas triviais, ou quase incomensuravelmente pequeno para grandes).


1 Isso não quer dizer que ninguém possa escrever Java com uma pegada tão próxima do que você obteria com C ++. É apenas dizer que apenas ter o mesmo número / tamanho de objetos e executar o GC com muita frequência não o levará lá como regra.

Jerry Coffin
fonte
7
Em relação ao seu primeiro ponto: eu não sou um cara Java, mas as APIs Java que eu vi nunca usaram Integer(por que usariam?) Em vez de int. Somente coleções genéricas não têm escolha a não ser usar Integerdevido ao apagamento de tipo, mas se você se importa, poderá substituí-las por uma implementação especializada intou qualquer tipo primitivo de que você precisa. E depois há boxe temporário para passar pelo código genérico de quebra automática (por exemplo, tudo o que requer um Object[]). Além disso, você tem fontes para a sobrecarga de espaço do GC? Realmente não duvido, estou apenas curioso.
9

Espero que você perceba que tudo isso é profundamente definido pela implementação, tanto para Java quanto para C ++. Dito isto, o modelo de objetos de Java requer bastante espaço.

Objetos C ++ não (geralmente) precisam qualquer armazenamento, exceto o que os membros precisam. Observe que (diferente do Java, onde tudo o que é definido pelo usuário é um tipo de referência), o código do cliente pode usar objetos como tipo de valor ou como tipos de referência, ou seja, um objeto pode armazenar um ponteiro / referência para outro objeto ou armazenar diretamente o objeto sem indireção. Um ponteiro adicional por objeto é necessário se houver algum virtualmétodo, mas várias classes úteis são projetadas para se dar bem sem polimorfismo e não precisam disso. Não há metadados do GC nem bloqueio por objeto. Assim, os class IntWrapper { int x; public: IntWrapper(int); ... };objetos não precisam de mais espaço do que o plain ints e podem ser colocados diretamente (ou seja, sem direcionamento) em coleções e outros objetos.

Matrizes são complicadas simplesmente porque não existe um equivalente comum pré-fabricado a uma Matriz Java em C ++. Você pode simplesmente alocar um monte de objetos com new[](sem custos indiretos / metadados), mas não há campo de comprimento - a implementação provavelmente armazena um, mas você não pode acessá-lo. std::vectoré uma matriz dinâmica e, portanto, possui sobrecarga adicional e uma interface maior. std::arraye matrizes no estilo C (int arr[N];), precisa de uma constante em tempo de compilação. Em teoria, deve ser apenas o armazenamento do objeto mais um único número inteiro para o comprimento - mas como você pode obter redimensionamento dinâmico e uma interface completa com muito pouco espaço extra, basta fazer isso na prática. Observe que todas essas, assim como todas as outras coleções, assumem o padrão de armazenar os objetos por valor, economizando indiretamente o espaço e as referências, além de melhorar o comportamento do cache. Você deve armazenar explicitamente ponteiros (inteligentes, por favor) para obter indireção.

As comparações acima não são inteiramente justas, porque algumas dessas economias são proporcionadas pela não inclusão de recursos que o Java inclui e seu equivalente em C ++ geralmente é menos otimizado do que o equivalente em Java (*). A maneira comum de implementar virtualem C ++ impõe exatamente tanta sobrecarga quanto a maneira comum de implementar virtualem Java. Para obter um bloqueio, você precisa de um objeto mutex com todos os recursos, que provavelmente é maior que alguns bits. Para obter a contagem de referência ( nãoequivalente ao GC e não deve ser usado como tal, mas às vezes útil), você precisa de um ponteiro inteligente que adicione um campo de contagem de referência. A menos que o objeto seja construído com cuidado, a contagem de referência, o objeto ponteiro inteligente e o objeto referenciado estejam em locais completamente separados, e mesmo quando você o constrói da maneira correta, o ponteiro compartilhado pode (deve?) Ainda ser dois ponteiros em vez de um. Por outro lado, o bom estilo C ++ não usa esses recursos o suficiente para importar - na prática, os objetos de uma biblioteca C ++ bem escrita usam menos. Isso não significa necessariamente menos uso geral de memória, mas significa que o C ++ tem uma boa vantagem nesse sentido.

(*) Por exemplo, você pode receber chamadas virtuais, códigos de hash de identidade e bloquear com apenas uma palavra para alguns objetos (e duas palavras para muitos outros objetos) mesclando as informações de tipo com vários sinalizadores e removendo os bits de bloqueio dos objetos que estão improvável que precise de bloqueios. Consulte Implementação com eficiência de espaço e tempo do Java Object Model (PDF) de David F. Bacon, Stephen J. Fink e David Grove para obter uma explicação detalhada dessa e de outras otimizações.


fonte
3

Uma planície int, em java, ocupa exatamente tanto espaço quanto uma intem C ++, desde que ambas as implementações usem o mesmo tamanho inteiro e alinhamento de memória.

Um int 'objeto' (um número inteiro in a box , ou seja, uma instância da classe Integer), carrega toda a sobrecarga de uma instância da classe em Java, portanto é significativamente maior que um intem C ++. No entanto, se você equipar um objeto em C ++ com os mesmos recursos que os objetos Java vêm prontos para uso (polimorfismo, boxe, coleta de lixo, RTTI), provavelmente terminará com um objeto igual Tamanho.

E depois há considerações de otimização; como os modelos de execução e os paradigmas de programação diferem, é improvável que qualquer problema não trivial seja resolvido da mesma forma nos dois idiomas; portanto, comparar o tamanho do armazenamento nesse nível não faz muito sentido.

Sim, os objetos Java carregam mais sobrecarga por padrão do que as classes C ++, mas eles vêm com mais recursos e isso leva a um estilo de programação diferente - um bom programador pode aproveitar as vantagens e desvantagens de qualquer linguagem.

tdammers
fonte
+1 mais sobrecarga, mas mais recursos em Java, eu entendo agora, graças
Anthony