是什么导致了在Nexus 4上进行计算代价高昂的数组处理时出现的周期性性能峰值?



我是一个线程新手(不要因为我下面的实现杀了我:),我需要在一个单独的线程上对像素进行多次模糊处理(见下文)。这不是最有效的方框模糊实现(它来自高斯过滤器,没有使用ConvolveOp),但性能峰值不会出现在Nexus 7平板电脑上,但会出现在Nexus 4手机上。

我已经发布了我的测试样本(在Android 4.2上运行-见下文)。

我不认为这是由于GC对内存的抖动引起的(它与峰值不一致)。

我认为这可能与缓存局部性或硬件内存抖动有关-但我不确定。

是什么导致了峰值?有时它们是突然发作的——例如50%的峰值。有时它们是缓慢发作的-例如峰值单调地增加/减少,峰值如下-> 5%,10%,20%,10%,5%。

我怎么能阻止他们发生时,做繁重的数组处理?

这在我测试过的Nexus 7平板电脑上没有发生(见下面的结果)

边问题:什么是最好的方法来睡眠和重新启动我的线程正确(新的线程)?

MainActivity.java


package com.example.test;
import android.os.Bundle;
import android.app.Activity;
public class MainActivity extends Activity {
    private MainThread thread;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        thread = new MainThread();
        thread.setRunning(true);
        thread.start();
        setContentView(R.layout.activity_main);
    }
    @Override
    protected void onResume() {
        super.onResume();
        thread.setRunning(true);
    }
    @Override
    protected void onPause() {
        super.onPause();
        thread.setRunning(false);
    }
}

MainThread.java


package com.example.test;
import android.util.Log;
public class MainThread extends Thread {
    int[] pixels;
    int kernel_rows = 2;
    int kernel_cols = 2;
    int width = 512;
    int height = 512;
    @Override
    public void run() {
        while (running) {
            long start = System.currentTimeMillis();
            for (int row = kernel_rows / 2; row < height - kernel_rows / 2; row++) {
                for (int col = kernel_cols / 2; col < width - kernel_cols / 2; col++) {
                    float pixel = 0;
                    // iterate over each pixel in the kernel
                    for (int row_offset = 0; row_offset < kernel_rows; row_offset++) {
                        for (int col_offset = 0; col_offset < kernel_cols; col_offset++) {
                            // subtract by half the kernel size to center the
                            // kernel
                            // on the pixel in question
                            final int row_index = row + row_offset
                                    - kernel_rows / 2;
                            final int col_index = col + col_offset
                                    - kernel_cols / 2;
                            pixel += pixels[row_index * width + col_index] * 1.0f / 4.0f;
                        }
                    }
                    pixels[row * width + col] = (int) pixel;
                }
            }
            long stop = System.currentTimeMillis();
            long delta = stop - start;
            Log.d("DELTA", Long.toString(delta));
        }
    }
    private boolean running;
    public void setRunning(boolean running) {
        this.pixels = new int[512 * 512];
        this.running = running;
    }
}

日志

Nexus 4 phone (ms):

01-13 10:56:05.663: D/DELTA(13507): 76
01-13 10:56:05.773: D/DELTA(13507): 107
01-13 10:56:05.843: D/DELTA(13507): 77
01-13 10:56:05.923: D/DELTA(13507): 75
01-13 10:56:06.053: D/DELTA(13507): 127
01-13 10:56:06.133: D/DELTA(13507): 78
01-13 10:56:06.213: D/DELTA(13507): 81
01-13 10:56:06.293: D/DELTA(13507): 80
01-13 10:56:06.353: D/DELTA(13507): 77
01-13 10:56:06.433: D/DELTA(13507): 79
01-13 10:56:06.513: D/DELTA(13507): 79
01-13 10:56:06.624: D/DELTA(13507): 106
01-13 10:56:06.694: D/DELTA(13507): 76

Nexus 7 tablet (ms):

01-13 11:01:03.283: D/DELTA(3909): 84
01-13 11:01:03.373: D/DELTA(3909): 85
01-13 11:01:03.453: D/DELTA(3909): 85
01-13 11:01:03.543: D/DELTA(3909): 84
01-13 11:01:03.623: D/DELTA(3909): 85
01-13 11:01:03.703: D/DELTA(3909): 84
01-13 11:01:03.793: D/DELTA(3909): 85
01-13 11:01:03.873: D/DELTA(3909): 84
01-13 11:01:03.963: D/DELTA(3909): 85
01-13 11:01:04.043: D/DELTA(3909): 84

我想我可能在某种程度上减轻了Nexus 4的这种影响。在计算一致性方面仍然存在一些可变性,但它是可以忍受的——我认为——在线程启动/关闭之外看不到太多的巨大峰值。我使用Android NDK和c p_threads来生成一个本地线程,直到前台应用程序被更改或关闭,这个本地线程基本上是由Java单独留下的(或者我被告知)。

代码如下:


MainActivity.java


