我正在ASM中使用NASM做一个关于pascal三角形的项目
所以在项目中你需要计算从第0行到第63行的pascal三角形
我的第一个问题是存储计算结果的位置 - >存储器
第二个问题我在内存中使用什么类型的存储,理解我的意思我有3种方式第一次声明完整矩阵所以会像这样 p>
memoryLabl: resd 63*64 ; 63 rows of 64 columns each
但问题就是这种方式没有使用一半的矩阵使我的程序效率不高所以让我们去第二种方法可用
为每一行声明一个内存标签 例如:
line0: dd 1
line1: dd 1,1
line2: dd 1,2,1 ; with pre-filled data for example purposes
...
line63: resd 64 ; reserve space for 64 dword entries
这种做法就像手工做,
该课程中的其他人尝试使用宏,因为您可以看到here 但我不明白
到目前为止一切顺利
让我们去看看我用过的最后一个 这是第一个,但我使用三角矩阵,怎么样, 通过仅声明我需要的内存量
所以要将第0行存储到pascal三角形的第63行,它会给我一个三角形矩阵,因为每个新行都会添加一个单元格
我为三角矩阵分配了2080 dword是怎么回事? 2080年解释dword:
okey we have line0 have 1 dword /* 1 number in first line */
line1 have 2 dword /* 2 numbers in second line */
line2 have 3 dword /* 3 numbers in third line */
...
line63 have 64 dword /* 64 numbers in final line*/
so in the end we have 2080 as the sum of them
我已经给每个数字1个dword
现在我们已经创建了存储结果的内存让我们开始计算 在pascal三角形中第一个,第0行中的所有单元格都有值1
我将在伪代码中执行此操作,以便您了解我如何在第0行的所有单元格中放置一个:
s=0
for(i=0;i<64;i++):
s = s+i
mov dword[x+s*4],1 /* x is addresses of triangle matrices */
pascal三角形的第二部分是每行的最后一行等于1
我将使用伪代码使其变得简单
s=0
for(i=2;i<64;i++):
s = s+i
mov dword[x+s*4],1
我从i开始等于2因为i = 0(i = 1)是line0(line1)而line0(line1)已满,因为我只保留一个(两个)值,如上所述
所以两段伪代码会让我的矩形在内存中看起来像:
1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
1 1
...
1 1
现在变得困难的是使用三角形中的这个值来计算填充所有三角形单元格
让我们从这个想法开始
let's take cell[line][row]
we have cell[2][1] = cell[1][0]+cell[1][1]
and cell[3][1]= cell[2][0]+cell[2][1]
cell[3][2]= cell[2][1]+cell[2][2]
in **general** we have
cell[line][row]= cell[line-1][row-1]+cell[line-1][row]
我的问题我无法使用ASM指令破坏此关系,因为我有
使用奇怪的三角矩阵可以帮助我使用关系或非常基本的伪代码或asm代码来破解它吗?答案 0 :(得分:2)
TL:DR:你只需要按顺序遍历数组,这样你就不必编制索引。见第2部分。
要随机访问index into a (lower) triangular matrix ,行r
会在大小为r-1
的三角形之后开始。使用Gauss's formula for the sum of numbers from 1 to n-1的大小为n
的三角形的元素总数为n*(n+1)/2
。因此,大小为r-1
的三角形具有(r-1)*r/2
个元素。一旦我们知道行开头的地址,索引行中的列当然是微不足道的。
每个DWORD元素的宽度为4个字节,我们可以将该缩放作为乘法的一部分,因为lea
lets us shift and add以及将结果放在不同的寄存器中。我们将n*(n-1)/2 elements * 4 bytes / elem
简化为n*(n-1) * 2 bytes
。
上述推理适用于基于1的索引,其中第1行有1个元素。如果我们想要通过在计算之前向行索引添加1来进行基于零的索引,我们必须进行调整,因此我们需要三角形的大小
r+1 - 1
行r*(r+1)/2 * 4 bytes
。 0
4 8
12 16 20
24 28 32 36
40 44 48 52 56
60 64 68 72 76 80
84 88 92 96 100 104 108
。它有助于将线性数组索引放入三角形中以快速仔细检查公式
(3+1)*(3+1-1) * 2
第4行,我们正在调用&#34;第3行和第34行,从整个数组的开头开始24个字节。这是(3+1)*3 * 2
= r*(r+1)/2
;是的,;; given a row number in EDI, and column in ESI (zero-extended into RSI)
;; load triangle[row][col] into eax
lea ecx, [2*rdi + 2]
imul ecx, edi ; ecx = r*(r+1) * 2 bytes
mov eax, [triangle + rcx + rsi*4]
公式有效。
triangle
这假设32位绝对寻址是可以的(32-bit absolute addresses no longer allowed in x86-64 Linux?)。如果不是,请使用RIP相对LEA在寄存器中获取rsi*4
基址,并将其添加到triangle
。当x86 addressing modes中的一个是常量时,my answer to the question you linked about macros只能有3个组件。但是在这里是静态global _start
_start:
mov esi, triangle ; src = address of triangle[0,0]
lea rdi, [rsi+4] ; dst = address of triangle[1,0]
mov dword [rsi], 1 ; triangle[0,0] = 1 special case: there is no source
.pascal_row: ; do {
mov rcx, rdi ; RCX = one-past-end of src row = start of dst row
xor eax, eax ; EAX = triangle[row-1][col-1] = 0 for first iteration
;; RSI points to start of src row: triangle[row-1][0]
;; RDI points to start of dst row: triangle[row ][0]
.column:
mov edx, [rsi] ; tri[r-1, c] ; will load 1 on the first iteration
add eax, edx ; eax = tri[r-1, c-1] + tri[r-1, c]
mov [rdi], eax ; store to triangle[row, col]
add rdi, 4 ; ++dst
add rsi, 4 ; ++src
mov eax, edx ; becomes col-1 src value for next iteration
cmp rsi, rcx
jb .column ; }while(src < end_src)
;; RSI points to one-past-end of src row, i.e. start of next row = src for next iteration
;; RDI points to last element of dst row (because dst row is 1 element longer than src row)
mov dword [rdi], 1 ; [r,r] = 1 end of a row
add rdi, 4 ; this is where dst-src distance grows each iteration
cmp rdi, end_triangle
jb .pascal_row
;;; triangle is constructed. Set a breakpoint here to look at it with a debugger
xor edi,edi
mov eax, 231
syscall ; Linux sys_exit_group(0), 64-bit ABI
section .bss
; you could just as well use resd 64*65/2
; but put a label on each row for debugging convenience.
ALIGN 16
triangle:
%assign i 0
%rep 64
row %+ i: resd i + 1
%assign i i+1
%endrep
end_triangle:
的情况,所以我们可以通过使用列的缩放索引和base作为计算的行偏移量和实际的数组地址来充分利用作为流离失所。
这里的诀窍是你只需要顺序循环;您不需要随机访问给定的行/列。
你在写下一行时读了一行。 当你到达行的末尾时,下一个元素就是下一行的开始。当你沿着行向下时,源指针和目标指针会越来越远,因为目的地总是提前一整排。并且您知道行的长度=行号,因此您实际上可以使用行计数器作为偏移量。
resd
我对此进行了测试并且工作正常:内存中的值正确,并且它在正确的位置停止。但请注意,在到达最后一行之前会发生整数溢出。如果您使用64位整数(简单更改注册名称和偏移量,并且不要忘记resq
到%rep
),这将被避免。 64选择32是1832624140942590534 = 2 ^ 60.66。
row0
块用于保留空间并将每行标记为row1
,dst
等来自vpaddd
,比其他答案IMO更加明智。< / p>
你标记了这个NASM,所以我使用的是因为我熟悉它。您在问题中使用的语法是MASM(直到最后一次编辑)。 MASM中的主要逻辑是相同的,但请记住,您需要使用OFFSET三角形来将地址作为立即数,而不是从中加载。
我使用的是x86-64,因为32位已经过时了,但是我避免了太多的寄存器,因此如果需要,你可以轻松地将其移植到32位。如果您将其置于函数而不是独立程序中,请不要忘记保存/恢复调用保留的寄存器。
展开内部循环可以保存一些复制寄存器的指令,以及循环开销。这是一个稍微优化的实现,但我主要将其限制为使代码更简单以及更小/更快的优化。 (除了可能使用指针增量而不是索引。)需要一段时间才能使它干净简单。 :P
在不同的CPU上执行数组索引的不同方式会更快。例如也许对内部循环中的负载使用索引寻址模式(相对于palignr
),因此只需要一个指针增量。但是如果你想让它快速运行,SSE2或AVX2 the x86 tag wiki可能会很好。使用eax=0
进行混洗可能很有用,但也可能是未对齐的加载而不是一些混乱,尤其是对于AVX2或AVX512。
但无论如何,这是我的版本;我不是按照你想要的方式写作,你需要为自己的作业编写自己的作品。我正在为未来的读者写作,他们可能会了解x86上的效率。 (另请参阅{{3}}中的效果部分。)
我是怎么写的:
我开始从顶部开始编写代码,但很快就意识到逐个错误会变得棘手,而且我并不想用branches write的方式将它写成特殊循环中的分支。例。
最终帮助的是在内循环的指针上写出前置和后置条件的注释。这表明我需要使用eax=1
而不是[rsi]
进入循环,并将eax存储为循环内的第一个操作,或类似的东西。
显然每个源值只需要读取一次,所以我不想编写一个内部循环来读取[rsi+4]
和end_triangle
或其他东西。此外,这将使得边界条件更加正确(其中不存在的值必须读为0)。
在我最终只使用整个三角形的结束指针之前,花了一些时间来决定我是否要在行长度或行号的寄存器中有一个实际的计数器。在我完成之前,使用纯指针增量/比较是要保存这么多指令(并且当上限是像{{1}}那样的构建时常量时寄存器),这是不明显的,但它很好地解决了