Android triple buffering – comportamiento esperado?
Estoy investigando el rendimiento de mi aplicación, ya que me di cuenta de que cayó algunos marcos durante el desplazamiento. Corrí systrace (en un Nexus 4 corriendo 4.3) y noté una sección interesante en la salida.
Todo está bien al principio. Zoom en la sección izquierda , podemos ver que el dibujo comienza en cada vsync, termina con tiempo de sobra, y espera hasta el próximo vsync. Dado que es triple buffer, debe dibujar en un búfer que se publicará en el siguiente vsync después de que se hace.
- Cómo limitar la velocidad de la red del dispositivo Android para propósitos de prueba
- ¿Es Hibernate un exceso para una aplicación de Android?
- Android Studio muestra que el límite de inotify de advertencia es demasiado bajo
- Múltiples declaraciones if
- Comportamiento de vista Web con Galaxy S cargar página web toma edades
En el 4º vsync en la captura de pantalla ampliada, la aplicación funciona y la operación de dibujo no finaliza a tiempo para el siguiente vsync. Sin embargo, no eliminamos ninguna trama porque los dibujos anteriores estaban trabajando un marco por delante.
Después de esto sucede sin embargo, las operaciones de dibujo no compensan el vsync perdido. En su lugar, sólo una operación de dibujo comienza por vsync, y ahora no están dibujando un marco adelante más.
Zoom en la sección de la derecha , la aplicación hace un poco más de trabajo y se pierde otro vsync. Puesto que no estábamos dibujando un marco adelante, un marco realmente se deja caer aquí. Después de esto, se remonta a dibujar un fotograma por delante.
¿Es este comportamiento esperado? Mi comprensión era que el almacenar intermediario triple permitió que usted recuperara si usted faltó a un vsync, pero este comportamiento parece que cae un marco una vez cada dos vsyncs que usted falta.
Preguntas de seguimiento
-
En la parte derecha de esta captura de pantalla , la aplicación está procesando buffers más rápido de lo que la pantalla los está consumiendo. Durante realizarTraversals # 1 (etiquetado en la captura de pantalla), digamos que se está visualizando el buffer A y se está procesando el buffer B. # 1 termina mucho antes del vsync y pone el búfer B en la cola. En este punto, ¿no debería la aplicación ser capaz de iniciar inmediatamente el renderizado de la memoria intermedia C? En su lugar, performTraversals # 2 no se inicia hasta el próximo vsync, perdiendo el valioso tiempo entre ellos.
-
En una vena similar, estoy un poco confundido acerca de la necesidad de waitForever en el lado izquierdo aquí . Digamos que se está mostrando el búfer A, el búfer B está en la cola y el búfer C está siendo procesado. Cuando el búfer C está finalizado, ¿por qué no se agrega inmediatamente a la cola? En su lugar, hace un waitForever hasta que el búfer B se elimina de la cola, momento en el que agrega el búfer C, por lo que la cola parece estar siempre en el tamaño 1, no importa lo rápido que la aplicación esté procesando buffers.
- Java MappedByteBuffer.get () sorprendentemente lento
- ¿Es bueno llamar a findViewById cada vez en el ciclo de vida de la actividad cuando sea necesario?
- AdView ralentiza la aplicación completa, posible razón
- Por qué getSpeed () siempre devuelve 0 en android
- ¿Existe una herramienta para eliminar las variables no deseadas, no utilizadas
- Google Maps v2 Projection.toScreenLocation (...) muy lento
- ¿Cómo almacenar en caché y almacenar objetos y establecer una política de expiración en android?
- Cómo leer '/data/anr/traces.txt' en samsung S2
La cantidad de almacenamiento en búfer proporcionado sólo importa si mantiene los búferes llenos. Eso significa que el renderizado más rápido que la pantalla los está consumiendo.
Las etiquetas no aparecen en las imágenes, pero supongo que la fila púrpura sobre la fila verde vsync es el estado BufferQueue. Puede ver que generalmente tiene 0 o 1 buffers completos en cualquier momento. A la izquierda de la imagen "ampliada a la izquierda" se puede ver que tiene dos búferes, pero después de eso sólo tiene uno, y 3/4 del camino a través de la pantalla se ve una barra púrpura muy corta la Indica que acaba de representar el marco en el tiempo.
Vea este post y este post como fondo.
Actualización para las preguntas añadidas …
El detalle en el otro poste apenas arañó la superficie. Debemos profundizar.
El número de BufferQueue mostrado en systrace es el número de buffers en cola, es decir, el número de buffers que tienen contenido en ellos. Cuando SurfaceFlinger agarra un búfer para mostrarlo, libera el búfer inmediatamente, cambiando su estado a "libre". Esto es particularmente emocionante cuando se muestra el búfer en una superposición, porque la visualización se representa directamente desde el búfer (en contraposición a la composición en un búfer de borrador y mostrarlo).
Permítanme decirlo de nuevo: el búfer desde el cual la pantalla está leyendo activamente los datos para su visualización en la pantalla está marcado como "libre" en el BufferQueue. El búfer tiene una valla asociada que es inicialmente "activa". Mientras está activo, nadie está autorizado a modificar el contenido del búfer. Cuando la pantalla ya no necesita el búfer, señala la valla.
Así que la razón por la que el código a la izquierda de su rastreo está en waitForever()
es porque está a la espera de la valla para señalar. Cuando VSYNC llega, la pantalla cambia a un búfer diferente, señala la valla, y su aplicación puede comenzar a usar el búfer inmediatamente. Esto elimina la latencia que se produciría si tuviera que esperar a que SurfaceFlinger se despierte, ver que el búfer ya no estaba en uso, enviar un IPC a través de BufferQueue para liberar el búfer, etc.
Tenga en cuenta que las llamadas a waitForever()
sólo aparecen cuando no se está cayendo detrás (lado izquierdo y lado derecho de la traza). No estoy seguro de improviso por qué está sucediendo en absoluto cuando la cola tiene sólo 1 buffer completo – debe ser dequeueing el más antiguo de amortiguación, que ya debería haber señalado.
La conclusión es que nunca verá el BufferQueue ir por encima de dos para el almacenamiento temporal triple.
No todos los dispositivos funcionan como se describe anteriormente. Nexus 7 (2012) no utiliza el mecanismo de "sincronización explícita", y los dispositivos pre-ICS no tienen BufferQueues en absoluto.
Volviendo a su captura de pantalla numerada, sí, hay mucho tiempo entre '1' y '2' donde su aplicación podría ejecutar performTraversals (). Es difícil decir con certeza sin saber lo que está haciendo su aplicación, pero me imagino que tienes un ciclo de animación impulsado por Choreographer que despierta cada VSYNC y funciona. No funciona más a menudo que eso.
Si sincroniza Android Breakout , puede ver lo que parece cuando renderiza lo más rápido posible ("cola de relleno") y confía en la presión de retorno de BufferQueue para regular la velocidad del juego.
Es especialmente interesante comparar N4 corriendo 4.3 con N4 que funciona 4.4. En 4,3, la traza es similar a la tuya, con la cola en su mayor parte flotando a 1, con gotas regulares a 0 y picos ocasionales a 2. En 4.4, la cola es casi siempre a 2 con una caída ocasional a 1. En ambos casos es eglSwapBuffers()
en eglSwapBuffers()
; En 4.3 la traza generalmente muestra waitForever()
debajo de eso, mientras que en 4.4 muestra dequeueBuffer()
. (No sé la razón de esto de improviso.)
Actualización 2: La razón de la diferencia entre 4,3 y 4,4 parece ser un cambio de controlador Nexus 4. El controlador 4.3 utilizó la llamada dequeueBuffer antigua, que se convierte en dequeueBuffer_DEPRECATED()
( Surface.cpp línea 112 ). La interfaz antigua no toma la valla como un parámetro "out", por lo que la llamada tiene que llamar a waitForever()
sí. La nueva interfaz acaba de devolver la valla al controlador GL, que hace la espera cuando es necesario (que puede no ser de inmediato).
Actualización 3: Una explicación aún más larga ya está disponible aquí .
- La clase generada para Component of Dagger 2 no se encuentra en compileTestJava del complemento Java de Gradle
- Aplicación que se bloquea sólo en dispositivos lollipop