多核汇编语言是什么样子的?

曾经,为了编写x86汇编程序,例如,你会有这样的指令:“用值5加载EDX寄存器”,“增加EDX”寄存器,等等。

对于拥有4核(甚至更多)的现代cpu,在机器代码级别上,它是否看起来就像有4个独立的cpu(即只有4个不同的“EDX”寄存器)?如果是这样,当你说“增加EDX寄存器”时,是什么决定哪个CPU的EDX寄存器被增加?现在在x86汇编器中有“CPU上下文”或“线程”概念吗?

内核之间的通信/同步是如何工作的?

如果您正在编写一个操作系统,通过硬件公开的什么机制允许您在不同的内核上调度执行?是一些特殊的特权指令吗?

如果你正在为一个多核CPU编写一个优化编译器/字节码虚拟机,你需要特别了解什么,比如说,x86,以使它生成跨所有核高效运行的代码?

为了支持多核功能,x86机器码做了哪些改变?

64237 次浏览

汇编代码将转换为将在一个核心上执行的机器代码。如果你希望它是多线程的,你将不得不使用操作系统原语在不同的处理器上多次启动这段代码,或者在不同的核上启动不同的代码段——每个核将执行一个单独的线程。每个线程只能看到当前正在执行的一个内核。

根据我的理解,每个“核心”都是一个完整的处理器,有自己的寄存器集。基本上,BIOS启动时只运行一个核心,然后操作系统可以通过初始化其他核心并将它们指向要运行的代码等方式“启动”其他核心。

同步由操作系统完成。通常,每个处理器为操作系统运行不同的进程,因此操作系统的多线程功能负责决定哪个进程可以访问哪个内存,以及在内存碰撞的情况下该做什么。

每个核心从不同的内存区域执行。你的操作系统将把一个核心指向你的程序,这个核心将执行你的程序。你的程序不会知道有多个核或者它在哪个核上执行。

也没有仅用于操作系统的附加指令。这些核心与单核芯片是相同的。每个内核运行操作系统的一部分,该部分将处理与用于信息交换的公共内存区域的通信,以查找下一个要执行的内存区域。

这是一个简化,但它给了你基本的想法,它是如何做到的。更多关于多核和多处理器的信息在Embedded.com上有很多关于这个主题的信息…这个话题很快就变得复杂起来!

如果你在写一个优化 多核编译器/字节码虚拟机 中央处理器,你需要知道什么 特别是关于x86的制作 它生成有效运行的代码 在所有的核上?< / p >

作为编写优化编译器/字节码虚拟机的人,我可能能够在这里帮助你。

您不需要特别了解x86,就可以让它生成跨所有核心高效运行的代码。

然而,为了编写在所有核上运行正确的代码,你可能需要了解cmpxchg和它的朋友。多核编程要求在执行线程之间使用同步和通信。

您可能需要了解一些关于x86的知识,以便让它生成在x86上高效运行的代码。

你还可以学习其他一些有用的东西:

您应该了解操作系统(Linux或Windows或OSX)提供的允许您运行多个线程的功能。你应该学习并行化api,比如OpenMP和Threading Building Blocks,或者OSX 10.6“Snow Leopard”即将推出的“Grand Central”。

您应该考虑编译器是否应该自动并行,或者编译器编译的应用程序的作者是否需要在他的程序中添加特殊的语法或API调用来利用多核。

这不是对问题的直接回答,但这是对评论中出现的一个问题的回答。本质上,问题是硬件对多核操作提供了什么支持,即同时运行多个软件线程的能力,而无需在它们之间进行软件上下文切换。(有时称为SMP系统)。

尼古拉斯·弗林特(Nicholas Flynt)是对的,至少对于x86。在多核环境(超线程、多核或多处理器)中,引导核心(通常是处理器0的核心0中的硬件线程(又名逻辑内核)0)从地址0xfffffff0开始获取代码。所有其他内核(硬件线程)在一种称为Wait-for-SIPI的特殊睡眠状态下启动。作为初始化的一部分,主核通过APIC向WFS中的每个核心发送一个特殊的处理器间中断(IPI),称为SIPI(启动IPI)。SIPI包含该核心应该从哪里开始获取代码的地址。

这种机制允许每个核心从不同的地址执行代码。所需要的只是为每个硬件核心提供软件支持,以便建立自己的表和消息队列。

