我正在用masm汇编编写一个程序来计算并返回整数在数组中出现的次数。我目前有以下代码,允许我用随机整数填充数组。我正在努力解决的是如何实现一个计数器,该计数器将在数组中的索引处存储整数的每次出现。例如,如果随机数组是 [3,4,3,3,4,5,7,8],我希望我的计数数组保持 [3, 2, 1, 1, 1],就像有一样(三个 3、两个 4 等(。
我将随机数的边界固定为 3/8,因此我知道它们将在此范围内。我目前的想法是将每个数字与 3-8 进行比较,并分别增加我的计数数组。我主要缺乏理解是如何增加数组的特定索引。这段代码是我生成随机整数数组的方式,并知道如何开始计算整数出现次数,但我不知道我是否朝着正确的方向前进。有什么建议吗?
push ebp
mov ebp, esp
mov esi, [ebp + 16] ; @ holds array to store count of integer occurances
mov edi, [ebp + 12] ; @ holds array to be populated with random ints
mov ecx, [ebp + 8] ; value of request in ecx
MakeArray:
mov eax, UPPER ; upper boundary for random num in array
sub eax, LOWER ; lower boundary for random num in array
inc eax
call RandomRange
add eax, LOWER
cmp eax, 3 ; Where I start to compare the random numbers added
je inc_3 ; current thought is it cmp to each num 3-8
mov [edi], eax ; put random number in array
add edi, 4 ; holds address of current element, moves to next element
loop fillArrLoop
inc_3: ; if random num == 3
inc esi ; holds address of count_array, increments count_array[0] to 1?
mov [edi], eax ; put random number in array to be displayed
add edi, 4 ; holds address of current element, moves to next element
loop MakeArray
我目前的想法是将每个数字与 3-8 进行比较,因为它被添加
不,你把这复杂化了。 您不想线性搜索j
(索引到计数中(以便arr[i] == j
,只需使用j = arr[i]
即可。
制作直方图的标准方法是++counts[ arr[i] ]
. 在您的情况下,您知道可能的值为 3..8,因此您可以使用arr[i] - 3
将数组值映射到计数存储桶,因此您将对counts[0..5]
进行操作。给定寄存器中的元素值,具有缩放索引寻址模式的内存目标add
指令可以在一条 x86 指令中执行此操作。
如果可能的值不连续,您通常使用哈希表将值映射到对存储桶进行计数。 您可以将这个简单的情况视为允许一个微不足道的哈希函数。
由于您在直方图的同时生成随机数来填充arr[i]
,因此您可以将这两个任务结合起来,而不是减去 3,只是不要添加它。
; inputs: unsigned len, int *values, int *counts
; outputs: values[0..len-1] filled with random numbers, counts[] incremented
; clobbers: EAX, ECX, EDX (not the other registers)
fill_array_and_counts:
push ebp
mov ebp, esp
push esi ; Save/restore the caller's ESI.
;; Irvine32 functions like RandomRange are special and don't clobber EAX, ECX, or EDX except as return values,
;; so we can use EDX and ECX even in a loop that makes a function call.
mov edi, [ebp + 16] ; int *counts ; assumed already zeroed?
mov edx, [ebp + 12] ; int *values ; output pointers
mov ecx, [ebp + 8] ; size_t length
MakeArray: ; do{
mov eax, UPPER - LOWER + 1 ; size of random range, calculated at assemble time
call RandomRange ; eax = 0 .. eax-1
add dword ptr [edi + eax*4], 1 ; ++counts[ randval ]
add eax, LOWER ; map 0..n to LOWER..UPPER
mov [edx], eax ; *values = randval+3
add edx, 4 ; values++
dec ecx
jnz MakeArray ; }while(--ecx);
pop edi ; restore call-preserved regs
pop ebp ; including tearing down the stack frame
ret
如果调用方没有为您清零counts
数组,您应该自己执行此操作,也许使用 EAX=0rep stosd
作为 ECX dword 元素的memset
,然后从堆栈参数重新加载 EDI 和 ECX。
我假设 UPPER 和 LOWER 是汇编时间常量,如UPPER = 8
或LOWER equ 3
,因为您对它们使用了全大写的名称,并且它们不是函数参数。 如果是这种情况,那么就没有必要在运行时进行数学运算,只需让汇编程序为您计算UPPER - LOWER + 1
即可。
我避免使用loop
指令,因为它很慢,并且不会做任何你用其他简单指令做不到的事情。
对于只有几个存储桶的直方图,一个标准的性能技巧是拥有多个计数数组并在其上展开:在 SIMD 中矢量化直方图的方法? 这隐藏了当同一计数器需要连续多次递增时存储/重新加载的延迟。 但是,您的随机值通常会避免相同值的长时间运行,因此可以避免最坏情况的性能。
对于大型阵列,AVX2 可能会有所收获,因为只有 6 个可能的存储桶:大型数组或列表的 4 个存储桶直方图的微优化。 (如果需要,您可以使用 AVX2 xorshift128+ PRNG 在 SIMD 向量中生成随机数。
如果你的范围是固定的(3-8(,你有一个固定长度的数组来保存你的计数:
(index0:Count of 3),(index1:Count of 4)..(index5:Count of 8s)
一旦你从随机数组中获得了一个元素,你只需获取该元素并将其通过开关:
cmp 3, [element]
jne compare4
mov ebx, [countsArrayAddress] ; Can replace [countsArrayAddress] with [ebp + 16]
add ebx, 0 ; First index, can comment out this line
mov ecx, [ebx]
add ecx, 1 ; Increment count
mov [ebx], ecx ; Count at the zeroth offset is now incremented
compare4:
cmp 4, [element]
jne compare5
mov ebx, [countsArrayAddress]
add ebx, 4 ; Second index (1*4)
mov ecx, [ebx]
add ecx, 1
mov [ebx], ecx
...
这是你的意思吗?我来自使用fasm语法,但它看起来非常相似。上面的块有点未优化,但认为这显示了如何构建 counts 数组。数组有一个固定长度,必须在堆栈(sub rsp 正确的数量(或堆上分配,即使用堆/malloc 调用。(已编辑,请参阅您使用的是 32 位寄存器(