Pergunta

eu preciso calcular Math.exp() de java com muita frequência, é possível fazer com que uma versão nativa rode mais rápido que Javade Math.exp()??

Eu tentei apenas jni + C, mas é mais lento do que simplesmente Java.

Foi útil?

Solução

+1 para escrever sua própria implementação de exp().Isto é, se isso for realmente um gargalo em sua aplicação.Se você conseguir lidar com um pouco de imprecisão, existem vários algoritmos de estimativa de expoente extremamente eficientes, alguns deles datados de séculos atrás.Pelo que entendi, a implementação de exp() do Java é bastante lenta, mesmo para algoritmos que devem retornar resultados "exatos".

Ah, e não tenha medo de escrever essa implementação de exp() em Java puro.JNI tem muita sobrecarga e a JVM é capaz de otimizar bytecode em tempo de execução, às vezes até além do que C/C++ é capaz de alcançar.

Outras dicas

Isto já foi solicitado diversas vezes (ver, por exemplo, aqui).Aqui está uma aproximação para Math.exp(), copiada de esta postagem do blog:

public static double exp(double val) {
    final long tmp = (long) (1512775 * val + (1072693248 - 60801));
    return Double.longBitsToDouble(tmp << 32);
}

É basicamente o mesmo que uma tabela de consulta com 2.048 entradas e interpolação linear entre as entradas, mas tudo isso com truques de ponto flutuante IEEE.É 5 vezes mais rápido que Math.exp() na minha máquina, mas isso pode variar drasticamente se você compilar com -server.

Use Java.

Além disso, armazene em cache os resultados da exp e então você poderá procurar a resposta mais rapidamente do que calculá-los novamente.

Você gostaria de encerrar qualquer loop que esteja chamando Math.exp() em C também.Caso contrário, a sobrecarga de empacotamento entre Java e C superará qualquer vantagem de desempenho.

Você poderá fazê-lo funcionar mais rápido se fizer isso em lotes.Fazer uma chamada JNI adiciona sobrecarga, então você não deseja fazer isso para cada exp() que precisa calcular.Eu tentaria passar uma matriz de 100 valores e obter os resultados para ver se isso ajuda no desempenho.

A verdadeira questão é: isso se tornou um gargalo para você?Você criou o perfil do seu aplicativo e descobriu que esta é uma das principais causas de lentidão?

Caso contrário, eu recomendaria usar a versão do Java.Tente não pré-otimizar, pois isso apenas causará lentidão no desenvolvimento.Você pode gastar muito tempo resolvendo um problema que pode não ser um problema.

Dito isto, acho que seu teste lhe deu sua resposta.Se jni + C for mais lento, use a versão do java.

Commons Matemática3 vem com uma versão otimizada: FastMath.exp(double x).Isso acelerou meu código significativamente.

Fabiano fiz alguns testes e descobri que era quase duas vezes mais rápido que Math.exp():

 0.75s for Math.exp     sum=1.7182816693332244E7
 0.40s for FastMath.exp sum=1.7182816693332244E7

Aqui está o javadoc:

Calcula exp(x), o resultado da função é quase arredondado.Será arredondado corretamente para o valor teórico para 99,9% dos valores de entrada, caso contrário terá um erro de 1 UPL.

Método:

    Lookup intVal = exp(int(x))
    Lookup fracVal = exp(int(x-int(x) / 1024.0) * 1024.0 );
    Compute z as the exponential of the remaining bits by a polynomial minus one
    exp(x) = intVal * fracVal * (1 + z)

Precisão:O cálculo é feito com 63 bits de precisão, portanto o resultado deve ser arredondado corretamente para 99,9% dos valores de entrada, caso contrário, com menos de 1 erro ULP.

Como o código Java será compilado em código nativo com o compilador just-in-time (JIT), não há realmente nenhuma razão para usar JNI para chamar código nativo.

Além disso, você não deve armazenar em cache os resultados de um método onde os parâmetros de entrada são números reais de ponto flutuante.Os ganhos obtidos em tempo serão muito perdidos na quantidade de espaço utilizado.

O problema de usar JNI é a sobrecarga envolvida em fazer a chamada para JNI.A máquina virtual Java está bastante otimizada atualmente, e as chamadas para o Math.exp() integrado são automaticamente otimizadas para chamar diretamente para a função C exp() e podem até ser otimizadas para montagem direta de ponto flutuante x87 instruções.

Há simplesmente uma sobrecarga associada ao uso do JNI, consulte também:http://java.sun.com/docs/books/performance/1st_edition/html/JPNativeCode.fm.html

Assim como outros sugeriram, tente agrupar operações que envolveriam o uso do JNI.

Escreva o seu próprio, adaptado às suas necessidades.

Por exemplo, se todos os seus expoentes forem potências de dois, você poderá usar a mudança de bits.Se você trabalha com um intervalo ou conjunto limitado de valores, poderá usar tabelas de consulta.Se você não precisa de precisão exata, use um algoritmo impreciso, mas mais rápido.

Há um custo associado às chamadas além dos limites do JNI.

Se você pudesse mover o loop que chama exp() também para o código nativo, de modo que haja apenas uma chamada nativa, você poderá obter melhores resultados, mas duvido que seja significativamente mais rápido que a solução Java pura.

Não conheço os detalhes do seu aplicativo, mas se você tiver um conjunto bastante limitado de argumentos possíveis para a chamada, poderá usar uma tabela de consulta pré-computada para tornar seu código Java mais rápido.

Existem algoritmos mais rápidos para experiência, dependendo do que você está tentando realizar.O espaço do problema está restrito a um determinado intervalo, você só precisa de uma certa resolução, precisão ou exatidão, etc.

Se você definir seu problema muito bem, poderá descobrir que pode usar uma tabela com interpolação, por exemplo, o que eliminará praticamente qualquer outro algoritmo.

Que restrições você pode aplicar à exp para obter essa compensação de desempenho?

-Adão

Eu executo um algoritmo de ajuste e o erro mínimo do resultado do ajuste é muito maior que a precisão do math.exp ().

As funções transcendentais são sempre muito mais lentas que a adição ou multiplicação e são um gargalo bem conhecido.Se você sabe que seus valores estão em um intervalo estreito, você pode simplesmente construir uma tabela de pesquisa (dois arrays classificados;uma entrada, uma saída).Use Arrays.binarySearch para encontrar o índice correto e interpolar o valor com os elementos em [index] e [index+1].

Outro método é dividir o número.Vamos pegar, por exemplo.3,81 e divida isso em 3+0,81.Agora você multiplica e = 2,718 três vezes e obtém 20,08.

Agora para 0,81.Todos os valores entre 0 e 1 convergem rapidamente com a conhecida série exponencial

1+x+x^2/2+x^3/6+x^4/24....etc.

Use quantos termos forem necessários para precisão;infelizmente é mais lento se x se aproximar de 1.Digamos que você vá para x ^ 4 e obtenha 2,2445 em vez do 2,2448 correto

Em seguida, multiplique o resultado 2,781^3 = 20,08 com 2,781^0,81 = 2,2445 e você tem o resultado 45.07 com um erro de uma parte de dois mil (correto:45.15).

Pode não ser mais relevante, mas só para você saber, nas versões mais recentes do OpenJDK (veja aqui), Math.exp deve se tornar intrínseco (se você não sabe o que é isso, verifique aqui).

Isso tornará o desempenho imbatível na maioria das arquiteturas, porque significa que a VM Hotspot substituirá a chamada para Math.exp por uma implementação específica do processador de exp em tempo de execução.Você nunca pode vencer essas chamadas, pois elas são otimizadas para a arquitetura...

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top