操作系统使用那些来执行软件任务的实际多线程调度。(一个正常的操作系统只需要在启动时启动一次其他内核,除非你是热插拔cpu,例如在虚拟机中。这与启动或将软件线程迁移到这些内核是分开的。每个核心正在运行内核,如果没有其他事情要做,它将花费时间调用sleep函数来等待中断。)

就实际的程序集而言,正如Nicholas所写的,单线程应用程序集和多线程应用程序集之间没有区别。每个核都有自己的寄存器集(执行上下文),因此编写:

mov edx, 0

将只更新EDX当前运行的线程.;没有办法使用单一的汇编指令在另一个处理器上修改EDX。你需要某种类型的系统调用来要求操作系统告诉另一个线程运行将更新自己的EDX的代码。

与之前的单处理器变体相比,每一个具有多处理能力的架构都增加了在核心之间同步的指令。此外,还有处理缓存一致性、刷新缓冲区以及操作系统必须处理的类似低级操作的指令。在并行多线程架构(如IBM POWER6、IBM Cell、Sun Niagara和Intel“Hyperthreading”)的情况下,您还会看到在线程之间划分优先级的新指令(如设置优先级和在无事可做时显式地放弃处理器)。

但是基本的单线程语义是相同的,您只是添加额外的设施来处理与其他核心的同步和通信。

这根本不是在机器指令中完成的;这些核心假装是不同的cpu,并且没有任何相互通信的特殊能力。他们有两种沟通方式:

  • 它们共享物理地址空间。硬件处理缓存一致性,因此一个CPU写入另一个CPU读取的内存地址。

  • 它们共享一个APIC(可编程中断控制器)。这是映射到物理地址空间的内存,一个处理器可以使用它来控制其他处理器,打开或关闭它们,发送中断等等。

http://www.cheesecake.org/sac/smp.html对于一个愚蠢的url是一个很好的参考。

单线程应用程序和多线程应用程序之间的主要区别是前者有一个堆栈,而后者每个线程都有一个堆栈。由于编译器会假设数据和堆栈段寄存器(ds和ss)不相等,因此生成的代码略有不同。这意味着通过默认为ss寄存器的ebp和esp寄存器的间接调用也不会默认为ds(因为ds!=ss)。相反,通过其他默认为ds的寄存器的间接调用不会默认为ss。

线程共享其他所有内容,包括数据和代码区。它们还共享库例程,因此要确保它们是线程安全的。在RAM中对区域进行排序的过程可以是多线程的,以加快速度。然后,线程将访问、比较和排序同一物理内存区域中的数据,并执行相同的代码,但使用不同的局部变量来控制各自的排序部分。这当然是因为线程有不同的堆栈,其中包含局部变量。这种类型的编程需要仔细调整代码,以减少内核之间的数据冲突(在缓存和RAM中),从而导致代码在两个或多个线程时比只有一个线程时更快。当然,未调优的代码在一个处理器上通常比在两个或多个处理器上更快。调试更具挑战性,因为标准的“int 3”断点将不适用,因为您希望中断一个特定的线程,而不是所有线程。调试寄存器断点也不能解决这个问题,除非您可以在执行要中断的特定线程的特定处理器上设置它们。

其他多线程代码可能涉及在程序的不同部分运行的不同线程。这种类型的编程不需要同样的调优,因此更容易学习。

非官方的SMP FAQ stack overflow logo

人力资源> < p > < 曾经,为了编写x86汇编程序,例如,你会有这样的指令:“用值5加载EDX寄存器”,“增加EDX”寄存器,等等。对于拥有4核(甚至更多)的现代cpu,在机器代码级别上,它是否看起来就像有4个独立的cpu(即只有4个不同的“EDX”寄存器)

完全正确。有4组寄存器,包括4个单独的指令指针。

如果是这样,当你说“增加EDX寄存器”时,是什么决定哪个CPU的EDX寄存器被增加?

当然是执行指令的CPU。可以把它想象成4个完全不同的微处理器共享相同的内存。

现在在x86汇编器中有“CPU上下文”或“线程”概念吗?

不。汇编程序只是像往常一样翻译指令。没有变化。

内核之间的通信/同步是如何工作的?

由于它们共享相同的内存,这主要是程序逻辑的问题。虽然现在有一个处理器间中断机制,但它不是必要的,最初也没有出现在第一个双cpu x86系统中。

如果您正在编写一个操作系统,通过硬件公开的什么机制允许您在不同的内核上调度执行?

