Android:为什么本机代码比Java代码快得多

Guy*_*Guy 9 performance android jit dalvik android-ndk

在下面的SO问题:https: //stackoverflow.com/questions/2067955/fast-bitmap-blur-for-android-sdk @zeh声称一个java模糊算法的端口到C运行速度快40倍.

鉴于大部分代码仅包含计算,并且所有分配仅在实际算法数字运算之前"一次"完成 - 任何人都可以解释为什么此代码运行速度快40倍?Dalvik JIT不应该翻译字节码并大大减少与本机编译代码速度的差距吗?

注意:我自己没有确认此算法的x40性能增益,但我遇到的所有严格的图像处理算法都使用NDK - 因此这支持NDK代码运行速度更快的概念.

jdr*_*5ca 14

对于在数据数组上运行的算法,有两件事可以显着改变Java和C之类的语言之间的性能:

  • 数组绑定检查 - Java将检查每个访问,bmap [i],并确认我在数组边界内.如果代码尝试访问越界,您将获得有用的异常.C&C++不会检查任何内容,只需信任您的代码.对越界访问的最佳案例响应是页面错误.更可能的结果是"意外行为".

  • 指针 - 您可以通过使用指针显着减少操作.

拿这个普通过滤器的无辜示例(类似于模糊,但是1D):

for{i=0; i<ndata-ncoef; ++i) {
  z[i] = 0;
  for{k=0; k<ncoef; ++k) {
    z[i] += c[k] * d[i+k];
  }
}
Run Code Online (Sandbox Code Playgroud)

当您访问数组元素时,coef [k]是:

  • 将数组coef加载到寄存器中
  • 将值k加载到寄存器中
  • 总结一下
  • 去那个地址得到记忆

可以改进每个数组访问,因为您知道索引是顺序的.编译器和JIT都可以知道索引是顺序的,因此无法完全优化(尽管他们不断尝试).

在C++中,您可以编写更像这样的代码:

int d[10000];
int z[10000];
int coef[10];
int* zptr;
int* dptr;
int* cptr;
dptr = &(d[0]); // Just being overly explicit here, more likely you would dptr = d;
zptr = &(z[0]); // or zptr = z;
for{i=0; i<(ndata-ncoef); ++i) {
  *zptr = 0; 
  *cptr = coef;
  *dptr = d + i;
  for{k=0; k<ncoef; ++k) {
    *zptr += *cptr * *dptr;
    cptr++;
    dptr++;
  }
  zptr++;
}
Run Code Online (Sandbox Code Playgroud)

当你第一次做这样的事情(并成功地使它正确)时,你会惊讶地发现它会更快.获取索引并对索引和基地址求和的所有数组地址计算都用增量指令替换.

对于2D阵列操作,例如图像上的模糊,无辜的代码数据[r,c]涉及两个值提取,乘法和和.因此,对于2D数组,指针的好处允许您删除多次运算.

因此,该语言允许真正减少CPU必须执行的操作.成本是C++代码读取和调试是可怕的.指针错误和缓冲区溢出是黑客的食物.但是当谈到原始数字磨削算法时,速度的提高太诱人了.

  • 同样非常重要的是:Dalvik JIT的优化范围有限(参见http://stackoverflow.com/questions/4912695/what-optimizations-can-i-expect-from-dalvik-and-the-android-工具链.请注意,数组边界检查可能不是一个重要的性能问题 - 如果编译器可以确定可能的索引值的范围必须介于0和N之间,它可以生成在数组大小和数组之间进行单次运行时比较的代码. N进入循环之前. (3认同)
  • 好吧,花括号是错的,6 年后纠正了。 (2认同)