¿Por qué el logaritmo es más lento en Rust que en Java?
floating-point microbenchmark (2)
La respuesta fue dada por @kennytm :
export RUSTFLAGS=''-Ctarget-cpu=native''
Soluciona el problema. Después de eso, los resultados son:
test tests::bench_ln ... bench: 43 ns/iter (+/- 3)
test tests::bench_rnd ... bench: 5 ns/iter (+/- 0)
Creo que 38 (± 3) está lo suficientemente cerca de 31.555 (± 0.234).
Si ejecuto estos puntos de referencia en Rust:
#[bench]
fn bench_rnd(b: &mut Bencher) {
let mut rng = rand::weak_rng();
b.iter(|| rng.gen_range::<f64>(2.0, 100.0));
}
#[bench]
fn bench_ln(b: &mut Bencher) {
let mut rng = rand::weak_rng();
b.iter(|| rng.gen_range::<f64>(2.0, 100.0).ln());
}
El resultado es:
test tests::bench_ln ... bench: 121 ns/iter (+/- 2)
test tests::bench_rnd ... bench: 6 ns/iter (+/- 0)
121-6 = 115 ns por llamada.
Pero el mismo punto de referencia en Java:
@State(Scope.Benchmark)
public static class Rnd {
final double x = ThreadLocalRandom.current().nextDouble(2, 100);
}
@Benchmark
public double testLog(Rnd rnd) {
return Math.log(rnd.x);
}
Me da:
Benchmark Mode Cnt Score Error Units
Main.testLog avgt 20 31,555 ± 0,234 ns/op
El registro es ~ 3.7 veces más lento (115/31) en Rust que en Java.
Cuando pruebo la implementación de la hipotenusa ( hypot
), la implementación en Rust es 15.8 veces más rápida que en Java.
¿He escrito malos puntos de referencia o es un problema de rendimiento?
Respuestas a las preguntas formuladas en los comentarios:
"," es un separador decimal en mi país.
Ejecuto el punto de referencia de Rust usando
cargo bench
que siempre se ejecuta en modo de lanzamiento.El marco de referencia de Java (JMH) crea un nuevo objeto para cada llamada, aunque sea una clase
static
y una variablefinal
. Si agrego una creación aleatoria en el método probado, obtengo 43 ns / op.
Voy a proporcionar la otra mitad de la explicación ya que no conozco a Rust. Math.log
está anotado con @HotSpotIntrinsicCandidate
lo que significa que será reemplazado por una instrucción de CPU nativa para tal operación: piense en Integer.bitCount
que haría muchos cambios o usaría una instrucción de CPU directa que lo haga mucho más rápido.
Tener un programa extremadamente simple como este:
public static void main(String[] args) {
System.out.println(mathLn(20_000));
}
private static long mathLn(int x) {
long result = 0L;
for (int i = 0; i < x; ++i) {
result = result + ln(i);
}
return result;
}
private static final long ln(int x) {
return (long) Math.log(x);
}
Y ejecutándolo con:
java -XX:+UnlockDiagnosticVMOptions
-XX:+PrintInlining
-XX:+PrintIntrinsics
-XX:CICompilerCount=2
-XX:+PrintCompilation
package/Classname
Generará muchas líneas, pero una de ellas es:
@ 2 java.lang.Math::log (5 bytes) intrinsic
haciendo este código extremadamente rápido.
No sé realmente cuándo y cómo sucede eso en Rust ...