调度程序实际上并没有改变,只是对临界区和所使用的锁类型更加谨慎。在SMP之前,内核代码最终会调用调度器,调度器会查看运行队列,并选择一个进程作为下一个线程运行。(内核的进程看起来很像线程。)SMP内核运行完全相同的代码,一次一个线程,只是现在临界区锁定需要SMP安全,以确保两个内核不会意外地选择相同的PID。

是一些特殊的特权指令吗?

不。这些核心都运行在相同的内存中,使用相同的旧指令。

如果你正在为一个多核CPU编写一个优化编译器/字节码虚拟机,你需要特别了解什么,比如说,x86,以使它生成跨所有核高效运行的代码?

运行与之前相同的代码。需要改变的是Unix或Windows内核。

你可以把我的问题总结为“为了支持多核功能,x86机器码做了哪些改变?”

没有什么是必要的。第一个SMP系统使用与单处理器完全相同的指令集。现在,已经有了大量的x86架构的演进和大量的新指令使事情变得更快,但没有一个是必要的 SMP。

有关更多信息,请参见英特尔多处理器规格


更新:所有后续问题都可以通过完全接受n方式的多核CPU几乎与__abc2完全相同,它们只是共享相同的内存。一些线程库使用对操作系统不可见的“绿色线程”,这些线程不会获得独立的内核,但只要线程库使用内核线程特性,那么你的线程程序就会自动多核。
1. 为了向后兼容,只有第一个核心在重置时启动,并且需要做一些驱动类型的事情来启动其余的核心。它们自然也共享所有的外围设备

Intel x86最小可运行的裸金属示例

可运行的裸露金属的例子,所有所需的样板。下面将介绍所有主要部分。

在Ubuntu 15.10 QEMU 2.3.0和联想ThinkPad T400 真正的硬件客人上测试。

英特尔手册卷3系统编程指南- 325384-056US 2015年9月在第8、9和10章中涵盖了SMP。

表8 - 1。“Broadcast INIT-SIPI-SIPI序列和超时的选择”包含了一个基本工作的示例:

MOV ESI, ICR_LOW    ; Load address of ICR low dword into ESI.
MOV EAX, 000C4500H  ; Load ICR encoding for broadcast INIT IPI
; to all APs into EAX.
MOV [ESI], EAX      ; Broadcast INIT IPI to all APs
; 10-millisecond delay loop.
MOV EAX, 000C46XXH  ; Load ICR encoding for broadcast SIPI IP
; to all APs into EAX, where xx is the vector computed in step 10.
MOV [ESI], EAX      ; Broadcast SIPI IPI to all APs
; 200-microsecond delay loop
MOV [ESI], EAX      ; Broadcast second SIPI IPI to all APs
; Waits for the timer interrupt until the timer expires

在代码中:

  1. 大多数操作系统将使环3(用户程序)中的大部分操作不可能实现。

    因此,您需要编写自己的内核来自由地使用它:一个用户域的Linux程序将无法工作

  2. 首先,运行一个单独的处理器,称为引导处理器(BSP)。

    它必须通过名为处理器间中断(IPI)的特殊中断唤醒其他处理器(称为应用程序处理器(AP))。

    这些中断可以通过中断命令寄存器(ICR)编程高级可编程中断控制器(APIC)来实现。

    ICR的格式记录在10.6“发出处理器间中断”

    当我们写入ICR时,IPI就会发生

  3. ICR_LOW在8.4.4“MP初始化示例”中定义为:

    ICR_LOW EQU 0FEE00300H
    

    神奇值0FEE00300是ICR的内存地址,如表10-1“本地APIC寄存器地址映射”

    所示
  4. 本例中使用了可能最简单的方法:它设置ICR发送广播ipi,这些广播ipi被发送到除当前处理器之外的所有其他处理器。

    但是,被一些人推荐也可以通过BIOS设置的特殊数据结构(如ACPI表或英特尔的MP配置表)来获得有关处理器的信息,并且只逐个唤醒你需要的处理器

  5. 000C46XXH中的XX将处理器将执行的第一条指令的地址编码为:

    CS = XX * 0x100
    IP = 0
    

    记住CS通过0x10将地址相乘,所以第一条指令的实际内存地址是:

    XX * 0x1000
    

    因此,如果例如XX == 1,处理器将从0x1000开始。

    然后,我们必须确保有16位实模式代码在该内存位置运行,例如:

    cld
    mov $init_len, %ecx
    mov $init, %esi
    mov 0x1000, %edi
    rep movsb
    
    
    .code16
    init:
    xor %ax, %ax
    mov %ax, %ds
    /* Do stuff. */
    hlt
    .equ init_len, . - init
    

    使用链接器脚本是另一种可能

  6. 延迟循环是工作中令人讨厌的部分:没有超级简单的方法来精确地进行这样的睡眠。

    可能的方法包括:

    • PIT(在我的例子中使用)
    • HPET
    • 用上面的方法校准繁忙循环的时间,并使用它来代替

    相关:如何在屏幕上显示一个数字和睡眠一秒钟与DOS x86汇编? < / p >

  7. 我认为初始处理器需要在保护模式下才能工作,因为我们写地址0FEE00300H对于16位来说太高了

  8. 要在处理器之间通信,我们可以在主进程上使用自旋锁,并从第二个核心修改锁。

    我们应该确保内存回写已经完成,例如通过wbinvd

