Java中方便快捷的3D向量数学计算

3
总的来说,我更喜欢使用Java编程而不是C++,主要是因为库的链接更容易(没有“依赖地狱”),并且有很多功能强大的包可以直接使用。我也喜欢像jMonkey和Processing这样的Java工具。
然而,我经常需要进行一些物理方面的操作,需要快速计算3D向量的数学运算。我没有找到任何方便的方法在Java中实现性能高效且易于阅读的解决方案(C++构造,例如宏、重载运算符、结构体和通过引用传递变量等都是非常有帮助的工具)。
例如,对于一个质点在中心力场中的跳跃积分器,在C++中我可以使用类似以下代码(使用类型float3的重载运算符):
float   ir2      =  1.0f/dot(vec_pos,vec_pos);
float   ir       =  sqrt(ir2);
float3  vec_G    = -vec_pos / (ir2*ir);
        vec_v   += vec_G*dt;
        vec_pos += vec_v*dt; 

Java中易读的代码应该长这样:

float   ir2      =  1.0f/vec_pos.mag2();
float   ir       =  sqrt(ir2);
float3  vec_G    =  vec_pos.mult( -ir2*ir);
        vec_v    .addLocal( vec_G.multLocal( dt ) );
        vec_pos  .addLocal( vec_v.mult     ( dt ) );

这样做对性能不是很好,因为它会分配新的对象作为临时变量,而在无法使用“Local”方法的情况下。我可以通过定义新的融合乘加方法来进行优化,例如:

float   ir2      =  1.0f/vec_pos.mag2();
float   ir       =  sqrt(ir2);
float3  vec_G    =  vec_pos.mult( -ir2*ir);
        vec_v    .addLocal_vec_times_scalar( vec_G, dt );
        vec_pos  .addLocal_vec_times_scalar( vec_v, dt );

但是为所有可能的float3向量算术运算定义专门的方法并不是很方便,比如:

  float3.addLocal_vec1_times_vec2_times_scalar() 

另一种避免即时分配临时对象的策略是将这些临时变量定义为静态全局变量(这不是良好的编码风格),或者作为封闭类的属性,例如:
class asteroide{
   // state variables
   float3 vec_pos;
   float3 vec_v; 
   // temporary variables
   float3 vec_G,vec_dpos;

   void update_leapfrog(float dt){
        float   ir2      =  1.0f/vec_pos.mag2();
        float   ir       =  sqrt(ir2);
        vec_G            .set_mult( vec_pos, -ir2*ir );
        vec_v            .addLocal( vec_G.multLocal( dt ) );
        dpos             .set_mult( vec_v, dt );
        vec_pos          .addLocal( dpos );
   }
}

在这两种情况下,解引用指针会产生性能开销,同时使得星体对象消耗更多内存。
调用对象方法也会有性能惩罚(即使我尝试将它们设为“final”和“static”,以便JIT可以有效地内联它们)。根据我的测试,使用float3.mult()比仅乘以3个float要慢2-3倍。
因此,我经常只使用float进行复杂的向量代数计算,以避免这些性能惩罚。但是这样做完全不可读。这种方式在刚体动力学和空气动力学计算中非常痛苦。这就像40年前的Fortran77程序一样糟糕!(只是出于好奇,可以查看Xfoil的代码)
您建议在Java中执行矢量数学的策略既能提高性能又方便(可读性)?

可能是Java矩阵数学库的性能?的重复问题。 - Alan
@Alan,这不是有关性能的问题,而是有关语法的问题。原帖作者想在Java中编写类似于纸上所写的数学表达式。 - Solomon Slow
@James,更准确地说:这是一个关于性能的问题,但也是一个关于语法的问题。然而,我承认我可能过于强调前者而不是后者。 - Alan
这是一个妥协问题:如果我想写出易读的向量数学代码,性能就会受到影响。 - Prokop Hapala
@ProkopHapala,问题就在这里:我认为你可以编写矩阵代码,a)快速、b)可读性强,并且c)使用Java...问题是你只能选择其中两个。 我通常要么用floats编写它,要么挑选最佳的执行矩阵库,具体取决于需要多少向量数学运算。 无论哪种方式,我都不得不接受丑陋的矩阵数学代码(与matlab / octave相比),并对其进行详细注释。 我尝试过的另一种方法是使用C/C++编写足够大的矩阵部分,然后使用JNI进行调用。 - Alan
1个回答

0

也许不是一个真正的答案,但对于评论来说太长了。

你是如何衡量性能的?所有的优化都需要一段时间;如果你的测量时间少于几秒钟,那就忘了它吧。

理论上,JIT应该能够优化掉不必要的分配。

关于丑陋:当然,像addLocal_vec_times_scalar这样的名称肯定很丑。像addProduct这样的名称就可以了。

调用对象方法也会有性能损失(即使我试图将它们设置为“final”和“static”,以便JIT可以有效地内联它们)

这是不必要的,因为JIT可以看到方法是否被某个地方覆盖。

您推荐在Java中进行向量数学运算的策略是什么,既高效又方便(可读性好)?

不要试图让它看起来像C语言。定义一些方法,比如融合乘法,忽略罕见情况。如果你使用像float3.addLocal_vec1_times_vec2_times_scalar这样的名称,它肯定很丑陋。有什么问题吗?

 vec1.addProduct(vec2, vec3, someScalar)

这不是关于丑陋的名称,而是关于代码的通用性(可重用性)。方法只是过于专业化了。我不喜欢为每个参数组合再次编写几乎相同的代码。 - Prokop Hapala
@ProkopHapala 我明白,但常见的参数组合并不多,你一般只会使用其中的5-10个,并忽略其他可能存在的低效性(如果有的话;我想不到超过10个有用的组合)。如果你的向量很大,那么你可以使用更聪明的方法,但是对于float3来说已经没有余地了。 - maaartinus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接