¿Cuál es la causa de los picos de rendimiento periódicos que se ven al hacer el procesamiento de matriz de computación caro en el Nexus 4?

Soy nuevo en los hilos (no me mates para mi implementación a continuación 🙂 y tengo que hacer varios borrones de píxeles en un hilo separado (véase a continuación). No es la implementación más eficiente del desenfoque de la caja (es de Gaussian Filter sin usar ConvolveOp ), pero los picos de rendimiento no se producen en la tableta Nexus 7, pero sí ocurren en el teléfono Nexus 4.

He publicado mi muestra de prueba (en ejecución en Android 4.2 – ver más abajo).

No creo que sea causado por el GC golpeando la memoria (no coincide con los picos).

Creo que podría ser algo que ver con la memoria caché localidad o la memoria del hardware thrashing – pero no estoy seguro.

¿Qué causaría los picos? A veces son de aparición repentina – por ejemplo, pico de 50%. A veces son de inicio lento – por ejemplo, picos de aumento / disminución monótona, con picos de la siguiente manera -> 5%, 10%, 20%, 10%, 5%.

¿Cómo podría evitar que se produzcan cuando se realiza el procesamiento de matrices pesadas?

Esto no ocurre en la tableta Nexus 7 que también he probado (ver los resultados a continuación)

Pregunta secundaria: ¿Cuál es la mejor manera de dormir y reiniciar mi hilo correctamente (nuevo en los hilos)?


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; } } 

Registros


Nexus 4 teléfono (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 comprimido (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 

Creo que puede haber mitigado un poco este efecto en el Nexus 4. Todavía hay cierta variabilidad en la consistencia del cálculo, pero es soportable – creo – no se pueden ver demasiados picos enormes – fuera del inicio / cierre del hilo. He hecho esto usando el NDK de Android y c p_threads para generar un hilo nativo que es en su mayoría abandonado por Java (o así me dicen) hasta que la aplicación de primer plano se cambia o se cierra.

Aquí está el código:


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


(que se debe poner en una carpeta jni en la raíz del proyecto de 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


(que se debe poner en una carpeta jni en la raíz del proyecto de 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) 

Resumen


Reducción del coste del código en ~ 20-30% y disminución de la variabilidad en un orden de magnitud.

El código se compila ejecutando el ndk-build de la biblioteca NDK proporcionada por Android en la carpeta raíz (que se encuentra aquí: http://developer.android.com/tools/sdk/ndk/index.html ).


Resultados


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 
  • XPath.Evaluar el rendimiento se ralentiza (absurdamente) en varias llamadas
  • ¿Por qué usar Fragmentos?
  • Impacto de 'instanceof' en el código Java de Android
  • Android sqlite / problema de rendimiento BLOB
  • El objeto se convierte en nulo
  • Android: RunOnUiThread vs AsyncTask
  • ¿Cuál es la diferencia entre BaseAdapter y ArrayAdapter?
  • Cómo compartir datos con Android Volley
  • ¿Cuáles son los posibles problemas de rendimiento en el uso de python over java para el desarrollo de aplicaciones para Android?
  • Android: WebView mejora la velocidad de carga de los archivos html locales
  • Jackson JSON Parser rendimiento
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.