Antes de mais, gostaria de deixar claro que essa não é uma questão de linguagem X versus linguagem Y para determinar qual é a melhor.
Uso Java há muito tempo e pretendo continuar usando-o. Paralelamente, atualmente estou aprendendo Scala com grande interesse: além de pequenas coisas que levam algum tempo para me acostumar com a minha impressão, eu posso realmente trabalhar muito bem nesse idioma.
Minha pergunta é: como o software escrito em Scala se compara ao software escrito em Java em termos de velocidade de execução e consumo de memória? Obviamente, essa é uma pergunta difícil de responder em geral, mas eu esperaria que construções de nível superior, como correspondência de padrões, funções de ordem superior, etc., introduzissem alguma sobrecarga.
No entanto, minha experiência atual no Scala é limitada a pequenos exemplos com menos de 50 linhas de código e, até o momento, não executei nenhum benchmark. Então, eu não tenho dados reais.
Se o Scala tiver algum Java sobrecarregado, faz sentido misturar projetos Scala / Java, onde se codifica as partes mais complexas do Scala e as partes críticas de desempenho em Java? Isto é uma prática comum?
EDIT 1
Eu executei uma pequena referência: crie uma lista de números inteiros, multiplique cada número inteiro por dois e coloque-o em uma nova lista, imprima a lista resultante. Eu escrevi uma implementação Java (Java 6) e uma implementação Scala (Scala 2.9). Eu executei ambos no Eclipse Indigo no Ubuntu 10.04.
Os resultados são comparáveis: 480 ms para Java e 493 ms para Scala (média de mais de 100 iterações). Aqui estão os trechos que eu usei.
// Java
public static void main(String[] args)
{
long total = 0;
final int maxCount = 100;
for (int count = 0; count < maxCount; count++)
{
final long t1 = System.currentTimeMillis();
final int max = 20000;
final List<Integer> list = new ArrayList<Integer>();
for (int index = 1; index <= max; index++)
{
list.add(index);
}
final List<Integer> doub = new ArrayList<Integer>();
for (Integer value : list)
{
doub.add(value * 2);
}
for (Integer value : doub)
{
System.out.println(value);
}
final long t2 = System.currentTimeMillis();
System.out.println("Elapsed milliseconds: " + (t2 - t1));
total += t2 - t1;
}
System.out.println("Average milliseconds: " + (total / maxCount));
}
// Scala
def main(args: Array[String])
{
var total: Long = 0
val maxCount = 100
for (i <- 1 to maxCount)
{
val t1 = System.currentTimeMillis()
val list = (1 to 20000) toList
val doub = list map { n: Int => 2 * n }
doub foreach ( println )
val t2 = System.currentTimeMillis()
println("Elapsed milliseconds: " + (t2 - t1))
total = total + (t2 - t1)
}
println("Average milliseconds: " + (total / maxCount))
}
Portanto, neste caso, parece que a sobrecarga do Scala (usando range, map, lambda) é realmente mínima, o que não está longe das informações fornecidas pelo World Engineer.
Talvez haja outras construções Scala que devam ser usadas com cuidado, porque são particularmente difíceis de executar?
EDIT 2
Alguns de vocês apontaram que as impressões nos loops internos ocupam a maior parte do tempo de execução. Eu os removi e defina o tamanho das listas como 100000 em vez de 20000. A média resultante foi de 88 ms para Java e 49 ms para Scala.
fonte
Respostas:
Há uma coisa que você pode fazer de forma concisa e eficiente em Java que não pode no Scala: enumerações. Para todo o resto, mesmo para construções que são lentas na biblioteca do Scala, você pode obter versões eficientes trabalhando no Scala.
Portanto, na maioria das vezes, você não precisa adicionar Java ao seu código. Mesmo para o código que usa enumeração em Java, geralmente há uma solução adequada ou boa no Scala - eu coloco a exceção em enumerações que possuem métodos extras e cujos valores int constantes são usados.
Quanto ao que observar, aqui estão algumas coisas.
Se você usar o padrão enriquecer minha biblioteca, sempre converta para uma classe. Por exemplo:
Seja cauteloso com os métodos de coleta - porque eles são polimórficos na maior parte, a JVM não os otimiza. Você não precisa evitá-los, mas preste atenção nas seções críticas. Esteja ciente de que o
for
Scala é implementado por meio de chamadas de método e classes anônimas.Se estiver usando uma classe Java, como
String
,Array
ouAnyVal
classes que correspondem às primitivas Java, prefira os métodos fornecidos por Java quando existirem alternativas. Por exemplo, uselength
emString
e emArray
vez desize
.Evite o uso descuidado de conversões implícitas, pois você pode usar conversões por engano e não por design.
Estender classes em vez de características. Por exemplo, se você estiver estendendo
Function1
, estendaAbstractFunction1
.Use
-optimise
e especialização para obter o máximo do Scala.Entenda o que está acontecendo:
javap
é seu amigo e um monte de bandeiras Scala que mostram o que está acontecendo.Os idiomas Scala são projetados para melhorar a correção e tornar o código mais conciso e sustentável. Eles não foram projetados para velocidade, portanto, se você precisar usá-lo em
null
vez deOption
um caminho crítico, faça isso! Há uma razão pela qual Scala é multiparadigma.Lembre-se de que a verdadeira medida de desempenho está executando o código. Veja esta pergunta para um exemplo do que pode acontecer se você ignorar essa regra.
fonte
De acordo com o Benchmarks Game para um sistema de núcleo único e 32 bits, o Scala está em uma mediana 80% mais rápida que Java. O desempenho é aproximadamente o mesmo para um computador Quad Core x64. Até o uso da memória e a densidade do código são muito semelhantes na maioria dos casos. Eu diria, com base nessas análises (não científicas), que você está correto ao afirmar que o Scala adiciona alguma sobrecarga ao Java. Parece não adicionar toneladas de sobrecarga, então eu suspeitaria que o diagnóstico de itens de ordem superior ocupando mais espaço / tempo seja o mais correto.
fonte
Int
,Char
, etc. quando pode. Enquanto loops são igualmente eficientes no Scala.Function
classes. Se você passar um lambda paramap
, a classe anônima precisará ser instanciada (e alguns locais podem precisar ser passados) e, em seguida, toda iteração terá sobrecarga adicional de chamada de função (com alguma passagem de parâmetro) dasapply
chamadas.scala.util.Random
são apenas wrappers em torno de classes JRE equivalentes. A chamada de função extra é um pouco inútil.java.lang.Math.signum(x)
é muito mais direto do quex.signum()
, que converte paraRichInt
e para trás.fonte
reduz o tempo de 800 ms para 20 no meu sistema.
fonte