处理器之间的共享状态

8.7.1“逻辑处理器的状态”说:

以下特性是Intel 64或IA-32处理器中逻辑处理器体系结构状态的一部分 支持Intel超线程技术。特征可以细分为三组:

  • 为每个逻辑处理器复制
  • 由物理处理器中的逻辑处理器共享
  • 共享或复制,取决于实现

以下特性对于每个逻辑处理器都是重复的:

  • 通用寄存器(EAX, EBX, ECX, EDX, ESI, EDI, ESP和EBP)
  • 段寄存器(CS, DS, SS, ES, FS, GS)
  • EFLAGS和EIP寄存器。注意,每个逻辑处理器的CS和EIP/RIP寄存器都指向 逻辑处理器正在执行的线程的指令流
  • x87 FPU寄存器(ST0到ST7,状态字,控制字,标记字,数据操作数指针和指令 李指针)< / >
  • MMX寄存器(MM0到MM7)
  • XMM寄存器(XMM0到XMM7)和MXCSR寄存器
  • 控制寄存器和系统表指针寄存器(GDTR, LDTR, IDTR,任务寄存器)
  • 调试寄存器(DR0, DR1, DR2, DR3, DR6, DR7)和调试控制msr
  • 机器检查全局状态(IA32_MCG_STATUS)和机器检查能力(IA32_MCG_CAP) MSRs
  • 热时钟调制和ACPI电源管理控制MSRs
  • 时间戳计数器MSRs
  • 大多数其他MSR寄存器,包括页属性表(PAT)。请参阅下面的例外情况。
  • 本地APIC寄存器。
  • 附加通用寄存器(R8-R15), XMM寄存器(XMM8-XMM15),控制寄存器,IA32_EFER on Intel 64处理器。

逻辑处理器共享以下特性:

  • 内存类型范围寄存器

以下功能是否共享或复制取决于具体实现:

  • (MSR地址1A0H)
  • 机器检查体系结构(MCA) MSRs (IA32_MCG_STATUS和IA32_MCG_CAP MSRs除外)
  • 性能监视、控制和计数器MSRs

缓存共享的讨论如下:

英特尔超线程具有比独立内核更大的缓存和管道共享:https://superuser.com/questions/133082/hyper-threading-and-dual-core-whats-the-difference/995858#995858

Linux内核4.2

主要的初始化操作似乎在arch/x86/kernel/smpboot.c

ARM最小可运行裸金属示例

下面我为QEMU提供了一个最小可运行ARMv8 aarch64的例子:

.global mystart
mystart:
/* Reset spinlock. */
mov x0, #0
ldr x1, =spinlock
str x0, [x1]


/* Read cpu id into x1.
* TODO: cores beyond 4th?
* Mnemonic: Main Processor ID Register
*/
mrs x1, mpidr_el1
ands x1, x1, 3
beq cpu0_only
cpu1_only:
/* Only CPU 1 reaches this point and sets the spinlock. */
mov x0, 1
ldr x1, =spinlock
str x0, [x1]
/* Ensure that CPU 0 sees the write right now.
* Optional, but could save some useless CPU 1 loops.
*/
dmb sy
/* Wake up CPU 0 if it is sleeping on wfe.
* Optional, but could save power on a real system.
*/
sev
cpu1_sleep_forever:
/* Hint CPU 1 to enter low power mode.
* Optional, but could save power on a real system.
*/
wfe
b cpu1_sleep_forever
cpu0_only:
/* Only CPU 0 reaches this point. */


