scala vs java, desempenho e memória? [fechadas]

160

Estou interessado em analisar o Scala e tenho uma pergunta básica para a qual não consigo encontrar uma resposta: em geral, há uma diferença no desempenho e no uso da memória entre o Scala e o Java?

JohnSmith
fonte
3
Ouvi afirmações de que o desempenho pode ser muito próximo. Eu suspeito que é altamente dependente do que você está fazendo. (como é para Java vs C)
Peter Lawrey
A resposta para esse tipo de pergunta é "depende" - para praticamente qualquer comparação entre o sistema X e o sistema Y. Além disso, esta é uma duplicata do stackoverflow.com/questions/2479819/…
James Moore

Respostas:

261

O Scala facilita muito o uso de enormes quantidades de memória sem perceber. Isso geralmente é muito poderoso, mas ocasionalmente pode ser irritante. Por exemplo, suponha que você tenha uma matriz de cadeias (chamada array) e um mapa dessas cadeias para arquivos (chamados mapping). Suponha que você deseja obter todos os arquivos que estão no mapa e vêm de cadeias de comprimento maiores que dois. Em Java, você pode

int n = 0;
for (String s: array) {
  if (s.length > 2 && mapping.containsKey(s)) n++;
}
String[] bigEnough = new String[n];
n = 0;
for (String s: array) {
  if (s.length <= 2) continue;
  bigEnough[n++] = map.get(s);
}

Uau! Trabalho duro. No Scala, a maneira mais compacta de fazer a mesma coisa é:

val bigEnough = array.filter(_.length > 2).flatMap(mapping.get)

Fácil! Mas, a menos que você esteja familiarizado com o funcionamento das coleções, o que você talvez não perceba é que essa maneira de fazer isso criou uma matriz intermediária extra (com filter) e um objeto extra para cada elemento da matriz (com mapping.get, que retorna uma opção). Ele também cria dois objetos de função (um para o filtro e outro para o flatMap), embora isso raramente seja um problema importante, pois os objetos de função são pequenos.

Então, basicamente, o uso da memória é, em um nível primitivo, o mesmo. Mas as bibliotecas do Scala têm muitos métodos poderosos que permitem criar um grande número de objetos (geralmente de curta duração) com muita facilidade. O coletor de lixo geralmente é muito bom com esse tipo de lixo, mas se você ficar completamente alheio à memória que está sendo usada, provavelmente terá problemas mais cedo no Scala do que no Java.

Observe que o código do Computer Languages ​​Benchmark Game Scala é escrito em um estilo semelhante ao Java para obter desempenho semelhante ao Java e, portanto, possui uso de memória semelhante ao Java. Você pode fazer isso no Scala: se você escrever seu código para parecer com código Java de alto desempenho, será um código Scala de alto desempenho. (Você pode escrevê-lo em um estilo Scala mais idiomático e ainda obter um bom desempenho, mas isso depende das especificidades.)

Devo acrescentar que, por quantidade de tempo gasto em programação, meu código Scala geralmente é mais rápido que meu código Java, pois no Scala eu posso realizar as partes tediosas que não são críticas para o desempenho com menos esforço e gastar mais atenção otimizando os algoritmos e código para as partes críticas de desempenho.

Rex Kerr
fonte
172
+1 para esse parágrafo final. É um ponto vital que é deixado fora de consideração muito muita freqüência.
Kevin Wright
2
Eu pensei que as opiniões poderiam ajudar muito com os problemas mencionados. Ou não é verdade com matrizes, especificamente?
Nicolas Payette 5/05
1
@ Kevin Wright - "É um ponto vital que é deixado de lado com demasiada frequência" - é algo fácil de dizer e difícil de demonstrar, e nos diz algo sobre as habilidades de Rex Kerr, e não o que outros menos qualificados alcançam.
Igouy
1
>> no estilo idiomático com desempenho superlativo << Há espaço no jogo de benchmarks para programas Scala idiomáticos que não alcançam desempenho "superlativo".
Igouy
2
@RexKerr - seu exemplo de Java não pesquisa a chave de mapeamento duas vezes para cada String possível, onde o seu exemplo Scala somente uma vez após a seleção das Strings? Ou seja, eles são otimizados de diferentes maneiras para diferentes conjuntos de dados?
Seth
103

