2017-08-14 86 views
3

从我今天写很多6502的理解来看,并行数组比存储数据的结构要好。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汇编语言客观的胜利,但有约翰·卡马克从his plan file

这个不起眼的评论......其实,早了解结构在平行美德一路在苹果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 

通过为每个项目制作一个平行指针数组,您可以摆脱乘法。你仍然有2行安装程序并行阵列不需要,你仍然会让代码的其他代码变得越来越大。每个访问8个周期vs每个访问5个和5个字节与3.

基本上,如果你绝对必须使用指针。如果你可以选择平行阵列,那么你应该总是选择它们。

+0

问题是X和Y寄存器是8位的,不能用作结构元素的索引。 – i486

+0

这个Q对我来说听起来太泛泛。如果你的任务允许的话,有更多的选择如何硬编码某些技巧,如果你的任务允许的话,我不熟悉6502,而Z80 + x86的差别太大了,但有时我们在内存中做了交错结构,所以每个元素都是在+256地址(通过递增ptr的高8位来移动指针),或者元素具有固定的大小,并且指针在处理过程中部分地增加以遍历每个字段,并且结束指向下一个元素的开始,或者固定大小元素与二的幂,因此乘法只是移位等等...... – Ped7g

+0

当我上次为C64实现一个光栅IRQ表时,我使用了一个结构化数据阵列用于下一个光栅线,以将IRQ和ISR负载设置为跳到。这更简单并且没有任何缺点,因为数据只是以线性顺序使用。也就是说,我不能想到结构化数据的**普遍优势。这真的取决于用例。 –

回答

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 

由于并行数组超过单页限制,所以必须为每个数组重新设置一个指针。另外,一旦指针处于运行状态,长索引计算可以用预先计算的指针的简单移动或索引指针表的单个移动代替。

鉴于优势(至少在他所使用的灵活性方面),动态分配项目的能力来自免费赠品。他在写作时并不清楚,但这似乎是他所掌握的。

+0

这仍然是净亏损。设置'MONSTER_PTR'是几个更糟的指令。 'lda(xxx),y'比'lda xxx,y'慢,所以更糟。而且,为每个字段设置'ldy#offset'要比根本不需要加载偏移要慢,所以更糟糕,所以我不了解这里的优势。另外,在只有64k内存的6502上,在一张表中需要256个以上结构的时间很少。当然,如果你真的需要这样做,那么你只需要按照上面的方式去做,但这不是一个真正的优势,而是一种必要性。 – gman

+0

并行数组的优点是我提到的第一件事情,因为直接模式总是比索引更灵活(因为在6502中没有(ptr + offset),idx的等价物。然而,当你开始使用指针的时候,这个优点就会丢失,并且由于给出的原因,结构变得更加高效。想想你在平行阵列上使用指针会怎么做 - 我认为,这正是他所掌握的。 – Mike

+1

当然可以,但是当我们讨论优势与劣势时,讨论应该被认为是“直到你不能使用最有效的方法”,这基本上意味着“直到你不能使用它们时才选择平行阵列”。否则,如果建议是“在并行数组中使用指针和结构数组”,那么20次中的19次就会写出更多的代码和效率更低的代码。 – gman

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 

但是: 你可能只有256个怪物(和大量的免费内存页不会忽略,这不应该是这么多的问题)

在每个新的情况(比如你达到新地图,进入一个新的地牢等)零页指针可以更新,指向新的水平的怪物

+0

'lda(zeropagedamgelist),y'比'load nonzeropagedamagelist,x'好吗?后者是5个周期,前者6,后者不需要设置,前者是。我想我不明白你的答案。也许你假设数据在运行时加载到任意位置?但这不是人们编写大多数6502应用程序的方式。 – gman

+0

你可以加载新的Zeropage指针,对于你到达的每个“级别”,你输入的每个新地牢等,但这是一个好点,我会相应地改变我的答案 – Tommylee2k