/* Wake up CPU 1 from initial sleep!
* See:https://github.com/cirosantilli/linux-kernel-module-cheat#psci
*/
/* PCSI function identifier: CPU_ON. */
ldr w0, =0xc4000003
/* Argument 1: target_cpu */
mov x1, 1
/* Argument 2: entry_point_address */
ldr x2, =cpu1_only
/* Argument 3: context_id */
mov x3, 0
/* Unused hvc args: the Linux kernel zeroes them,
* but I don't think it is required.
*/
hvc 0


spinlock_start:
ldr x0, spinlock
/* Hint CPU 0 to enter low power mode. */
wfe
cbz x0, spinlock_start


/* Semihost exit. */
mov x1, 0x26
movk x1, 2, lsl 16
str x1, [sp, 0]
mov x0, 0
str x0, [sp, 8]
mov x1, sp
mov w0, 0x18
hlt 0xf000


spinlock:
.skip 8

GitHub上游

组装和运行:

aarch64-linux-gnu-gcc \
-mcpu=cortex-a57 \
-nostdlib \
-nostartfiles \
-Wl,--section-start=.text=0x40000000 \
-Wl,-N \
-o aarch64.elf \
-T link.ld \
aarch64.S \
;
qemu-system-aarch64 \
-machine virt \
-cpu cortex-a57 \
-d in_asm \
-kernel aarch64.elf \
-nographic \
-semihosting \
-smp 2 \
;

在本例中,我们将cpu0放入自旋锁循环中,只有当cpu1释放自旋锁时,它才会退出。

自旋锁之后,cpu0执行半主机退出调用,使QEMU退出。

如果你用-smp 1启动QEMU,那么模拟就永远挂在自旋锁上。

CPU 1被PSCI接口唤醒,更多细节在:ARM:启动/唤醒/唤醒其他CPU核心/ ap和pass执行起始地址?

上游版本也有一些调整,使它在gem5上工作,所以你也可以试验性能特征。

我还没有在真正的硬件上测试过,所以我不确定它的可移植性。下面的树莓派参考书目可能会感兴趣:

本文档提供了一些关于使用ARM同步原语的指导,然后你可以使用这些原语在多核上做一些有趣的事情:http://infocenter.arm.com/help/topic/com.arm.doc.dht0008a/DHT0008A_arm_synchronization_primitives.pdf

在Ubuntu 18.10, GCC 8.2.0, Binutils 2.31.1, QEMU 2.12.0上测试。

更方便的可编程性的下一步

前面的例子使用专用指令唤醒辅助CPU并执行基本的内存同步,这是一个良好的开始。

