Performance 在x86_64汇编程序(yasm)中使用POSIX线程库需要更多的执行时间

Performance 在x86_64汇编程序(yasm)中使用POSIX线程库需要更多的执行时间,performance,assembly,concurrency,x86,nasm,Performance,Assembly,Concurrency,X86,Nasm,我想使用POSIXthread(或者干脆pthread)库在yasm程序中实现并行处理 代码 这是我计划中最重要的部分 section .data pThreadID1 dq 0 pThreadID2 dq 0 MAX: dq 100000000 value: dd 0 section .bss extern pthread_create extern pth

我想使用
POSIX
thread(或者干脆
pthread
)库在
yasm
程序中实现并行处理

代码 这是我计划中最重要的部分

section .data
pThreadID1      dq      0
pThreadID2      dq      0
MAX:            dq      100000000
value:          dd      0

section .bss
extern          pthread_create
extern          pthread_join

section .text

global main
main:
    ;create the first thread with id = pThreadID1
    mov     rdi, pThreadID1 
    mov     rsi, NULL
    mov     rdx, thread1
    mov     rcx, NULL
    call    pthread_create
    ;join the 1st thread
    mov     rdi, qword [pThreadID1]
    mov     rsi, NULL
    call    pthread_join 

    ;create the second thread with id = pThreadID2
    mov     rdi, pThreadID2 
    mov     rsi, NULL
    mov     rdx, thread2
    mov     rcx, NULL
    call    pthread_create
    ;join the 2nd thread
    mov     rdi, qword [pThreadID2]
    mov     rsi, NULL
    call    pthread_join
    ;print value block
其中,
thread1
包含循环,其中,
递增1
MAX/2
倍:

global thread1
thread1:
    mov     rcx, qword [MAX]
    shr     rcx, 1

    thread1.loop:
        mov     eax, dword [value]
        inc     eax
        mov     dword [value], eax
        loop    thread1.loop

    ret
thread2
类似。 注意:
thread1
thread2
共享变量

结果 我汇编并编译上述程序如下:

yasm -g dwarf2 -f elf64 Parallel.asm -l Parallel.lst
gcc -g Parallel.o -lpthread -o Parallel
;create thread1
;create thread2
;join   thread1
;join   thread2
然后我使用命令来了解经过的执行时间:

time ./Parallel
我得到

value: +100000000

real    0m0.482s
user    0m0.472s
sys 0m0.000s
问题 嗯。在上面的程序中,我创建一个线程,等待它完成,然后再创建第二个线程。不是最好的“线程”,不是吗?因此,我将程序中的顺序更改如下:

yasm -g dwarf2 -f elf64 Parallel.asm -l Parallel.lst
gcc -g Parallel.o -lpthread -o Parallel
;create thread1
;create thread2
;join   thread1
;join   thread2
我希望在这种情况下经过的时间会更少,但我得到了

value: +48634696

real    0m2.403s
user    0m4.772s
sys 0m0.000s
我理解为什么
value
不等于
MAX
,但我不理解的是,为什么在这种情况下,经过的时间明显更长?我错过什么了吗

编辑 我决定排除
thread1
thread2
之间的重叠,方法是分别使用不同的变量,然后添加结果。在这种情况下,“并行”顺序给出的运行时间较少(与之前的结果相比),但无论如何,大于“串行”顺序

代码 仅显示更改

资料 现在有两个变量——每个线程一个

section .data
value1:         dd      0
value2:         dd      0
线程 每个线程负责增加自己的值

global thread1
thread1:
    mov     rcx, qword [MAX]
    shr     rcx, 1

    thread1.loop:
        mov     eax, dword [value1]
        inc     eax
        mov     dword [value1], eax
        loop    thread1.loop

    ret
thread2
类似(将1替换为2)

取得最终结果 假设注释代表问题开头的代码部分的相应代码块,则程序如下

平行顺序
;create thread1
;create thread1

;join thread1
;join thread2

mov     eax, dword [value]
add     eax, dword [value1]
add     eax, dword [value2]
mov     dword [value], eax
结果 系列订单 结果 更新 我画了一个简单的图表,它反映了4种不同模式对
MAX
值的时间依赖性。

同时运行两个线程的版本速度较慢,因为运行代码的两个内核将在包含计数器值的缓存线上竞争。缓存线必须在两个核心之间来回移动,每次需要10秒的周期,并且在它移回另一个核心之前只会出现几个增量。与单线程情况相比,在单线程情况下,增量每~5个周期发生一次1,受存储转发延迟和慢速
循环
指令的限制

对于线程增加一个共享值的情况,以及线程不同的情况,都是如此。它甚至适用于后一种情况,因为值
value1
value2
被声明为占用内存中的连续位置,因此出现在同一缓存线上。由于一致性发生在缓存线粒度上,因此这种所谓的“虚假共享”效果类似于真实共享(第一种情况)


您提到,尽管真共享和假共享的速度都比单线程的慢得多,但真共享的速度仍然比假共享的慢。我认为,在没有测试的情况下,这两种情况在性能方面是等效的,但它们没有意义:尽管它们都会遭受上述缓存线抖动,真正的共享情况还可能会出现额外的内存顺序清除-尽管确切的机制尚不清楚。

性能基本上与从两个线程递增同一缓存线中的两个不重叠计数器相同。这种情况称为虚假分享。查看
ocperf.py stat-e machine\u clears.memory\u ordering./Parallel
(),另请参阅。另请参阅CPU如何在多个内核尝试加载/存储到同一线程时保持缓存一致性:MESI RFO请求以获得独占访问。(投票赞成自己实际做一个实验,并获得正确的实验数据。虽然你没有显示进行递增的实际循环,但对于这一点,如何做并不重要,单独加载/inc/store或内存目标add或inc是等效的。:@PeterCordes,我编辑了这篇文章。我已经为循环添加了代码和一些更新。为什么
thread1
thread2
有不同的代码?您可以在两个线程中运行相同的函数(传递指针arg)。顺便说一句。尽管即使没有内存订购机器核,存储/重新加载dep链也是一个瓶颈,就像Skylake上每次迭代5个循环一样。另外,对于诸如
[value]
之类的寻址模式,使用
default rel
进行RIP相对寻址。(同样不会产生差异,但总体上会更好。)重叠/非重叠计时差异是否可重复?或者,如果你有一个带超读功能的CPU,那么这可能只是一个偶然的机会,比如有一段时间你会在同一个物理内核上结束,而不是在不同的物理内核上结束?顺便说一句,比较一下将两个计数器相隔至少64字节,使它们位于单独的缓存线中。并且一定要使用
ocperf.py
来查看这方面的性能计数器;内存排序机器清除应该充分解释您看到的所有性能影响。我怀疑非重叠测试是对我的第一条评论的响应,这表明性能将与在同一缓存线中有两个计数器的更常见的错误共享情况相同。因此,是的,这两个计数器可能是故意排在同一行。但是,性能不一样是很奇怪的。@PeterCordes-我有点反对错误共享(相同的缓存线,不同的内存位置)比真正共享(相同的位置)更常见的想法,如果这是你的意思的话。当然,虚假共享经常被提及,并且有一个易于识别的单一名称
value: +100000000

Performance counter stats for './Parallel':

    508.757321      cpu-clock (msec)                                            

   0.509709406 seconds time elapsed