package com.example.test;
import android.os.Bundle;
import android.app.Activity;
public class MainActivity extends Activity {
    static {
        System.loadLibrary("native");
    }
    private native void init();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Initializes and spawns native thread
        init();
        setContentView(R.layout.activity_main);
    }
}

演示


(应该放在Android项目根目录下的jni文件夹中)

#include <time.h>
#include <pthread.h>
#include <jni.h>
#include <android/log.h>
#define APPNAME "DELTA"
int* pixels;
int kernel_rows = 2;
int kernel_cols = 2;
int width = 60;
int height = 39;
int running = 1;
// from android samples
/* return current time in milliseconds */
static double now_ms(void) {
    struct timespec res;
    clock_gettime(CLOCK_REALTIME, &res);
    return 1000.0 * res.tv_sec + (double) res.tv_nsec / 1e6;
}
// initialize thread/begin it
jint Java_com_example_testa_MainActivity_init(JNIEnv* env, jobject javaThis) {
    int i1 = 1;
    pthread_t thread;
    void *run();
    pthread_create(&thread, NULL, run, &i1);
    pthread_join(thread, NULL);
    return 0;
}
// thread function
void *run(int *x) {
    // init pixels within thread
    pixels = (int*) malloc(sizeof(int) * width * height);
    // loop until stopped - java won't interfere
    // unless closed/switch application (or so I'm told)
    while (running) {
        double start = now_ms();
        int row, col, row_offset, col_offset;
        for (row = kernel_rows / 2; row < height - kernel_rows / 2; row++) {
            for (col = kernel_cols / 2; col < width - kernel_cols / 2; col++) {
                float pixel = 0;
                // iterate over each pixel in the kernel
                for (row_offset = 0; row_offset < kernel_rows; row_offset++) {
                    for (col_offset = 0; col_offset < kernel_cols;
                            col_offset++) {
                        // subtract by half the kernel size to center the
                        // kernel
                        // on the pixel in question
                        int row_index = row + row_offset - kernel_rows / 2;
                        int col_index = col + col_offset - kernel_cols / 2;
                        pixel += pixels[row_index * width + col_index] * 1.0f
                                / 4.0f;
                    }
                }
                pixels[row * width + col] = (int) pixel;
            }
        }
        double end = now_ms();
        double delta = end - start;
        __android_log_print(ANDROID_LOG_VERBOSE, APPNAME, "%f", delta);
    }
    pthread_exit(0);
}

Android.mk


(应该放在Android项目根目录下的jni文件夹中)

LOCAL_PATH := $(call my-dir)
MY_PATH := $(LOCAL_PATH)
include $(call all-subdir-makefiles)
include $(CLEAR_VARS)
LOCAL_PATH := $(MY_PATH)
LOCAL_MODULE    := native
LOCAL_LDLIBS := -llog
LOCAL_SRC_FILES := native.c
include $(BUILD_SHARED_LIBRARY)

总结

降低了20-30%的代码成本,并减少了一个数量级的可变性。

代码是通过从Android提供的NDK库(在这里:http://developer.android.com/tools/sdk/ndk/index.html)中执行ndk-build命令编译的。


结果


Nexus 4 (ms):

01-14 13:41:21.132: V/DELTA(23679): 56.554199
01-14 13:41:21.192: V/DELTA(23679): 58.568604
01-14 13:41:21.252: V/DELTA(23679): 59.484131
01-14 13:41:21.302: V/DELTA(23679): 56.768066
01-14 13:41:21.362: V/DELTA(23679): 54.692383
01-14 13:41:21.412: V/DELTA(23679): 51.823730
01-14 13:41:21.472: V/DELTA(23679): 55.668945
01-14 13:41:21.522: V/DELTA(23679): 56.920654
01-14 13:41:21.582: V/DELTA(23679): 56.371094
01-14 13:41:21.642: V/DELTA(23679): 58.507568
01-14 13:41:21.702: V/DELTA(23679): 59.697754
01-14 13:41:21.752: V/DELTA(23679): 53.990723
01-14 13:41:21.812: V/DELTA(23679): 55.669189

Nexus 7 (ms):

01-14 13:41:25.685: V/DELTA(2916): 65.867920
01-14 13:41:25.745: V/DELTA(2916): 65.986816
01-14 13:41:25.815: V/DELTA(2916): 66.685059
01-14 13:41:25.885: V/DELTA(2916): 67.033936
01-14 13:41:25.945: V/DELTA(2916): 65.703857
01-14 13:41:26.015: V/DELTA(2916): 66.653076
01-14 13:41:26.085: V/DELTA(2916): 66.922119
01-14 13:41:26.145: V/DELTA(2916): 67.030029
01-14 13:41:26.215: V/DELTA(2916): 67.014893
01-14 13:41:26.285: V/DELTA(2916): 67.034912
01-14 13:41:26.345: V/DELTA(2916): 67.089844
01-14 13:41:26.415: V/DELTA(2916): 65.860107
01-14 13:41:26.485: V/DELTA(2916): 65.642090
01-14 13:41:26.545: V/DELTA(2916): 65.574951
01-14 13:41:26.615: V/DELTA(2916): 65.991943

最新更新