我在当天写的很多6502的理解是,并行数组比存储数据的结构要好。
想象一下,你想要一个怪物统计数据表,在C中定义类似这样的
struct Monster {
unsigned char hitPoints;
unsigned char damage;
unsigned char shieldLevel;
char* name;
};
您可以将其存储为结构数组
static Monster s_monsters[] = {
{ 5, 1, 0, "orc", },
{ 50, 10, 5, "dragon", },
{ 10, 3, 1, "goblin", },
};
或者您可以将其存储为并行数组(通常使用宏或工具生成)。注意:我在C中显示代码,但请想象它是6502汇编。
unsigned char Monster_hitPoints[] = { 5, 50, 10, };
unsigned char Monster_damage[] = { 1, 10, 3, },
unsigned char Monster_sheildLevel[] = { 0, 5, 1, };
unsigned char Monster_nameLow[] = {
&m_orc_name & 0xFF,
&m_dragon_name & 0xFF,
&m_goblin_name & 0xFF,
};
unsigned char Monster_nameHigh[] = {
&m_orc_name >> 8 ,
&m_dragon_name >> 8,
&m_goblin_name >> 8,
};
在6502中,给定itemNdx,您可以访问具有此类
的并行数组的所有字段ldx itemNdx
lda Monster_hitPoints,x ; access hitpoints
...
lda Monster_damage,x ; access damage
...
lda Monster_shieldLevel,x ; access shieldLevel
...
lda Monster_nameLow,x ; access name
sta pageZeroStringPointer
lda Monster_nameHigh,x
sta pageZeroStringPointer + 1
ldy #0
lda (pageZeroStringPointer),y
如果您使用结构而不是并行数组,则它变为
lda itemNdx
clc ; have to compute offset
asl a ; a = itemNdx * 2
asl a ; a = itemNdx * 4
adc itemNdx ; a = itemNdx * 5
tax ; x = itemNdx * 5
lda s_monsters+Monster.hitPoints,x ; access hitpoints
...
lda s_monsters+Monster.damage,x ; access damage
...
lda s_monsters+Monster.shieldLevel,x ; access shieldLevel
...
lda s_monsters+Monster.name,x ; access name
sta pageZeroStringPointer
lda s_monsters+Monster.name+1,x
sta pageZeroStringPointer + 1
ldy #0
lda (pageZeroStringPointer),y ; a is now first char of name
结构版本必须计算每个结构的偏移量。在上面的情况下,与并行阵列版本相比,还有5个指令。最重要的是,用于计算偏移的数学是手动编码的,这意味着如果结构发生变化,则必须在大小的任何时候重写它。最重要的是,您只能拥有256 / sizeof(Monster)
大的表格。如果你有更多的字段(20到30并不罕见),这意味着你的表只能有8到12个条目,而对于并行数组,你可以有256个条目。如果您想迭代表,还有一个优点。对于并行数组,您只需增加x inx
一条指令。使用结构你必须添加sizeof(怪物),只添加一个将是
txa
clc
adc #sizeof(Monster)
tax
这是比并行阵列版本多3个指令。
似乎并行数组是6502汇编语言的客观胜利,但John Carmack从his plan file
中得到了这个模糊的评论。...实际上,在Apple II汇编语言中一直回到了解并行数组结构的优点...... ...
有谁知道这些优点是什么?
我能想到的唯一优势是,使用结构数组分配动态数组更容易,但大多数游戏在6502天内没有分配任何东西。他们硬编码修复了大小的内存数组,因此看起来不像是这样。 6502没有缓存,因此没有缓存优势。
如果你完全使用指针,你可以处理超过256个项目,但完全指针太多更慢,需要多多于上面显示的任何一种方法因此,它们通常是最后的选择。
; setup pointer
lda itemNdx
ldx #sizeof(Monster)
jsr multiplyAX ; 8->16 bit multiply is around 70 cycles result in A(low), X(high)
clc
adc #s_monster && 0xFF
sta POINTER
txa
adc #s_monster >> 8
sta POINTER + 1
ldy #Monster.hitPoints ; access hitpoints
lda (POINTER),y
...
ldy #Monster.damage ; access damage
lda (POINTER),y
...
ldy #Monster.shieldLevel ; access shieldLevel
lda (POINTER),y
...
ldy #Monster.name ; access name
lda (POINTER),y
sta pageZeroStringPointer
ldy #Monster.name+1
lda (POINTER),y
sta pageZeroStringPointer + 1
ldy #0
lda (pageZeroStringPointer),y ; a is now first char of name
你可以通过制作每个项目的并行指针数组来摆脱相乘。您仍然需要两行并行数组不需要的设置,并且您仍然会使其余代码变得越来越慢。每次访问8个周期,每次访问5个和5个字节,相对于3个。
基本上,如果你绝对必须,你只会使用指针。如果您可以选择并行数组,那么您似乎应该总是选择它们。
答案 0 :(得分:2)
使用绝对寻址,并行数组在一组固定的参数内工作得非常快。但是,当你超越它并且必须使用零页索引时,表就会被转换。
; Assuming MONSTER_PTR is zp, set to the start of the current structure
ldy #Monster.hitPoints
lda (MONSTER_PTR),y
...
ldy #Monster.damage
lda (MONSTER_PTR),y
对于超出单页限制的并行数组,必须为每个数组重置一个指针。此外,一旦指针在进行中,长索引计算可以用预先计算的指针的简单移动或单个移位到索引指针表来替换。
鉴于优势(至少在他使用的灵活性方面),动态分配项目的能力来自免费赠品。他的写作并不清楚,但这似乎是他所得到的。
答案 1 :(得分:1)
唯一的一点是,并行数组比结构访问更好(如果你有到每个数组的现成设置指针,则需要在每次访问时计算当前怪物的地址)零页面:
; set up zero page, done only once per level
; (per level, per stage, per dungeon, whatever)
lda #<monster_HP_list_for_this_level
sta $f0
lda #>monster_HP_list_for_this_level
sta $f1
lda #<monster_damage_list_for_this_level
sta $f2
lda #>monster_damage_list_for_this_level
sta $f3
lda #<monster_shield_list_for_this_level
sta $f4
lda #>monster_shield_list_for_this_level
sta $f5
...
; here you'd have instant access to the monster's values :
lda ($F0),y ; monster Nr. y's HP
; or
lda ($F4),y ; monster Nr. y's Shield level
BUT: 你可能只有256个怪物(还有很多免费的zeropage地址,这不应该是一个很大的问题)
在每一个新情况下(例如你到达一个新的地图,输入一个新的地牢等)零页面指针可以更新,指向新级别的怪物