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

时间:2014-01-28 07:21:11

标签: android performance android-ndk jit dalvik

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

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

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

3 个答案:

答案 0 :(得分:13)

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

  • 数组绑定检查 - Java将检查每次访问,bmap [i],并确认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];
  }
}

访问数组元素时,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++;
}

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

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

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

答案 1 :(得分:0)

上面未提及的另一个因素是垃圾收集器。问题在于,垃圾回收需要时间,而且它可以随时运行。这意味着创建大量临时对象的Java程序(请注意某些类型的String操作对此不利)通常会触发垃圾收集器,从而使程序(应用程序)变慢。

答案 2 :(得分:-9)

以下是基于级别的编程语言列表,

  • 汇编语言(机器语言,情人等级)
  • C语言(中级)
  • C ++,Java,.net,(更高级别)

此处低级语言可直接访问硬件。只要级别增加,对硬件的访问就会减少。因此汇编语言的代码以最高速度运行,而其他语言的代码则根据其级别运行。

这就是C语言代码运行速度比Java代码快得多的原因。