Como sou um usuário novo, não posso adicionar um comentário à resposta de Rex Kerr acima (permitir que novos usuários "respondam", mas não "comentem" é uma regra muito estranha).

Eu me inscrevi simplesmente para responder à insinuação "ufa, Java é tão detalhado e tão árduo" da resposta popular de Rex acima. Embora você possa, naturalmente, escrever um código Scala mais conciso, o exemplo de Java dado é claramente inchado. A maioria dos desenvolvedores Java codificaria algo assim:

List<String> bigEnough = new ArrayList<String>();
for(String s : array) {
  if(s.length() > 2 && mapping.get(s) != null) {
    bigEnough.add(mapping.get(s));
  }
}

E, é claro, se vamos fingir que o Eclipse não faz a maior parte da digitação real para você e que cada caractere salvo realmente o torna um programador melhor, então você pode codificar isso:

List b=new ArrayList();
for(String s:array)
  if(s.length()>2 && mapping.get(s) != null) b.add(mapping.get(s));

Agora, não apenas economizei o tempo que levei para digitar nomes de variáveis ​​completos e chaves (me liberando para gastar mais 5 segundos para pensar em pensamentos algorítmicos profundos), mas também posso inserir meu código em concursos de ofuscação e potencialmente ganhar dinheiro extra por os feriados.

Não está dormindo
fonte
7
Por que você não é um membro do clube "língua do quadril do mês"? Bons comentários. Gostei particularmente de ler o último parágrafo.
Stepanian
21
Soberbamente posto! Fico cansado de exemplos planejados, nos quais o código Java inflado é seguido por um exemplo conciso e cuidadosamente construído de Scala (ou outra linguagem FP) e, em seguida, uma conclusão apressada de que o Scala deve ser melhor que o Java por causa disso. Quem já escreveu algo significativo em Scala de qualquer maneira! ;-) E não diga Twitter ...
chrisjleu
2
Bem, a solução da Rex pré-aloca a memória para a matriz, o que tornará o código compilado mais rápido (porque com sua abordagem, você permite que a JVM realoque periodicamente sua matriz conforme ela cresce). Embora houvesse mais digitação envolvida, em termos de desempenho, poderia ser um vencedor.
Ashalynd
5
enquanto nós para ele, em java8 será:Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
bennyl
2
O que torna o Scala "melhor" em alguns aspectos do que o Java são os recursos expandidos do sistema de tipos que facilitam a expressão de padrões mais genéricos como tipos (como Mônadas, Functors etc.). Isso permite criar tipos que não atrapalham devido a contratos excessivamente rígidos, como costuma acontecer em Java. Contratos estritos, não baseados em padrões reais no código, são a razão pela qual os padrões de Inversão de Responsabilidade são necessários apenas para testar adequadamente seu código (a Injeção de Dependência vem à mente primeiro e o Inferno XML que ele traz). O addl. concisão que a flexibilidade traz é apenas um bônus.
Josias
67

Escreva seu Scala como Java, e você pode esperar que um bytecode quase idêntico seja emitido - com métricas quase idênticas.

Escreva de forma mais "linguística", com objetos imutáveis ​​e funções de ordem superior, e será um pouco mais lento e um pouco maior. A única exceção a essa regra geral é ao usar objetos genéricos nos quais os parâmetros de tipo usam a @specialisedanotação, isso criará um bytecode ainda maior que pode superar o desempenho do Java, evitando boxe / unboxing.

Também vale mencionar o fato de que mais memória / menos velocidade é uma troca inevitável ao escrever código que pode ser executado em paralelo. O código Idiomatic Scala é de natureza muito mais declarativa do que o código Java típico e geralmente fica a apenas 4 caracteres ( .par) de ser totalmente paralelo.

