Hello I made a sieve of Eratosthenes algorithm on x86 assembly using NASM.
The highest number it can take is about 2 million and it takes like 2 seconds to complete.
Here's the code:
section .bss
buffer resb 100
numbers resb 0
section .data
arrayLength db 0
section .text
global _start
_start:
call getLength
call initList
call algorithm
call printNumbers
exit
algorithm:
mov r10, 0 ;starting index
mov r11, 2 ;every nth number to be crossed out
loop3:
mov rax, [numbers]
mov rbp, [arrayLength]
crossOut rax, r10, r11, rbp ;if this returns 0 it means all non primes are already crossed out
jz return
call getIndex ; get next r10d and r11d
jmp loop3
return:
ret
getIndex: ;gets the next item that isnt already crossed out (0 means not crossed)
mov rax, [numbers]
loop2:
inc r10
inc r11
mov r12b, byte [rax + r10]
cmp r12b, 0
jnz loop2
ret
getLength:
mov eax, SYSREAD
mov edi, 1
mov esi, buffer
mov edx, 100
syscall
stringToNumber buffer ; returns edi as number
mov rbp, rdi ; store in ebp
sub rbp, 2 ; if user enters 30 then array length should be 28
; since first array item is 2
mov [arrayLength], rbp ; save in pointer
extern malloc
call malloc
mov [numbers], rax
ret
initList:
mov rbx, 0 ;index
mov rbp, [arrayLength]
mov rax, [numbers]
initloop:
mov byte [rax + rbx], 0
inc rbx
cmp rbx, rbp
jl initloop
ret
printNumbers:
mov rbp, [arrayLength]
mov r8d, 2 ; value
mov r9d, 0 ; index
printLoop:
mov rax, [numbers]
mov r11b, byte [rax + r9]
cmp r11b, 0
je handlePrime
loopend:
inc r9d
inc r8d
cmp r9, rbp ;return at array length
jnge printLoop
ret
handlePrime:
printNumber r8
jmp loopend
%macro crossOut 4
xor rdi, rdi ;edi keeps track of how many numbers were crossed out
;if 0 end loop
mov rbx, %1 ;array
add rbx, %2 ;move position to starting index
mov rax, %3 ;every nth number to be crossed out
mov rbp, %4 ; array length
mov rcx, 0 ;counter
%%loop:
add rcx, rax
cmp rcx, rbp
jge %%exit
add rbx, rax
cmp byte [rbx], 0
je %%crossout
jmp %%loop
%%crossout:
mov byte [rbx], 1
inc rdi
jmp %%loop
%%exit:
cmp rdi, 0
%endmacro
;example:
;"123" -> starting from 1
;1 + 0 * 10 = 1
;2 + 1 * 10 = 12
;3 + 12 * 10 = 123
%macro stringToNumber 1
mov rdi, 0 ; number stored here
mov ebx, %1
mov ecx, 0
%%loop:
xor esi, esi
mov sil, byte [ebx + ecx]
sub sil, 48
cmp esi, 9 ;if this is greater than 9 the string has ended
jg %%exit
mov rax, 10
mul rdi ; multiply by 10
add rsi, rax
mov rdi, rsi
inc ecx
jmp %%loop
%%exit:
%endmacro
je %%crossout
over ajmp %%loop
instead of ajne %%loop
. And the fact that you're branching at all instead of unconditionally storing; is that an attempt to save cache bandwidth for larger problem sizes, by being read-only for cache lines you don't modify, instead of reading + dirtying it? Or is that just a mistake? If I was going to write an answer, not sure how much CPU performance detail would be relevant. (agner.org/optimize) \$\endgroup\$jcc
over ajmp
, a compiler could show you better way for some things. It wouldn't do major changes, though, except maybe turning malloc + memset(0) intocalloc
for you. (Also, whymalloc
? That's the only libc function you use. Unless you use any inprintNumber
orexit
which you forgot to define) \$\endgroup\$echo 1000000 | perf stat ./sieve
can show you the average CPU frequency while it was running) \$\endgroup\$perf record --all-user ./a.out
/perf report -Mintel
to profile clock cycles. Orperf stat --all-user -d
. To find the max value that caused a problem for my version; I just ran it under GDB and looked at the value in R11 when it (intentionally) crashed. Note thejc abort
check aftertzcnt
in the sieve outer loop (to implement your getIndex search): if there's no prime in the next 63 to 56 bits of the bitmap, it just bails out because I didn't bother to write a loop. But it bails out to aud2
instruction that will SIGILL, so I can see the current state in GDB. \$\endgroup\$