Un poco de ensamblador...

Soporte técnico sobre los lanzamientos de MojonTwins y comentarios sobre los güegos. Ofrecemos soporte técnico con Fourspriter, te ayudamos con ZX Basic o Z88DK, te damos pistas some cómo saltarse un bicho y cosas así.

Moderador: na_th_an

Avatar de Usuario
elborra
Mensajes: 209
Registrado: Dom, 12 Ene 2014, 14:37

Un poco de ensamblador...

Mensajepor elborra » Sab, 28 Feb 2015, 01:27

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.
Avatar de Usuario
na_th_an
Mensajes: 26413
Registrado: Vie, 09 Ene 2009, 12:18

Re: Un poco de ensamblador...

Mensajepor na_th_an » Lun, 02 Mar 2015, 10:07

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.