You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

非并行运算场景下Android NDK为何比RenderScript慢

问题背景

和大多数RenderScript(以下简称RS)用户一样,我得知RS被废弃的消息时十分意外。这个决定虽然可以理解,但确实给实际开发造成了不小的麻烦。

我实现的算法里有两个图像处理模块依赖RS:Canny边缘检测、距离变换。

  • Canny边缘检测模块迁移到Vulkan的过程非常顺利,最终运行效果和RS版本持平,部分场景下Vulkan的运行速度甚至比RS更好。
  • 但[Rosenfeld and Pfaltz 1966]提出的距离变换算法本身不具备并行性,我当前在RS里的实现是纯串行逻辑,通过invoke()调用执行,底层都是常规实现,基于RS Allocation完成set/get这类数据读写操作。

因为要找RS的替代方案,而Vulkan不适合非并行运算场景,我原本判断NDK的性能应该和RS相当;考虑到NDK还省去了Java层和Allocation之间的双向数据拷贝开销,我甚至预估NDK实现的速度会更快。

但在写完和RS逻辑完全等价的NDK C++版本后,我发现NDK版本的运行速度居然慢了23倍,这个结果在多款安卓智能手机上测试复现一致,都是NDK版本慢23倍。

我一直想不通这个现象的成因:

  • 是不是RS Allocation针对内存访问做了特殊的性能优化?
  • RenderScript内部有没有未公开的特殊优化机制?
  • 为什么基于invoke()和Allocation实现的简单for循环,速度会比NDK C++里逻辑完全一致的for循环更快?

更新I

应solidpixel要求,补充相关实现代码如下:

kernel.rs

#pragma version(1)
#pragma rs java_package_name(distancetransform)

rs_allocation inAlloc;
uint32_t width;
uint32_t height;
uint max_value;

uint __attribute__((kernel)) initialize(uint32_t x, uint32_t y) {

    if(rsGetElementAt_uint(inAlloc,x,y)==1) {
        return 0;
    } else{
        return max_value;
    }
    
}

uint __attribute__((kernel)) clear(uint32_t x, uint32_t y) {
    return 0;
}

//SEQUENCIAL NO MAP X,Y

void first_pass_() {
    
    int i,j;
    
    for (i=1;i<height-1;i++){
        for (j=1;j<width-1;j++){
            uint c00 = rsGetElementAt_uint(inAlloc,j-1,i-1)+4;
            uint c01 = rsGetElementAt_uint(inAlloc,j,i-1)+3;
            uint c02 = rsGetElementAt_uint(inAlloc,j+1,i-1)+4;
            uint c10 = rsGetElementAt_uint(inAlloc,j-1,i)+3;
            uint c11 = rsGetElementAt_uint(inAlloc,j,i);
        
            uint min_a = min(c00,c01);
            uint min_b = min(c02,c10);
            uint min_ab = min(min_a,min_b);
            uint min_sum = min(min_ab,c11);
            
            rsSetElementAt_uint(inAlloc,min_sum,j,i);
        }
    }
}

void second_pass_() {
    
    int i,j;
    
    for (i=height-2;i>0;i--){
        for (j=width-2;j>0;j--){
            uint c00 = rsGetElementAt_uint(inAlloc,j,i);
            uint c01 = rsGetElementAt_uint(inAlloc,j+1,i)+3;
            uint c02 = rsGetElementAt_uint(inAlloc,j-1,i+1)+4;
            uint c10 = rsGetElementAt_uint(inAlloc,j,i+1)+3;
            uint c11 = rsGetElementAt_uint(inAlloc,j+1,i+1)+4;
            
            uint min_a = min(c00,c01);
            uint min_b = min(c02,c10);
            uint min_ab = min(min_a,min_b);
            uint min_sum = min(min_ab,c11);
            
            rsSetElementAt_uint(inAlloc,min_sum,j,i);
        }
    }
}