Então se

  • O código Scala leva 1,25x mais tempo que o código Java em um único encadeamento
  • Ele pode ser facilmente dividido em 4 núcleos (agora comuns mesmo em laptops)
  • para um tempo de execução paralelo de (1,24 / 4 =) 0,3125x o Java original

Você diria que o código Scala agora é comparativamente 25% mais lento ou 3x mais rápido?

A resposta correta depende exatamente de como você define "desempenho" :)

Kevin Wright
fonte
4
Aliás, você pode mencionar que .parestá em 2.9.
Rex Kerr #
26
>> Você diria que o código Scala agora é comparativamente 25% mais lento ou 3x mais rápido? << Eu diria por que sua comparação hipotética não é com o código Java multiencadeado?
Igouy
17
@igouy - O ponto é que o referido código hipotético não existe, a natureza imperativa do código Java "mais rápido" dificulta a paralelização, de modo que a relação custo / benefício significa que é improvável que isso aconteça. A Scala Idiomatic, por outro lado, sendo muito mais declarativa por natureza, pode frequentemente tornar-se concorrente com não mais do que uma mudança trivial.
Kevin Wright
7
A existência de programas Java simultâneos não implica que um programa Java típico possa ser facilmente adaptado à simultaneidade. De qualquer forma, eu diria que esse estilo particular de junção de forquilha é particularmente raro em Java e deve ser explicitamente codificado, enquanto operações simples como encontrar o valor mínimo contido ou a soma de valores em uma coleção podem ser trivialmente paralelas no Scala usando simplesmente .par.
Kevin Wright
5
Não, não posso. Esse tipo de coisa é um alicerce fundamental para muitos algoritmos e vê-lo presente em um nível tão baixo na linguagem e nas bibliotecas padrão (as mesmas bibliotecas padrão que todos os programas usarão, e não apenas as típicas) é evidência de que você ' já está mais perto de ser concorrente simplesmente escolhendo o idioma. Por exemplo, o mapeamento sobre uma coleção é inerentemente adequado à paralelização, e o número de programas Scala que não usam o mapmétodo será muito pequeno.
Kevin Wright
31

Jogo de benchmarks de linguagem de computador:

Teste de velocidade java / scala 1.71 / 2.25

Teste de memória java / scala 66.55 / 80.81

Portanto, esses benchmarks dizem que o java é 24% mais rápido e o scala usa 21% mais memória.

No geral, não é grande coisa e não deve importar em aplicativos do mundo real, onde a maior parte do tempo é consumida por banco de dados e rede.

Conclusão: Se o Scala torna você e sua equipe (e as pessoas que assumem o projeto quando você sai) mais produtivos, deve tentar.

Peter Knego
fonte
34
O tamanho do código java / scala 3,39 / 2,21
Hammar
22
Tenha cuidado com números como esses, eles soam muito precisos, enquanto na realidade eles significam quase nada. Não é como se Scala é sempre 24% mais rápido do que Java, em média, etc.
Jesper
3
Os números citados por Afaik indicam o contrário: o Java é 24% mais rápido que o scala. Mas como você diz - são marcas de micropigmentação, que não precisam coincidir com o que está acontecendo em aplicativos reais. E a maneira diferente ou a solução de problemas em diferentes idiomas pode levar a programas menos comparáveis ​​no final.
usuário desconhecido
9
"Se Scala faz você e sua equipe ..." Bottom line: Você sabe que depois não antes :-)
igouy
A página de Ajuda do jogo de benchmarks fornece um exemplo de como "Comparar a velocidade e o tamanho do programa para implementações em 2 idiomas". Para Scala e Java a página de comparação web apropriada é - shootout.alioth.debian.org/u64q/scala.php
igouy
20

Outros responderam a essa pergunta com relação a loops apertados, embora pareça haver uma óbvia diferença de desempenho entre os exemplos de Rex Kerr que eu comentei.

Essa resposta é realmente direcionada a pessoas que possam investigar a necessidade de otimização de loop apertado como falha de design.

