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

标签 performance assembly concurrency x86 nasm

我想在 yasm 程序中使用 POSIX 线程(或简称 pthread)库实现并行处理。

代码

这是我程序中最重要的部分。

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 包含循环,其中 value 递增 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类似。 注意:thread1thread2 共享 变量 value

结果

我将上面的程序汇编编译如下:

yasm -g dwarf2 -f elf64 Parallel.asm -l Parallel.lst
gcc -g Parallel.o -lpthread -o Parallel

然后我使用 time命令以了解经过的执行时间:

time ./Parallel

我明白了

value: +100000000

real    0m0.482s
user    0m0.472s
sys 0m0.000s

问题

好的。在上面的程序中,我创建了一个线程,等待它完成,然后才创建第二个线程。不是最好的“线程”,不是吗?所以我改变了程序中的顺序如下:

;create thread1
;create thread2
;join   thread1
;join   thread2

我希望在这种情况下耗时会更少,但我明白了

value: +48634696

real    0m2.403s
user    0m4.772s
sys 0m0.000s

我明白为什么 value 不等于 MAX 但我不明白的是 为什么在这种情况下耗时明显更多 ?我错过了什么吗?

编辑

我决定通过为每个变量使用不同的变量来排除 thread1thread2 之间的重叠,然后只添加结果。在这种情况下,“并行”顺序给出的运行时间较少(与之前的结果相比),但无论如何,大于“系列”顺序。

代码

仅显示更改

数据

现在有两个变量 --- 每个线程一个。

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
结果
value: +100000000

Performance counter stats for './Parallel':

   3078.140527      cpu-clock (msec)                                            

   1.586070821 seconds time elapsed
系列订单
;create thread1
;join thread1

;create thread2
;join thread2

mov     eax, dword [value]
add     eax, dword [value1]
add     eax, dword [value2]
mov     dword [value], eax
结果
value: +100000000

Performance counter stats for './Parallel':

    508.757321      cpu-clock (msec)                                            

   0.509709406 seconds time elapsed

更新

我绘制了一个简单的图表,反射(reflect)了 4 种不同模式下 MAX 值的时间依赖性。 enter image description here

最佳答案

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

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

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

关于performance - 在 x86_64 汇编程序 (yasm) 中使用 POSIX 线程库需要更多的执行时间,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49475380/

相关文章:

[var] 和 var 之间的汇编差异

c - 将带有数组的C代码转换为MIPS汇编

java - KMP前缀表运行时间

assembly - NASM 中的 MOVZBQ 等效项

javascript - 通过性能 api 测量站点加载时间

C++ 线程安全括号运算符代理

c# - C# 中的原子读-修改-写

concurrency - Go 例程可以共享 channel 的所有权吗?

c# - WinForms性能之谜: derived PictureBox control slower than the original?

python - 有没有比 while 循环更好的方法来执行此功能?