Java Tip 141: Fast math with JNI

本文介绍了一种通过Java Native Interface (JNI) 调用C/C++数学库来提高Java应用程序中数学运算速度的方法。作者在开发计算机生成全息图(CGH)程序时发现J2SE 1.4x的数学库比J2SE 1.3.1慢,通过JNI调用本地C/C++数学库实现了显著的性能提升。

http://www.javaworld.com/javaworld/javatips/jw-javatip141.html

While developing a computer-generated hologram (CGH) program, I noticed that the math routines in Java 2 Platform, Standard Edition (J2SE) 1.4x were several times slower than the corresponding routines in J2SE 1.3.1. Sun Microsystems has implemented a new math package, StrictMath, in J2SE 1.4 that guarantees identical math calculations across all platforms at the expense of execution speed. My CGH program ran seven times slower with the new J2SE 1.4x routines! I didn't want to switch back to J2SE 1.3.1, however, because I wanted to use the new javax.imageio package to save my holograms to disk as .png files. I submitted some bug reports to Sun about this serious performance regression, but received no meaningful response.

JNI to the rescue

Since my trusty C++ compiler produced fast math code, I just needed to call these math routines instead of the slow StrictMathroutines from my Java program. Java Native Interface (JNI) enables Java code to interface with native code written in other languages like C/C++. It enables you to write the bulk of your application in Java and still utilize highly optimized native code where necessary. And in the corporate computing world, JNI enables Java applications to connect to legacy code that might be difficult or impractical to port to Java.

To build my JNI math library, I first create a Java class, MathLib, which declares three native methods, cos()sin(), and sqrt(). I add a main method to test these native methods:

package com.softtechdesign.math;
/**
 * MathLib declares native math routines to get around the slow StrictMath math
 * routines in JDK 1.4x
 * @author Jeff S Smith
 */
public class MathLib
{
    public static native double sin(double radians);
    public static native double cos(double radians);
    public static native double sqrt(double value);
    static 
    {
        System.loadLibrary("MathLib");
    }
    
    public static void main(String[] args) 
    {
        System.out.println("MathLib benchmark..." + new java.util.Date());
        double d = 0.5;
        for (int i=0; i < 10000000; i++)
        {
            d = (d * i) / 3.1415926;
            d = MathLib.sin(d);
            d = MathLib.cos(d);
            d = MathLib.sqrt(d) * 3.1415926;
        }
        
        System.out.println("Benchmark done. Time is " + new java.util.Date());
    }
}


The static initializer, which executes before the code in the main method, loads the MathLib library. The main method performs a benchmark test of the routines by calling them in a loop. Next, I compile my class and create a header (.h) file for it by using the standard javah tool:

javah -jni -o MathLib.h -classpath . com.softtechdesign.math.MathLib


This command creates a file called MathLib.h that contains the following C function signatures: cos()sin(), and sqrt(). This C header file includes <jni.h>, the standard header file for JNI applications. Note how the fully qualified class name becomes part of the C function name. MathLib.h looks like this:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_softtechdesign_math_MathLib */
#ifndef _Included_com_softtechdesign_math_MathLib
#define _Included_com_softtechdesign_math_MathLib
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_softtechdesign_math_MathLib
 * Method:    sin
 */
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_sin
  (JNIEnv *, jclass, jdouble);
/*
 * Class:     com_softtechdesign_math_MathLib
 * Method:    cos
 */
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_cos
  (JNIEnv *, jclass, jdouble);
/*
 * Class:     com_softtechdesign_math_MathLib
 * Method:    sqrt
 */
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_sqrt
  (JNIEnv *, jclass, jdouble);
#ifdef __cplusplus
}
#endif
#endif


Next, I create a file called Math.c and implement the native C code that includes the three math routines using the signatures found in my MathLib.h file. Note that these routines simply call the standard C versions of sin()cos(), and sqrt():

#include <jni.h>
#include <math.h>
#include "MathLib.h"
#include <stdio.h>
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_sin(JNIEnv *env, jobject obj, jdouble value)
{
    return(sin(value));
}
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_cos(JNIEnv *env, jobject obj, jdouble value)
{
    return(cos(value));
}
JNIEXPORT jdouble JNICALL Java_com_softtechdesign_math_MathLib_sqrt(JNIEnv *env, jobject obj, jdouble value)
{
    return(sqrt(value));
}