但是为了使多核系统易于编程,例如POSIX pthreads,你还需要进入以下更复杂的主题:

  • 设置中断并运行一个计时器,定期决定现在运行哪个线程。这被称为先发制人的多线程

    这样的系统还需要在线程寄存器启动和停止时保存和恢复线程寄存器。

    也可以有非抢占的多任务系统,但这可能需要你修改你的代码,以便每个线程都有产出(例如,使用pthread_yield实现),并且很难平衡工作负载。

    以下是一些简单的裸金属计时器的例子:

    这些都是使用Linux内核或其他操作系统的好理由:-)

    用户域内存同步原语

    尽管线程启动/停止/管理通常超出了用户域的范围,但是您可以使用来自用户域线程的汇编指令来同步内存访问,而不需要潜在的更昂贵的系统调用。

    当然,您应该更喜欢使用可移植地包装这些低级原语的库。c++标准本身在<mutex><atomic>头文件方面取得了很大的进步,特别是在std::memory_order方面。我不确定它是否涵盖了所有可能的内存语义,但它只是可能。

    更微妙的语义在无锁数据结构上下文中特别相关,它可以在某些情况下提供性能优势。要实现这些,你可能需要学习一些不同类型的内存障碍:https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/

    例如,Boost在:https://www.boost.org/doc/libs/1_63_0/doc/html/lockfree.html有一些无锁容器实现

    这样的用户域指令似乎也被用于实现Linux futex系统调用,这是Linux中主要的同步原语之一。man futex 4.15读到:

    futex()系统调用提供了一个方法,用于等待直到某个条件变为真。它通常被用作 共享内存同步上下文中的块构造。当使用futexes时,大部分的同步 操作在用户空间进行。用户空间程序仅在可能发生futex()事件时才使用futex()系统调用 程序必须阻塞更长的时间,直到条件变为真。其他futex()操作可用于唤醒任何

    .等待特定条件的进程或线程

    系统调用名称本身的意思是“快速用户空间XXX”。

    下面是一个最小的无用的c++ x86_64 / aarch64内联汇编示例,它主要是为了好玩而演示这些指令的基本用法:

    main.cpp

    #include <atomic>
    #include <cassert>
    #include <iostream>
    #include <thread>
    #include <vector>
    
    
    std::atomic_ulong my_atomic_ulong(0);
    unsigned long my_non_atomic_ulong = 0;
    #if defined(__x86_64__) || defined(__aarch64__)
    unsigned long my_arch_atomic_ulong = 0;
    unsigned long my_arch_non_atomic_ulong = 0;
    #endif
    size_t niters;
    
    
    void threadMain() {
    for (size_t i = 0; i < niters; ++i) {
    my_atomic_ulong++;
    my_non_atomic_ulong++;
    #if defined(__x86_64__)
    __asm__ __volatile__ (
    "incq %0;"
    : "+m" (my_arch_non_atomic_ulong)
    :
    :
    );
    // https://github.com/cirosantilli/linux-kernel-module-cheat#x86-lock-prefix
    __asm__ __volatile__ (
    "lock;"
    "incq %0;"
    : "+m" (my_arch_atomic_ulong)
    :
    :
    );
    #elif defined(__aarch64__)
    __asm__ __volatile__ (
    "add %0, %0, 1;"
    : "+r" (my_arch_non_atomic_ulong)
    :
    :
    );
    // https://github.com/cirosantilli/linux-kernel-module-cheat#arm-lse
    __asm__ __volatile__ (
    "ldadd %[inc], xzr, [%[addr]];"
    : "=m" (my_arch_atomic_ulong)
    : [inc] "r" (1),
    [addr] "r" (&my_arch_atomic_ulong)
    :
    );
    #endif
    }
    }
    
    
    int main(int argc, char **argv) {
    size_t nthreads;
    if (argc > 1) {
    nthreads = std::stoull(argv[1], NULL, 0);
    } else {
    nthreads = 2;
    }
    if (argc > 2) {
    niters = std::stoull(argv[2], NULL, 0);
    } else {
    niters = 10000;
    }
    std::vector<std::thread> threads(nthreads);
    for (size_t i = 0; i < nthreads; ++i)
    threads[i] = std::thread(threadMain);
    for (size_t i = 0; i < nthreads; ++i)
    threads[i].join();
    assert(my_atomic_ulong.load() == nthreads * niters);
    // We can also use the atomics direclty through `operator T` conversion.
    assert(my_atomic_ulong == my_atomic_ulong.load());
    std::cout << "my_non_atomic_ulong " << my_non_atomic_ulong << std::endl;
    #if defined(__x86_64__) || defined(__aarch64__)
    assert(my_arch_atomic_ulong == nthreads * niters);
    std::cout << "my_arch_non_atomic_ulong " << my_arch_non_atomic_ulong << std::endl;
    #endif
    }
    
    
    

    GitHub上游

    可能的输出:

    my_non_atomic_ulong 15264
    my_arch_non_atomic_ulong 15267
    

    从这里我们可以看到,x86 LOCK前缀/ aarch64 LDADD指令使添加操作具有原子性:如果没有它,许多添加操作都有竞态条件,并且最后的总计数小于同步的20000。

    参见:

我认为提问者可能是想通过让多个核并行工作来使程序运行得更快。无论如何,这就是我想要的,但所有的答案都没有让我更聪明。然而,我认为我明白了这一点:你不能将不同的线程同步到指令执行时间精度。所以你不能让4个核心并行地对4个不同的数组元素进行乘法运算,从而将处理速度提高到4:1。相反,你必须把你的程序看作是由按顺序执行的主要块组成的

  1. 对一些数据做FFT吗
  2. 把结果放到一个矩阵中,然后找出它的特征值和特征向量
  3. 根据特征值对后者进行排序
  4. 用新的数据重复第一步
你能做的是在不同的核心上运行步骤1的结果,同时在新的数据上运行步骤1,在不同的核心上运行步骤3,而步骤2在下一个数据上运行,步骤1在之后的数据上运行。 你可以在Compaq Visual Fortran和Intel Fortran中做到这一点,它是CVF的进化,为这三个步骤编写三个独立的程序/子程序,而不是一个“调用”。然后调用API来启动线程。 它们可以通过使用COMMON来共享数据,这将是所有线程的公共数据内存。 你必须研究手册,直到你头痛,实验,直到你工作,但我至少成功了一次