传递函数作为Java中快速数值的静态类
我想用java进行一些数值计算,为了使运算真正模块化,我想把传递函数作为其他函数的参数。我在搜索,通常是在java中使用类来扭曲函数。我真的不需要实例化这些类,因为里面没有数据,我想让它尽可能快地在某个地方编写,最终的静态方法由JIT编译器内联。所以我做了这样的东西传递函数作为Java中快速数值的静态类,java,function,static,numerics,Java,Function,Static,Numerics,我想用java进行一些数值计算,为了使运算真正模块化,我想把传递函数作为其他函数的参数。我在搜索,通常是在java中使用类来扭曲函数。我真的不需要实例化这些类,因为里面没有数据,我想让它尽可能快地在某个地方编写,最终的静态方法由JIT编译器内联。所以我做了这样的东西 public static class Function2 { public static float eval(float a, float b){ return Float.NaN; } } public static
public static class Function2 {
public static float eval(float a, float b){ return Float.NaN; }
}
public static class FAdd extends Function2 {
public static float eval(float a, float b){ return a+b; }
}
public static class Fmult extends Function2 {
public static float eval(float a, float b){ return a*b; }
}
void arrayOp( float [] a, float [] b, float [] out, Function2 func ){
for (int i=0; i<a.length; i++){ out[i] = func.eval( a[i], b[i] ); }
}
float [] a,b, out;
void setup(){
println( FAdd.eval(10,20) );
arrayOp( a,b, out, FAdd );
}
但是它打印错误:当我试图将它传递给arrayOp时,找不到类似FAdd的内容,即使println FAdd.eval10,20工作正常。所以,出于某种原因,似乎不可能通过静态类作为prameter
你有什么建议来解决这样的任务?实际上,我希望FAdd是类似于宏的东西,nad arrayOp是polymorf,其行为取决于我传入的宏。但理想情况是在编译时而不是在运行时解决,以提高数值速度。编译后的结果应该与我编写的相同
void arrayAdd( float [] a, float [] b, float [] out ){
for (int i=0; i<a.length; i++){ out[i] = a[i] + b[i]; }
}
void arrayMult( float [] a, float [] b, float [] out ){
for (int i=0; i<a.length; i++){ out[i] = a[i] * b[i]; }
}
你考虑过使用枚举吗
private void test() {
test(3.0f, 4.0f, F.Add);
test(3.0f, 4.0f, F.Sub);
test(3.0f, 4.0f, F.Mul);
test(3.0f, 4.0f, F.Div);
float[] a = {1f, 2f, 3f, 4f, 5f};
float[] b = {4f, 9f, 16f, 25f, 36f};
test(a, b, F.Add);
test(a, b, F.Sub);
test(a, b, F.Mul);
test(a, b, F.Div);
}
private void test(float[] a, float[] b, F f) {
System.out.println(Arrays.toString(a) + " " + f + " " + Arrays.toString(b) + " = " + Arrays.toString(f.f(a, b, f)));
}
private void test(float a, float b, F f) {
System.out.println(a + " " + f + " " + b + " = " + f.f(a, b));
}
public enum F {
Add {
@Override
public float f(float x, float y) {
return x + y;
}
@Override
public String toString() {
return "+";
}
},
Sub {
@Override
public float f(float x, float y) {
return x - y;
}
@Override
public String toString() {
return "-";
}
},
Mul {
@Override
public float f(float x, float y) {
return x * y;
}
@Override
public String toString() {
return "*";
}
},
Div {
@Override
public float f(float x, float y) {
return x / y;
}
@Override
public String toString() {
return "/";
}
};
// Evaluate to a new array.
static float[] f(float[] x, float[] y, F f) {
float[] c = new float[x.length];
for (int i = 0; i < x.length; i++) {
c[i] = f.f(x[i], y[i]);
}
return c;
}
// All must have an f(x,y) method.
public abstract float f(float x, float y);
// Also offer a toString - defaults to the enum name.
@Override
public String toString() {
return this.name();
}
}
您想要实现的实际上是或lambda表达式的功能,它位于Java编程语言的JSR335 lambda表达式中,将在Java8中提供。目前,只有匿名内部类接近于此。stackoverflow中的这个问题可能会对您有所帮助。您实际上在实现中混淆了实例和类。当您有这样声明的方法时:
void arrayOp( float [] a, float [] b, float [] out, Function2 func ){
for (int i=0; i<a.length; i++){ out[i] = func.eval( a[i], b[i] ); }
}
假设您想将类本身发送给一个方法,那么您的arrayOp声明将如下所示:
void arrayOp( float [] a, float [] b, float [] out, Class func ){
调用此方法时,将以以下方式传入参数:
arrayOp( a,b, out, FAdd.class );
但是静态方法不能通过继承被重写。你需要一个完全不同的实现来实现你的目标。也就是说,@OldCurmudgeon为您的问题提供了一个非常好的解决方案。考虑使用它。 你正在做一些大规模的假设,最快的代码只有在它最终的静态方法。您很可能是错的,应该把重点放在正确地构建它和测试性能上 如上所述,一种方法是使用敌人的武器。我想说,你应该做的是有一个与eval函数的接口。然后可以传入接口的实现
Java虚拟机将实现对代码的适当优化。静态方法不能被重写,但您可以使用匿名类执行此操作:
public static class Function2 {
public float eval(float a, float b){ return Float.NaN; }
}
arrayOp(a, b, out, new Function2() {
public float eval(float a, float b){
return FAdd.eval(a, b);
}});
请注意,Function2中eval的方法声明不是静态的。我做了一些测试,似乎真的没有必要在现代机器上对其进行优化 机器1-我的旧家用电脑32位WinXP,英特尔奔腾3,我不确定java版本 对于float.mult和float.add这两种操作,静态版本的速度要快2倍以上
static 100000000 [ops] 406.0 [s] 4.06 [ns/op]
dynamic 100000000 [ops] 1188.0 [s] 11.88 [ns/op]
但是对于float Sqrt,差异已经非常小了
static 100000000 [ops] 922.0 [s] 9.22 [ns/op]
dynamic 100000000 [ops] 1172.0 [s] 11.719999 [ns/op]
机器2-我的工作电脑-64位ubuntu 12.04LTS、Intel Core5、java版本1.6.0_12-ea、JavaTM SE运行时环境构建1.6.0_12-ea-b02、java HotSpotTM 64位服务器VM构建11.2-b01、混合模式
对于float,结果要好得多。添加:
static 1000000000 [ops] 1747.0 [s] 1.7470001 [ns/op]
dynamic 1000000000 [ops] 1750.0 [s] 1.75 [ns/op]
所以-我认为处理器或JIT已经足够聪明了,不需要优化这个函数
注:
-没有传递函数的静态平均解我只是内联的
手动将操作输入到循环中,
-当我使用传递函数作为动态对象实例而不是静态类时,动态平均解。JIT似乎理解类中没有动态数据,因此无论如何它都会在编译时解析它
因此,我的动态解决方案非常简单:
函数在Java中不是一流的对象,因此这在Java中是不可能的。结果是一样的,但它不会在任何需要的地方快速运行。如果性能非常关键,您应该像最后一样提供单独的方法。@Makoto方法将是Java 8中的第一类对象,但尚未发布;这是一种非常好的技术,对于解决方案来说是+1。但是,请认识到,这与为每个操作定义单独的类和实例没有什么不同。它只是简单地减少了类型。@parsifal-不仅减少了类型,而且它还遵守了各种编码原则,例如等等。此外,每个类只实例化一次。@OldCurmudgeon-注意,我并没有说这是一种不好的做法。然而,根据我的经验,Enum有任何行为的想法让很多人感到惊讶。创建枚举的匿名子类可能会让人更加惊讶。哇,有趣的是,枚举有一些不平凡的功能。然而,我更喜欢类而不是枚举,因为类可以在内部进行扩展,如果用户希望为arrayOp定义自己的函数核心,则稍后可以重写该类。另外,我主要是在处理方面工作,似乎枚举仍然不在那里工作。我不喜欢这里的新内容,这可能会 分配新对象。我不确定它是否在JIT中进行了优化,是否可以在编译时内联没有数据的新对象?
static 100000000 [ops] 922.0 [s] 9.22 [ns/op]
dynamic 100000000 [ops] 1172.0 [s] 11.719999 [ns/op]
static 1000000000 [ops] 1747.0 [s] 1.7470001 [ns/op]
dynamic 1000000000 [ops] 1750.0 [s] 1.75 [ns/op]
public class Function2 {
public float eval(float a, float b){ return Float.NaN; }
}
public class FAdd extends Function2 {
public float eval(float a, float b){ return a+b; }
}
public class FMult extends Function2 {
public float eval(float a, float b){ return a*b; }
}
public void arrayOp( float [] a, float [] b, float [] out, Function2 func ){
for (int i=0; i<a.length; i++){ out[i] = func.eval( a[i], b[i] ); }
}
final int m = 100;
final int n = 10000000;
float t1,t2;
float [] a,b, out;
a = new float[n]; b = new float[n]; out = new float[n];
t1 = millis();
Function2 func = new FMult();
for (int i=0;i<m;i++) arrayOp( a,b, out, func );
t2 = millis();
println( " dynamic " +(n*m)+" [ops] "+(t2-t1)+" [s] "+ 1000000*((t2-t1)/(n*m))+" [ns/op] " );