Eu sou relativamente novo no Scala (cerca de um ano ou mais), mas a sensação até agora é que ele permite adiar muitos aspectos do design, implementação e execução com relativa facilidade (com leitura e experimentação suficientes em segundo plano :)

Recursos de design diferido:

Recursos de implementação adiada:

Recursos de execução adiada: (desculpe, sem links)

  • Valores preguiçosos seguros para threads
  • Pass-by-name
  • Coisas monádicas

Esses recursos, para mim, são os que nos ajudam a trilhar o caminho para aplicativos rápidos e restritos.


Os exemplos de Rex Kerr diferem em quais aspectos da execução são adiados. No exemplo de Java, a alocação de memória é adiada até que seu tamanho seja calculado, onde o exemplo Scala adia a pesquisa de mapeamento. Para mim, eles parecem algoritmos completamente diferentes.

Aqui está o que eu acho que é mais equivalente a maçãs com maçãs para o seu exemplo de Java:

val bigEnough = array.collect({
    case k: String if k.length > 2 && mapping.contains(k) => mapping(k)
})

Nenhuma coleção intermediários, há Optioncasos etc. Isto também preserva o tipo de coleção para bigEnough's tipo é Array[File]- Array' s collectimplementação provavelmente será fazer algo ao longo das linhas do que o código Java do Sr. Kerr faz.

Os recursos de design diferido listados acima também permitiriam que os desenvolvedores da API de coleção da Scala implementassem essa rápida implementação específica de coleta de Array em versões futuras sem interromper a API. É a isso que estou me referindo ao trilhar o caminho da velocidade.

Além disso:

val bigEnough = array.withFilter(_.length > 2).flatMap(mapping.get)

O withFiltermétodo que eu usei aqui em vez de filtercorrige o problema de coleção intermediária, mas ainda existe o problema da instância Option.


Um exemplo de velocidade de execução simples no Scala é com o log.

Em Java, podemos escrever algo como:

if (logger.isDebugEnabled())
    logger.debug("trace");

Em Scala, isso é apenas:

logger.debug("trace")

porque o parâmetro de mensagem para depuração no Scala tem o tipo " => String" que eu considero uma função sem parâmetro que é executada quando é avaliada, mas que a documentação chama de pass-by-name.

EDIT {Funções no Scala são objetos, então há um objeto extra aqui. Para o meu trabalho, vale a pena o peso de um objeto trivial, removendo a possibilidade de uma mensagem de log ser avaliada desnecessariamente. }

Isso não torna o código mais rápido, mas aumenta a probabilidade de ser mais rápido e é menos provável que tenhamos a experiência de analisar e limpar o código de outras pessoas em massa.

Para mim, esse é um tema consistente no Scala.


O código rígido falha ao capturar o motivo pelo qual o Scala é mais rápido, apesar de sugerir um pouco.

Eu sinto que é uma combinação de reutilização de código e o limite máximo de qualidade de código no Scala.

Em Java, o código impressionante geralmente é forçado a se tornar uma bagunça incompreensível e, portanto, não é realmente viável nas APIs de qualidade de produção, pois a maioria dos programadores não seria capaz de usá-lo.

Tenho grandes esperanças de que o Scala permita que os einsteins entre nós implementem APIs muito mais competentes, potencialmente expressas por meio de DSLs. As principais APIs do Scala já estão distantes nesse caminho.

