Página 1 de 2

Un poco de ensamblador...

Publicado: Sab, 28 Feb 2015, 01:27
por elborra
Voy a ver si vuelvo poquito a poco a la carga,

Estoy intentando hacer mejoras al sistema de cargas de pantallas que tenía hecho (por motivos de rendimiento y blablabla). Para ello estoy rehaciendo una serie rutinas en c que implicaba estar continuamente paginando aún estando la mayor parte del tiempo usando la misma página de memoria (no entraré en detalles para no volveros locos con datos innecesarios).

Para evitar esto estoy 'trasladando' (no realmente porque cambian cosas) a ensamblador algunas rutinas. Una de ellas requiere la copia de bloques de bytes desde una dirección a otra; pero me estoy volviendo loco :cry:

- Una función que copie bloques de 32 bytes N veces. N es constante.
- La dirección de memoria destino se conoce y es fija. Los N bloques de 32 bytes se colocan sucesivamente
- La dirección de origen se conoce pero no es fija, hay que añadirle un offset. Dicho offset es diferente para cada bloque.

En principio los offsets vendrian representados por un array declarado en c (pero estoy abierto a otras posibilidades).
$this->bbcode_second_pass_code('', 'unsigned char offsets [N] = {4,10,5,6,7}')
Pero a la hora de interpretar los valores contenidos en el array habría que multiplicar por 32 cada valor (si es más rápido siempre podría hacer el array de enteros y guardar el valor real necesitado desde un principio)
$this->bbcode_second_pass_code('', '

offset[0]=128
offset[1]=320
offset[2]=160
... etc
')

Ok, hasta aquí la teoría, en la práctica entiendo que puedo copiar bloques facilmente con ldir (que no se si será lo más rápido); Así, en el ejemplo siguiente se copiarian 32 bytes consecutivos desde 56000-56031 a 20000-20031:
$this->bbcode_second_pass_code('', '
ld hl, 56000
ld de, 20000
ld bc,31
ldir
')

Ok, repetir esto N veces (perdonad mi ignorancia), pero podria usar el registro a y un salto condicional. Así:
$this->bbcode_second_pass_code('', '
ld a, 5

ld de, 20000

.bucle
ld hl, 56000
ld bc,31
ldir

dec a
jr nz, bucle

')
En este caso, y al sacar ld de,20000 del bucle y aprovechar que ldir autoincrementa el registro de, conseguiria copiar 5 veces el mismo bloque de 32 bytes (56000-56031) al 20000-20159 (160 bytes)

A partir de aquí he hecho multitud de pruebas para añadir el offset a hl y obtener asi la dirección origen correcta, pero no hay manera. En pseudocódigo tendría que hacer lo siguiente:
$this->bbcode_second_pass_code('', '
DESTINO = 20000
PARA N VECES
ORIGEN = 56000 + offset[N]*32
COPIAR 32 BYTES DESDE ORIGEN A DESTINO
DESTINO = DESTINO + 32
SIGUIENTE N
')
Alguna ayudita please :picha:

PD: edito para corregir alguna cosa y aprovecho para lanzar otra pregunta ya que lo que pretendo únicamente en NO paginar continuamente tal como ahora hago:
$this->bbcode_second_pass_code('', '
void CopiaBloque(unsigned int num) {
ram_page [0] = 3
ram_address [0] = 56000+(offset[num]<<5);
ram_destination [0] = (unsigned int) (destino+(num<<5));

#asm
di
ld a, (_ram_page)
ld b, a
call SetRAMBank

ld hl, (_ram_address)
ld de, (_ram_destination)
ld bc,31
ldir

ld b, 0
call SetRAMBank
ei
#endasm
};
')
donde destino se declaró como (por ejemplo):
$this->bbcode_second_pass_code('', 'unsigned char destino[16] = {0,1,2,3,15,38,44,2,0,12,11,90,100,101,102,103} ')
y llamar a dicha función en un bucle:
$this->bbcode_second_pass_code('', '
for(i=0;i<16;i++) {
CopiaBloque(i);
}
')
¿Realmente ganaría (en ciclos de reloj, pero algo humanamente perceptible) haciendolo todo en asm (en una hipotética función CopiarBloqueS(void) {}; ) evitando paginar con SetRAMBank cada vez que llamo a CopiaBloque? Hay que tener en cuenta, para mi caso, que finalmente se copiarian entre pantalla y pantalla (de un mismo nivel) en torno a 60 bloques de 32 bytes.

Re: Un poco de ensamblador...

Publicado: Lun, 02 Mar 2015, 10:07
por na_th_an
A mí me pillas super espeso, pero vamos a intentarlo :lol:

$this->bbcode_second_pass_quote('elborra', '[')...]Pero a la hora de interpretar los valores contenidos en el array habría que multiplicar por 32 cada valor (si es más rápido siempre podría hacer el array de enteros y guardar el valor real necesitado desde un principio)
$this->bbcode_second_pass_code('', '

offset[0]=128
offset[1]=320
offset[2]=160
... etc
')


En este tipo de casos la regla es muy sencilla: si puedes precalcular, precalcula. Es tontería hacer que un programa calcule todas las veces valores que van a ser constantes. Si te parece poco legible, z88dk resuelve correctamente esto precalculando en tiempo de compilación (en el binario va el resultado, así que no se pierde tiempo):

unsigned char offsets [N] = {4*32,10*32,5*32,6*32,7*32};

$this->bbcode_second_pass_quote('elborra', 'e')n la práctica entiendo que puedo copiar bloques facilmente con ldir (que no se si será lo más rápido);


Es más rápido poner 32 ldi seguidos en el código. Pero luego hay que ver si merece la pena el despliegue o no. En una función que se ejecutase dentro del código del juego, yo lo haría. En algo que se ejecute fuera, donde el cambio es menos perceptible, no.

$this->bbcode_second_pass_quote('elborra', ' ')Así, en el ejemplo siguiente se copiarian 32 bytes consecutivos desde 56000-56031 a 20000-20031:
$this->bbcode_second_pass_code('', '
ld hl, 56000
ld de, 20000
ld bc,31
ldir
')


Que me corrijan si me equivoco, pero para copiar 32 bytes tienes que poner un 32 en BC.

$this->bbcode_second_pass_quote('elborra', 'A') partir de aquí he hecho multitud de pruebas para añadir el offset a hl y obtener asi la dirección origen correcta, pero no hay manera. En pseudocódigo tendría que hacer lo siguiente:
$this->bbcode_second_pass_code('', '
DESTINO = 20000
PARA N VECES
ORIGEN = 56000 + offset[N]*32
COPIAR 32 BYTES DESDE ORIGEN A DESTINO
DESTINO = DESTINO + 32
SIGUIENTE N
')


Mi kung fu no es demasiado bueno, pero yo lo haría así (con los offsets precalculados) (no está probado, puede estar mal)

$this->bbcode_second_pass_code('', ' ld a, 5
ld de, 20000


.bucle
; Calculamos de donde tenemos que leer
ld h, 0
ld l, a
add l, a ; hl = a*2
add hl, _offsets ; hl = _offsets + a*2
ld c, (hl)
inc hl
ld b, (hl) ; bc contiene el offset
ld l, c
ld h, b ; hl contiene el offset
add hl, 56000 ; hl = 56000 + offset

ld bc, 32
ldir
dec a
jr nz, bucle
ret')

De hecho, ya que vas a precalcular, suma el 56000 en los offsets y ahórrate el add hl, 56000.

$this->bbcode_second_pass_quote('elborra', 'P')D: edito para corregir alguna cosa y aprovecho para lanzar otra pregunta ya que lo que pretendo únicamente en NO paginar continuamente tal como ahora hago:
$this->bbcode_second_pass_code('', '
void CopiaBloque(unsigned int num) {
ram_page [0] = 3
ram_address [0] = 56000+(offset[num]<<5);
ram_destination [0] = (unsigned int) (destino+(num<<5));

#asm
di
ld a, (_ram_page)
ld b, a
call SetRAMBank

ld hl, (_ram_address)
ld de, (_ram_destination)
ld bc,31
ldir

ld b, 0
call SetRAMBank
ei
#endasm
};
')
donde destino se declaró como (por ejemplo):
$this->bbcode_second_pass_code('', 'unsigned char destino[16] = {0,1,2,3,15,38,44,2,0,12,11,90,100,101,102,103} ')
y llamar a dicha función en un bucle:
$this->bbcode_second_pass_code('', '
for(i=0;i<16;i++) {
CopiaBloque(i);
}
')
¿Realmente ganaría (en ciclos de reloj, pero algo humanamente perceptible) haciendolo todo en asm (en una hipotética función CopiarBloqueS(void) {}; ) evitando paginar con SetRAMBank cada vez que llamo a CopiaBloque? Hay que tener en cuenta, para mi caso, que finalmente se copiarian entre pantalla y pantalla (de un mismo nivel) en torno a 60 bloques de 32 bytes.

"Humanamente perceptible" depende de donde se ejecute este código. Si es al cambiar la pantalla, yo no me quebraría la cabeza, directamente. No creo que sea perceptible.

Ahora bien, sí que sería más rápido y quizá ahorrases bytes. Ten en cuenta que, de entrada, el bucle for(i = 0; i < 16; i ++), en especial si "i" es una variable local, ocupará mucho más espacio y tiempo que el bucle con el registro A que estás proponiendo en ensamblador.

Re: Un poco de ensamblador...

Publicado: Mar, 03 Mar 2015, 23:26
por elborra
ok, muchas gracias na_th_an. Queria responderte antes pero sólo me ha dado tiempo a hacer un par de pruebas del tema.

Hay alguna instrucción que no es válida como "add hl, 56000", si no me equivoco, ya que no acepta valores directamente (habría que cargar el valor en otro registro, p.ej BC y luego hacer la suma)

De momento no me hace falta nada más, sigo probando cuando pueda y ya te respongo correctamente. :vahka:

Publicado: Mié, 04 Mar 2015, 09:14
por na_th_an
Tienes razón. Es que vengo de una temporada programando para 6509 y este suele ser más flexible con esas cosas :-)

Publicado: Mié, 04 Mar 2015, 09:22
por na_th_an
Si de verdad quieres ganar un montón de velocidad y espacio (sobre todo, espacio) y tienes tiempo y un buen editor de texto, hay dos cosas que puedes hacer :

1. Eliminar todas las variables locales y sustituirlas por globales. Hay muchas que se pueden reaprovechar. El acceso a una global es más rápido y ocupa menos instrucciones.

2. Cargarte todas las estructuras que puedas, por ejemplo player, y cambiarlo por globales normales. Esto lo puedes conseguir sustituyendo player.x por player_x y todas las demás. Hay algunas que no podrás eliminar, como las que escribe el conversor de mapas o el colocador, pero sólo con cambiar player y en_an, y bullets si lo utilizas, ganarás un montón.
Y si te sientes generoso podemos sacar eso como nueva versión de la churrera :-P

Re: Un poco de ensamblador...

Publicado: Mié, 04 Mar 2015, 23:52
por elborra
Ufff... antes de meterme tan de lleno tendría que ver como van los cambios e ir probando el rendimiento.

Sigo, lamentablemente, dandole vueltas al asunto, porque no hay manera. He probado tu código pero sigue sin copiar correctamente. Parece que el problema principal es que no se como ir accediendo a los datos del "array" consecutivamente.

Este código está fuera de la función, en otro lado totalmente distinto (de hecho en otro fichero .h). Estos valores pueden cambiar a lo largo del juego, pero inicialmente le damos esos valores
$this->bbcode_second_pass_code('', '._ram_datos
defw 0*32, 1*32, 7*32, 3*32, 4*32, 5*32, 6*32, 16*32;')
Dentro ya de la función propiemente dicha no encuentro la manera de que a cada iteracion del bucle (que realizamos con el registro A) pueda acceder consecutivamente a:
ram_datos[0] -> ram_datos -> 0*32
ram_datos[1] -> ram_datos+2? -> 1*32
ram_datos[2] -> ram_datos+4? -> 7*32
...
ram_datos[7] -> ram_datos+14? -> 16*32
y poder sumarle dicho valor como offset para calcular el origen de los datos a copiar (con ldi o ldir) dejando HL como 56000+offset

Por cierto, "ret" realmente que hace ¿es necesario?, porque en algunas ocasiones dejar esta linea hace que se cuelgue el juego en cuando ejecuta la funcion de copiar bloques; y quitándola vuelve a funcionar (independientemente de que no hace lo que quiero xD )

Re: Un poco de ensamblador...

Publicado: Jue, 05 Mar 2015, 08:15
por na_th_an
La forma de hacerlo que yo sé es la que te he puesto que, por supuesto, puede tener algún error. ¿Has mirado con el debugger de Spectaculator (CTRL+ENTER) cómo se va ejecutando el tema? Cuando empiezas con el ensamblador tienes que tener a mano el debugger.

Vale, no sé cual es mi problema, así que voy a orientarlo de otra forma :lol: . De entrada, 'a' es el acumulador, es el registro más versátil, no deberías desperdiciarlo en un iterador - para eso puedes usar B que además te sirve para terminar el bucle con un DJNZ. Prueba así. Seguro que viene alguien a reñirme por usar la pila. Que venga, te dará un código mejor que el mío. Y a lo mejor funciona y todo :lol:

$this->bbcode_second_pass_code('', ' ld b, 5 ; Contamos con b, mejor.
ld de, 20000
ld hl, _offsets ; Vamos a ir leyendo de aquí los offsets

.bucle
; Salvamos el contador
push bc

; Leemos un nuevo offset
ld c, (hl)
inc hl
ld b, (hl)
inc hl ; Siguiente offset

; Offset en bc, sumamos 56000.
push hl ; Antes salvamos hl
ld hl, 56000
add hl, bc

; Copiamos 32 bytes
ld bc, 32
ldir

; Recuperamos nuestro puntero a _offsets
pop hl

; Recuperamos el contador y cerramos el bucle
pop bc
djnz bucle ; Decrementa b y salta si es != 0')

Lo del ret, depende. Si has llegado al bloque #asm porque viene a continuación de código C, de ninguna manera. Si has llegado con un call desde ensamblador, es necesario para poder volver.

Sobre los otros cambios que te propongo, es cuestión de find & replace con un buen editor de textos. Yo lo hice usando code::blocks, que me permitía cargar todos los archivos y hacer cambios globales. Y sí, te aseguro que así ocupa menos y se ejecuta más rápido. De hecho, fue la base de MK2. Lo primero que hicimos fue optimizar así la Churrera. Luego ya empezamos a reescribir rutinas.

A lo mejor te interesaría portar a MK2, en realidad...

Re: Un poco de ensamblador...

Publicado: Jue, 05 Mar 2015, 13:44
por elborra
$this->bbcode_second_pass_quote('na_th_an', 'A') lo mejor te interesaría portar a MK2, en realidad...

Pues no te creas que le eché un vistazo al código de la 4 parte de Leovigildo; el problema era meterle todas las modificaciones (mapa comprimido de Antonio, cambio tamaño zona juego, plataformas en "genital", los bloques arrastrables, scripts en memoria extendida, personajes con textos animados, puentes, set de tiles..) que ya ni me acuerdo de muchas de ellas U_U y tengo que mirarmelo 20 veces para saber que es lo que estaba haciendo, como y el porque XD. Vamos que le metí un repaso a las estructuras principales para adecuarlo a mis necesidades, obviado el generador de niveles de 128k y haciendo mi propio "crearNiveles" XD.

Sip, esto quiere decir que a cada carga de pantalla se ve cambiaban tiles y atributos (descomprimiendo), sprites de enemigos (descomprimiendo), scripts para esa pantalla concreta, enemigos para esa pantalla concreta, y alguna cosa más.. ¡y oye! ¡que la transición entre pantallas era bastante buena! :shock: (teniendo en cuenta que no necesitas la rapidez de cambio de un plataformas), pero lo suficientemente "lenta" como para que se quedara pillada la música en alguna nota durante ese tiempo (hasta volver a RAM0 y habilitar las interrupciones)

De ahí que al retomarlo este empezando de "0" a ver si con lo aprendido podía distribuir mejor todo pero al menos con la misma "base". Es cierto que a simple vista el código de MK2 se ve todo más ordenado y distribuido en más ficheros, pero claro el haber estado buceando tanto en la churrera me ha pasado factura, y no se por donde empezar a meterle mano xD.

Re: Un poco de ensamblador...

Publicado: Jue, 05 Mar 2015, 18:55
por na_th_an
Sobre los tirones, mira el hilo de pentacorn. Creo que los dejamos al mínimo con un par de historias. Si consigues que el tiempo que están las interrupciones deshabilitadas sea menos de un frame, con un halt en el sitio correcto lo solucionas.

Re: Un poco de ensamblador...

Publicado: Vie, 06 Mar 2015, 18:02
por elborra
Lo has clavado de esta otra forma na_ta_an, yo ya me hebía metido con push y pop pero no daba con un código correcto (Algo estaría haciendo mal U_U )

Como emulador estaba usando el Fuse pero el debugger es muy simple y sólo puedor hacer paso a paso (no si se puede poner algún punto de ruptura y tampoco lo miré mucho), me pasaré al Spectacular a ver si puedo controlarlo medianamente.

$this->bbcode_second_pass_quote('na_th_an', 'L')a forma de hacerlo que yo sé es la que te he puesto que, por supuesto, puede tener algún error. ¿Has mirado con el debugger de Spectaculator (CTRL+ENTER) cómo se va ejecutando el tema? Cuando empiezas con el ensamblador tienes que tener a mano el debugger.

Vale, no sé cual es mi problema, así que voy a orientarlo de otra forma :lol: . De entrada, 'a' es el acumulador, es el registro más versátil, no deberías desperdiciarlo en un iterador - para eso puedes usar B que además te sirve para terminar el bucle con un DJNZ. Prueba así. Seguro que viene alguien a reñirme por usar la pila. Que venga, te dará un código mejor que el mío. Y a lo mejor funciona y todo :lol:

$this->bbcode_second_pass_code('', ' ld b, 5 ; Contamos con b, mejor.
ld de, 20000
ld hl, _offsets ; Vamos a ir leyendo de aquí los offsets

.bucle
; Salvamos el contador
push bc

; Leemos un nuevo offset
ld c, (hl)
inc hl
ld b, (hl)
inc hl ; Siguiente offset

; Offset en bc, sumamos 56000.
push hl ; Antes salvamos hl
ld hl, 56000
add hl, bc

; Copiamos 32 bytes
ld bc, 32
ldir

; Recuperamos nuestro puntero a _offsets
pop hl

; Recuperamos el contador y cerramos el bucle
pop bc
djnz bucle ; Decrementa b y salta si es != 0')

Lo del ret, depende. Si has llegado al bloque #asm porque viene a continuación de código C, de ninguna manera. Si has llegado con un call desde ensamblador, es necesario para poder volver.

Sobre los otros cambios que te propongo, es cuestión de find & replace con un buen editor de textos. Yo lo hice usando code::blocks, que me permitía cargar todos los archivos y hacer cambios globales. Y sí, te aseguro que así ocupa menos y se ejecuta más rápido. De hecho, fue la base de MK2. Lo primero que hicimos fue optimizar así la Churrera. Luego ya empezamos a reescribir rutinas.

A lo mejor te interesaría portar a MK2, en realidad...