Java层实现

public void distanceTransform(IntBuffer edgeBuffer) {
        
        long total_0 = System.nanoTime();
        
        edgeBuffer.get(_input);
        edgeBuffer.rewind();
        _allocK.copyFrom(_input);
        _script.forEach_initialize(_allocK);
        
        _script.invoke_first_pass_();
        _script.invoke_second_pass_();
        
        _allocK.copyTo(_result);
        
        _distMapBuffer.put(_result);
        _distMapBuffer.rewind();
        
        long total_1 = System.nanoTime();
        Log.d(TAG,"total call time = "+((total_1-total_0)*0.000001)+"ms");
    }

注:edgeBuffer与distMapBuffer为Java NIO缓冲区,用于实现跨语言高效绑定,与本问题核心无关,仅做实现完整性补充。

ndk.cpp

extern "C" JNIEXPORT void JNICALL Java_distanceTransform(
        JNIEnv* env, jobject /* this */,jobject edgeMap, jobject distMap) {
    auto* dt = (int32_t*)env->GetDirectBufferAddress(distMap);
    auto* edgemap = (int32_t*)env->GetDirectBufferAddress(edgeMap);

    auto s_init = std::chrono::high_resolution_clock::now();

    int32_t i, j;
    int32_t size = h*w;
    int32_t max_val = w+h;
    for (i = 0; i < size; i++) {
        if (edgemap[i]!=0) {
            dt[i] = 0;
        } else {
            dt[i] = max_val;
        }
    }

    auto e_init = std::chrono::high_resolution_clock::now();
    auto elapsed_init = std::chrono::duration_cast<std::chrono::nanoseconds>(e_init - s_init);
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Time init = %f", elapsed_init.count() * 1e-9);

    auto s_first = std::chrono::high_resolution_clock::now();

    for (i = 1; i < h-1; i++) {
        for (j = 1; j < w-1; j++) {
            int32_t c00 = dt[(i-1)*w+(j-1)]+4;
            int32_t c01 = dt[(i-1)*w+j]+3;
            int32_t c02 = dt[(i-1)*w+(j+1)]+4;
            int32_t c10 = dt[i*w+(j-1)]+3;
            int32_t c11 = dt[i*w+j];

            int32_t min_a = c00<c01?c00:c01;
            int32_t min_b = c02<c10?c02:c10;
            int32_t min_ab = min_a<min_b?min_a:min_b;
            int32_t min_sum = min_ab<c11?min_ab:c11;
            dt[i*w+j] = min_sum;
        }
    }

    auto e_first = std::chrono::high_resolution_clock::now();
    auto elapsed_first = std::chrono::duration_cast<std::chrono::nanoseconds>(e_first - s_first);
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Time first pass = %f", elapsed_first.count() * 1e-9);

    auto s_second = std::chrono::high_resolution_clock::now();

    for (i = h-2; i > 0; i--) {
        for (j = w-2; j > 0; j--) {
            int32_t c00 = dt[i*w+(j+1)]+3;
            int32_t c01 = dt[(i+1)*w+(j-1)]+4;
            int32_t c02 = dt[(i+1)*w+j]+3;
            int32_t c10 = dt[(i+1)*w+(j+1)]+4;
            int32_t c11 = dt[i*w+j];

            int32_t min_a = c00<c01?c00:c01;
            int32_t min_b = c02<c10?c02:c10;
            int32_t min_ab = min_a<min_b?min_a:min_b;
            int32_t min_sum = min_ab<c11?min_ab:c11;
            dt[i*w+j] = min_sum;
        }
    }

    auto e_second = std::chrono::high_resolution_clock::now();
    auto elapsed_second = std::chrono::duration_cast<std::chrono::nanoseconds>(e_second - s_second);
    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Time second pass = %f", elapsed_second.count() * 1e-9);
}

内容的提问来源于stack exchange,提问作者Antonio Lopes

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 08:24:21