Seth
fonte
Seu material de registro é um bom exemplo para as armadilhas de desempenho do Scala: logger.debug ("trace") cria um novo objeto para a função sem parâmetros.
jcsahnwaldt Restabelecer Monica
De fato - como isso afeta meu ponto associado?
Seth
Os objetos mencionados acima também podem ser usados ​​para criar estruturas de controle de IoC transparentes por uma questão de eficiência. Sim, o mesmo resultado é teoricamente possível em Java, mas seria algo que afetou / ofuscou dramaticamente a maneira como o código é escrito - portanto, meu argumento de que a habilidade de Scala em adiar muitos elementos do desenvolvimento de software nos ajuda a avançar em direção a códigos mais rápidos - com maior probabilidade de serem mais rápido na prática versus desempenho marginalmente mais rápido da unidade.
Seth
Ok, reli isso e escrevi, "velocidade de execução simples" - adicionarei uma nota. Bom ponto :)
Seth
3
Previsível se declaração (basicamente livre em um processador superescalar) vs alocação de objetos + lixo. O código Java é obviamente mais rápido (observe que apenas avalia a condição, a execução não alcançará a instrução de log.) Em resposta a "Para o meu trabalho, vale a pena o peso de um objeto trivial, removendo a possibilidade de uma mensagem de log ser avaliada desnecessariamente . "
Eloff
10

Java e Scala são compilados no bytecode da JVM, portanto a diferença não é tão grande. A melhor comparação que você pode obter é provavelmente no jogo de benchmarks de linguagem de computador , que diz essencialmente que Java e Scala têm o mesmo uso de memória. O Scala é apenas um pouco mais lento que o Java em alguns dos benchmarks listados, mas isso pode ser simplesmente porque a implementação dos programas é diferente.

Na verdade, eles são tão próximos que não vale a pena se preocupar. O aumento de produtividade que você obtém ao usar uma linguagem mais expressiva como o Scala vale muito mais do que um desempenho mínimo (se houver) atingido.

ryeguy
fonte
7
Eu vejo uma falácia lógica aqui: ambas as linguagens são compiladas até o bytecode, mas um programador experiente e um novato - seu código também é compilado no bytecode - mas não no mesmo bytecode, então a conclusão é que a diferença não pode ser tão grande , pode estar errado. De fato, antigamente, um loop while poderia ser muito, muito mais rápido na escala do que um loop for semanticamente equivalente (se bem me lembro, hoje está muito melhor). E ambos foram compilados no bytecode, é claro.
usuário desconhecido
@user unknown - "um loop while pode ser muito, muito mais rápido no scala do que um loop for semanticamente equivalente" - observe que esses programas de jogos de referência do Scala são gravados com loops while.
Igouy
@igouy: Eu não falei sobre os resultados desta marca de microbench, mas sobre a argumentação. Uma afirmação verdadeira Java and Scala both compile down to JVM bytecode, que foi combinada com soa à afirmação em questão que diffence isn't that big.eu queria mostrar, que soé apenas um truque retórico, e não uma conclusão argumentativa.
usuário desconhecido
3
resposta surpreendentemente incorreta com votos surpreendentemente altos.
shabunc
4

O exemplo de Java não é realmente um idioma para programas aplicativos típicos. Esse código otimizado pode ser encontrado em um método de biblioteca do sistema. Mas então ele usaria uma matriz do tipo certo, ou seja, File [] e não lançaria uma IndexOutOfBoundsException. (Diferentes condições de filtro para contar e adicionar). Minha versão seria (sempre (!) Com chaves, porque eu não gosto de passar uma hora pesquisando um bug que foi introduzido ao salvar os 2 segundos para pressionar uma única tecla no Eclipse):

List<File> bigEnough = new ArrayList<File>();
for(String s : array) {
  if(s.length() > 2) {
    File file = mapping.get(s);
    if (file != null) {
      bigEnough.add(file);
    }
  }
}

Mas eu poderia trazer muitos outros exemplos feios de código Java do meu projeto atual. Tentei evitar a cópia comum e modificar o estilo de codificação fatorando estruturas e comportamentos comuns.

Na minha classe base abstrata do DAO, tenho uma classe interna abstrata para o mecanismo de cache comum. Para cada tipo de objeto de modelo concreto, há uma subclasse da classe base abstrata do DAO, na qual a classe interna é subclassificada para fornecer uma implementação para o método que cria o objeto de negócios quando ele é carregado no banco de dados. (Não podemos usar uma ferramenta ORM porque acessamos outro sistema por meio de uma API proprietária.)

Esse código de subclassificação e instanciação não é nada claro em Java e seria muito legível no Scala.

MickH
fonte