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?
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?
Respostas:
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.
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.
Arrays.stream(array).map(mapping::get).filter(x->x!=null).toArray(File[]::new);
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 @specialised
anotaçã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
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" :)
.par
está em 2.9.
.par
.
map
método será muito pequeno.
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.
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)
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á Option
casos etc. Isto também preserva o tipo de coleção para bigEnough
's tipo é Array[File]
- Array
' s collect
implementaçã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 withFilter
método que eu usei aqui em vez de filter
corrige 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.
Apresentação do @higherkinded sobre o assunto - Scala Performance Considerations, que faz algumas comparações Java / Scala.
Ferramentas:
Ótimo post:
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.
Java and Scala both compile down to JVM bytecode,
que foi combinada com so
a à 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.
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.