Existe algum benefício de desempenho de uma forma ou de outra? É específico do compilador / VM? Estou usando o Hotspot.
Existe algum benefício de desempenho de uma forma ou de outra? É específico do compilador / VM? Estou usando o Hotspot.
Respostas:
Primeiro: você não deve escolher entre estático e não estático com base no desempenho.
Segundo: na prática, não fará nenhuma diferença. O ponto de acesso pode escolher otimizar de maneiras que tornem as chamadas estáticas mais rápidas para um método e as chamadas não estáticas mais rápidas para outro.
Terceiro: muitos dos mitos que cercam estático versus não estático são baseados em JVMs muito antigos (que não faziam nada perto da otimização que o Hotspot faz) ou em algumas curiosidades sobre C ++ (em que uma chamada dinâmica usa mais um acesso de memória do que uma chamada estática).
Quatro anos depois...
Ok, na esperança de resolver essa questão de uma vez por todas, eu escrevi um benchmark que mostra como os diferentes tipos de chamadas (virtuais, não virtuais, estáticas) se comparam entre si.
Eu executei em um único , e isto é o que consegui:
(Quanto maior o número de iterações, melhor.)
Success time: 3.12 memory: 320576 signal:0
Name | Iterations
VirtualTest | 128009996
NonVirtualTest | 301765679
StaticTest | 352298601
Done.
Como esperado, as chamadas de métodos virtuais são as mais lentas, as chamadas de métodos não virtuais são mais rápidas e as chamadas de métodos estáticos são ainda mais rápidas.
O que eu não esperava era que as diferenças fossem tão pronunciadas: chamadas de método virtual foram medidas para rodar a menos da metade da velocidade de chamadas de método não virtuais, que por sua vez foram medidas para rodar 15% mais lento do que chamadas estáticas. É isso que essas medidas mostram; as diferenças reais devem ser um pouco mais pronunciadas, pois para cada chamada de método virtual, não virtual e estático, meu código de benchmarking tem uma sobrecarga constante adicional de incremento de uma variável inteira, verificação de uma variável booleana e loop se não for verdadeiro.
Suponho que os resultados variam de CPU para CPU e de JVM para JVM, então experimente e veja o que você obtém:
import java.io.*;
class StaticVsInstanceBenchmark
{
public static void main( String[] args ) throws Exception
{
StaticVsInstanceBenchmark program = new StaticVsInstanceBenchmark();
program.run();
}
static final int DURATION = 1000;
public void run() throws Exception
{
doBenchmark( new VirtualTest( new ClassWithVirtualMethod() ),
new NonVirtualTest( new ClassWithNonVirtualMethod() ),
new StaticTest() );
}
void doBenchmark( Test... tests ) throws Exception
{
System.out.println( " Name | Iterations" );
doBenchmark2( devNull, 1, tests ); //warmup
doBenchmark2( System.out, DURATION, tests );
System.out.println( "Done." );
}
void doBenchmark2( PrintStream printStream, int duration, Test[] tests ) throws Exception
{
for( Test test : tests )
{
long iterations = runTest( duration, test );
printStream.printf( "%15s | %10d\n", test.getClass().getSimpleName(), iterations );
}
}
long runTest( int duration, Test test ) throws Exception
{
test.terminate = false;
test.count = 0;
Thread thread = new Thread( test );
thread.start();
Thread.sleep( duration );
test.terminate = true;
thread.join();
return test.count;
}
static abstract class Test implements Runnable
{
boolean terminate = false;
long count = 0;
}
static class ClassWithStaticStuff
{
static int staticDummy;
static void staticMethod() { staticDummy++; }
}
static class StaticTest extends Test
{
@Override
public void run()
{
for( count = 0; !terminate; count++ )
{
ClassWithStaticStuff.staticMethod();
}
}
}
static class ClassWithVirtualMethod implements Runnable
{
int instanceDummy;
@Override public void run() { instanceDummy++; }
}
static class VirtualTest extends Test
{
final Runnable runnable;
VirtualTest( Runnable runnable )
{
this.runnable = runnable;
}
@Override
public void run()
{
for( count = 0; !terminate; count++ )
{
runnable.run();
}
}
}
static class ClassWithNonVirtualMethod
{
int instanceDummy;
final void nonVirtualMethod() { instanceDummy++; }
}
static class NonVirtualTest extends Test
{
final ClassWithNonVirtualMethod objectWithNonVirtualMethod;
NonVirtualTest( ClassWithNonVirtualMethod objectWithNonVirtualMethod )
{
this.objectWithNonVirtualMethod = objectWithNonVirtualMethod;
}
@Override
public void run()
{
for( count = 0; !terminate; count++ )
{
objectWithNonVirtualMethod.nonVirtualMethod();
}
}
}
static final PrintStream devNull = new PrintStream( new OutputStream()
{
public void write(int b) {}
} );
}
É importante notar que essa diferença de desempenho só se aplica ao código que não faz nada além de invocar métodos sem parâmetros. Qualquer outro código que você tenha entre as invocações diluirá as diferenças, e isso inclui a passagem de parâmetros. Na verdade, a diferença de 15% entre chamadas estáticas e não virtuais é provavelmente explicada por completo pelo fato de que o this
ponteiro não precisa ser passado para o método estático. Portanto, seria necessário apenas uma pequena quantidade de código fazendo coisas triviais entre as chamadas para que a diferença entre os diferentes tipos de chamadas fosse diluída a ponto de não ter nenhum impacto na rede.
Além disso, as chamadas de método virtual existem por um motivo; eles têm um propósito a servir e são implementados usando os meios mais eficientes fornecidos pelo hardware subjacente. (O conjunto de instruções da CPU.) Se, no seu desejo de eliminá-los substituindo-os por chamadas não virtuais ou estáticas, você acabar tendo que adicionar até um iota de código extra para emular sua funcionalidade, então sua sobrecarga líquida resultante é limitada ser não menos, mas mais. Muito possivelmente, muito, muito, incomensuravelmente muito, mais.
VirtualTest | 488846733 -- NonVirtualTest | 480530022 -- StaticTest | 484353198
na minha instalação do OpenJDK. FTR: Isso é verdade mesmo se eu remover o final
modificador. Btw. Tive que fazer o terminate
campo volatile
, senão a prova não acabou.
VirtualTest | 12451872 -- NonVirtualTest | 12089542 -- StaticTest | 8181170
. Além do OpenJDK no meu notebook conseguir realizar 40x mais iterações, o teste estático sempre tem cerca de 30% menos throughput. Este pode ser um fenômeno específico da ART, porque obtenho um resultado esperado em um tablet Android 4.4:VirtualTest | 138183740 -- NonVirtualTest | 142268636 -- StaticTest | 161388933
Bem, as chamadas estáticas não podem ser substituídas (portanto, são sempre candidatas a inlining) e não exigem nenhuma verificação de nulidade. O HotSpot faz um monte de otimizações legais para métodos de instância que podem negar essas vantagens, mas são possíveis razões pelas quais uma chamada estática pode ser mais rápida.
No entanto, isso não deve afetar seu design - código da forma mais legível e natural - e apenas se preocupar com esse tipo de microotimização se tiver uma causa justa (o que você quase nunca terá).
É específico do compilador / VM.
Portanto, provavelmente não vale a pena se preocupar com isso, a menos que você tenha identificado isso como um problema de desempenho verdadeiramente crítico em seu aplicativo. A otimização prematura é a raiz de todos os males, etc ...
No entanto , vi essa otimização dar um aumento substancial de desempenho na seguinte situação:
Se o acima se aplica a você, pode valer a pena testar.
Há também uma outra razão boa (e potencialmente ainda mais importante!) Para usar um método estático - se o método realmente tiver semântica estática (ou seja, logicamente não está conectado a uma determinada instância da classe), então faz sentido torná-lo estático para refletir esse fato. Os programadores Java experientes então notarão o modificador estático e pensarão imediatamente "aha! Este método é estático, então não precisa de uma instância e presumivelmente não manipula o estado específico da instância". Portanto, você comunicou a natureza estática do método de forma eficaz ....
Como já disseram os pôsteres anteriores: Parece uma otimização prematura.
No entanto, há uma diferença (parte do fato de que as invocações não estáticas exigem um envio adicional de um objeto callee na pilha de operandos):
Como os métodos estáticos não podem ser substituídos, não haverá nenhuma pesquisa virtual no tempo de execução para uma chamada de método estático. Isso pode resultar em uma diferença observável em algumas circunstâncias.
A diferença em um nível de byte-code é que uma chamada de método não-estático é feito através de INVOKEVIRTUAL
, INVOKEINTERFACE
ou INVOKESPECIAL
durante uma chamada de método estático é feito através de INVOKESTATIC
.
invokespecial
uma vez que não é virtual.
É inacreditavelmente improvável que qualquer diferença no desempenho de chamadas estáticas em relação às não estáticas esteja fazendo diferença em seu aplicativo. Lembre-se de que "a otimização prematura é a raiz de todos os males".
7 anos depois ...
Não tenho muita confiança nos resultados que Mike Nakis encontrou porque eles não abordam alguns problemas comuns relacionados às otimizações de pontos de acesso. Eu instrumentei benchmarks usando JMH e descobri que a sobrecarga de um método de instância é de cerca de 0,75% na minha máquina em comparação com uma chamada estática. Considerando a baixa sobrecarga, acho que, exceto nas operações mais sensíveis à latência, essa não é, sem dúvida, a maior preocupação no design de um aplicativo. Os resultados resumidos do meu benchmark JMH são os seguintes;
java -jar target/benchmark.jar
# -- snip --
Benchmark Mode Cnt Score Error Units
MyBenchmark.testInstanceMethod thrpt 200 414036562.933 ± 2198178.163 ops/s
MyBenchmark.testStaticMethod thrpt 200 417194553.496 ± 1055872.594 ops/s
Você pode olhar o código aqui no Github;
https://github.com/nfisher/svsi
O benchmark em si é bastante simples, mas visa minimizar a eliminação de código morto e o dobramento constante. Possivelmente, há outras otimizações que perdi / negligenciei e esses resultados provavelmente variam de acordo com a versão da JVM e o SO.
package ca.junctionbox.svsi;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.infra.Blackhole;
class InstanceSum {
public int sum(final int a, final int b) {
return a + b;
}
}
class StaticSum {
public static int sum(final int a, final int b) {
return a + b;
}
}
public class MyBenchmark {
private static final InstanceSum impl = new InstanceSum();
@State(Scope.Thread)
public static class Input {
public int a = 1;
public int b = 2;
}
@Benchmark
public void testStaticMethod(Input i, Blackhole blackhole) {
int sum = StaticSum.sum(i.a, i.b);
blackhole.consume(sum);
}
@Benchmark
public void testInstanceMethod(Input i, Blackhole blackhole) {
int sum = impl.sum(i.a, i.b);
blackhole.consume(sum);
}
}
ops/s
microotimização pode ter em métricas, exceto principalmente em um ambiente ART (por exemplo, uso de memória, tamanho de arquivo reduzido .oat, etc.). Você conhece alguma ferramenta / maneira relativamente simples de tentar avaliar essas outras métricas?
Para a decisão se um método deve ser estático, o aspecto do desempenho deve ser irrelevante. Se você tiver um problema de desempenho, tornar muitos métodos estáticos não vai salvar o dia. Dito isso, os métodos estáticos quase certamente não são mais lentos do que qualquer método de instância, na maioria dos casos ligeiramente mais rápidos :
1.) Os métodos estáticos não são polimórficos, portanto, a JVM tem menos decisões a tomar para encontrar o código real a ser executado. Este é um ponto discutível no Age of Hotspot, uma vez que o Hotspot otimizará chamadas de método de instância que têm apenas um site de implementação, para que executem o mesmo.
2.) Outra diferença sutil é que os métodos estáticos obviamente não têm nenhuma referência "esta". Isso resulta em um quadro de pilha um slot menor do que o de um método de instância com a mesma assinatura e corpo ("this" é colocado no slot 0 das variáveis locais no nível de bytecode, enquanto para métodos estáticos o slot 0 é usado para o primeiro parâmetro do método).
Pode haver uma diferença, e pode ser de qualquer maneira para qualquer parte específica do código, e pode mudar até mesmo com um pequeno release da JVM.
Isso é definitivamente parte dos 97% de pequenas eficiências que você deve esquecer .
TableView
milhões de registros.
Em teoria, menos caro.
A inicialização estática será feita mesmo se você criar uma instância do objeto, enquanto os métodos estáticos não farão nenhuma inicialização normalmente feita em um construtor.
No entanto, não testei isso.
Como Jon observa, os métodos estáticos não podem ser substituídos, então simplesmente invocar um método estático pode ser - em um Java runtime suficientemente ingênuo - mais rápido do que invocar um método de instância.
Mas então, mesmo supondo que você esteja no ponto em que se preocupa em bagunçar seu design para economizar alguns nanossegundos, isso apenas levanta outra questão: você precisará substituir o método por si mesmo? Se você alterar seu código para tornar um método de instância em um método estático para economizar um nanossegundo aqui e ali, e então virar e implementar seu próprio despachante em cima disso, o seu quase certamente será menos eficiente do que aquele construído em seu Java runtime já.
Eu gostaria de acrescentar às outras ótimas respostas aqui que também depende do seu fluxo, por exemplo:
Public class MyDao {
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, new MyRowMapper());
};
};
Preste atenção ao criar um novo objeto MyRowMapper para cada chamada.
Em vez disso, sugiro usar aqui um campo estático.
Public class MyDao {
private static RowMapper myRowMapper = new MyRowMapper();
private String sql = "select * from MY_ITEM";
public List<MyItem> getAllItems() {
springJdbcTemplate.query(sql, myRowMapper);
};
};