The routines all return a jdouble and have two standard JNI parameters, JNIEnv (JNI interface pointer) and jobject (a reference to the calling object itself, similar to this). All implemented JNI routines have these first two parameters (even if the native method doesn't declare any parameters). The third parameter is my user-defined parameter that contains the value I pass into the function. These routines calculate the sin()cos(), or sqrt() of this parameter and return the value as a jdouble.

A word about JNI types

Native methods can access the following types:

Boolean  jboolean
string   jstring
byte     jbyte
char     jchar
short    jshort
int      jint
long     jlong
float    jfloat
double   jdouble
void     void 


All of these types can be directly accessed except for jstring, which requires a subroutine call to convert a Java Unicode string (2 bytes) to a C-style char* string (1 byte UTF-8 format). To convert a jstring to a C-style string, you might write code like the following:

JNIEXPORT void JNICALL
Java_MyJavaClass_printName(JNIEnv *env, jobject obj, jstring name)
{
    const char *str = (*env)->GetStringUTFChars(env, name, 0);
    printf("%s", name);
      //Need to release this string when done with it
      //to avoid memory leak
    (*env)->ReleaseStringUTFChars(env, name, str);
}


You can use the (*env)->NewStringUTF() function to create a new jstring from a C-style string. For example, a C function can contain the following code:

JNIEXPORT jstring JNICALL
Java_MyJavaClass_getName(JNIEnv *env, jobject obj)
{
    return (*env)->NewStringUTF(env, "Fred Flintstone");
}


Back to finishing MathLib

Continuing with MathLib, I compile my C code into a library (a Dynamic Link Library (DLL) since my code runs under Windows). I use Borland C++ Builder to create my DLL. If you use C++ Builder, make sure you create a DLL project (named MathLib) and then add the Math.c file to it. You also need to add the following to your compiler's include path:

  \javadir\include
  \javadir\include\win32


C++ Builder creates a library named MathLib.dll when it builds this project.

If you compile a Windows C library using Visual C++, you compile it like so:

cl -Ic:\javadir\include -Ic:\javadir\include\win32 -LD Math.c -FeMathLib.dll


And if you compile a C library under Solaris, you use the following command:

cc -G -I/javadir/include -I/javadir/include/solaris Math.c -o MathLib.so


The final step entails adding my DLL directory to my Java runtime library path (alternatively, I can copy the DLL to my Java program's working directory). If the Java code can't find the library, you will get a java.lang.UnsatisfiedLinkError. Try running your Java program:

java -classpath . com.softtechdesign.math.MathLib


The benchmark code in your main method should execute. If you want to compare the execution speed with the StrictMath versions, change the calls in MathLib.java from MathLib.xxx() to Math.xxx(). On my Pentium 4, 2-GHz machine, the benchmark took 24.5 seconds with the StrictMath routines and 7 seconds with the MathLib (JNI) routines. Not a bad performance boost!

The MathLib (JNI) routines are about half as fast as the Math routines in J2SE 1.3.1—a result of the overhead involved in making a JNI call. Note that when you make numerous calls to the MathLib routines in a tight loop, you sometimes get an exception:

Unexpected Signal : EXCEPTION_FLT_STACK_CHECK occurred at PC=0x9ED0D2


I submitted a bug report to Sun about this problem, but I have not received a response. The good news is that in practice, I have never gotten the error when running any of my hologram program code.

Hooray for JNI

JNI enables programmers to employ fast C/C++ math routines in their Java programs. It solves problems for which Java is poorly suited: when you need the speed of C or assembler, or when you need to write low-level code to communicate directly with hardware. It can also be used to access legacy applications or interface with libraries written in other languages.

This MathLib example barely scratches the surface of what you can do with JNI. JNI native methods can create and manipulate Java objects such as strings and arrays and accept Java objects as parameters. JNI can even catch and throw exceptions from native methods and handle these exceptions from the native code or from your Java application. This almost seamless sharing of objects makes it very easy to incorporate native methods in your Java applications.

About the author

Jeff S. Smith is president of SoftTech Design, a Web and software development company located near Denver, Colo. Jeff has a BA in physics from the University of Virginia and 15 years of software/database/Web development experience as well as 6 years of experience as a Delphi and Java instructor. He has written numerous games and home entertainment programs and authored articles on genetic algorithms and Java Database Connectivity (JDBC) frameworks. You can read more of Jeff's articles at: http://www.SoftTechDesign.com/media.html.
<think>好的,我现在需要帮用户解决如何在RVIZ显示PCD点云文件的问题。首先,我要回忆一下ROS中处理点云的常用工具和步骤。记得PCD文件点云数据的一种格式,通常使用PCL库来处理。在ROS中,可能需要用到pcl_ros包来转换和发布点云数据。 用户提到之前有引用关于读取PCD文件RVIZ显示的内容,但之前的问题可能遇到了路径错误或者Frame设置不正确的情况。比如,Fixed Frame设置为odom1,而如果程序中发布的点云Frame ID不一致,就会导致RVIZ无法显示。此外,topic的名称是否正确也是关键点。 接下来,我应该分步骤指导用户。首先,安装必要的依赖,比如pcl和pcl_ros,确保这些包已经安装。然后,创建一个ROS节点,用于读取PCD文件发布为PointCloud2消息。这里可能需要编写一个简单的Python或C++节点。例如,使用Python的话,可以用pcl库读取PCD文件,然后通过ROS的Publisher发布到某个topic上。 然后,在RVIZ中添加PointCloud2显示,选择正确的topic和Fixed Frame。这里要提醒用户注意检查发布的topic名称是否与RVIZ中订阅的一致,以及Frame ID是否正确,比如odom或其他自定义的Frame。如果Frame不对,点云可能不会显示。 另外,用户之前遇到的问题可能是文件路径错误。需要强调在代码中使用绝对路径或者正确的工作目录下的相对路径,避免找不到文件的错误。还有,可能需要使用rosrun或者roslaunch正确运行节点,确保节点能找到PCD文件。 还有可能的问题是点云的坐标系问题,如果发布的点云Frame没有对应的tf转换,RVIZ也无法显示。这时候需要确保有正确的静态tf发布或者动态的坐标系转换。 最后,生成相关问题的时候,要考虑用户可能遇到的后续问题,比如如何转换点云格式、如何处理大量点云数据、或者如何自定义显示颜色等。这些问题可以帮助用户进一步深入使用RVIZ和ROS进行点云处理。</think>### 如何在 RVIZ显示 PCD 点云文件? #### 步骤说明 1. **安装依赖** 确保已安装 PCL 库和 ROS 的 `pcl_ros` 包: ```bash sudo apt-get install ros-<distro>-pcl-ros libpcl-dev ``` 2. **创建 ROS 节点** 编写 Python/C++ 节点读取 PCD 文件发布为 `PointCloud2` 消息。 ```python # Python 示例(需安装 python3-pcl) import pcl import rospy from sensor_msgs.msg import PointCloud2 def pcd_publisher(): pub = rospy.Publisher('/pointcloud_topic', PointCloud2, queue_size=10) rospy.init_node('pcd_publisher') cloud = pcl.load("path/to/file.pcd") ros_cloud = pcl_to_ros(cloud) # 需实现点云格式转换函数 while not rospy.is_shutdown(): pub.publish(ros_cloud) rospy.sleep(1.0) ``` 3. **配置 RVIZ** - 启动 RVIZ:`rosrun rviz rviz` - 添加 `PointCloud2` 显示类型 - 设置 `Fixed Frame` 与点云消息中的 `frame_id` 一致(如 `odom` 或自定义坐标系) - 选择正确的 `Topic`(如 `/pointcloud_topic`) 4. **验证数据流** 使用 `rostopic echo /pointcloud_topic` 确认数据正常发布,检查终端是否有文件路径错误[^1]。 #### 关键注意事项 - **文件路径**:建议使用绝对路径或 ROS `package://` 格式路径 - **坐标系对齐**:通过 `tf` 发布静态坐标系转换(如需) - **点云密度**:大规模点云需使用 `octomap` 或降采样处理 ```bash # 静态坐标系发布示例 rosrun tf static_transform_publisher 0 0 0 0 0 0 map odom 100 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值