Что вызывает периодические скачки производительности, которые наблюдаются при выполнении вычислительно дорогостоящей обработки массивов на Nexus 4?

Я новичок в потоках (не убивайте меня за мою реализацию ниже:), и мне нужно сделать несколько проходов размытия пикселей в отдельном потоке (см. Ниже). Это не самая эффективная реализация размытия рамки (это из Gaussian Filter без использования 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 (мс):

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 (мс):

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

1 ответ

Решение

Я думаю, что я, возможно, несколько смягчил этот эффект на 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);

    }

}

native.c


(который должен быть помещен в папку jni в корне проекта Android)

#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


(который должен быть помещен в папку jni в корне проекта Android)

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% и уменьшена изменчивость на порядок.

Код компилируется путем выполнения ndk-build команда из библиотеки NDK, предоставляемая Android в корневой папке (находится здесь: http://developer.android.com/tools/sdk/ndk/index.html).


Результаты


Нексус 4 (мс):

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

Нексус 7 (мс):

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
Другие вопросы по тегам