当前位置: 首页 > news >正文

linux 内存屏障

Linux 内存屏障是用于控制 CPU 与编译器内存访问重排、保证多核 / 设备间可见性与顺序的底层同步原语,核心分为编译器屏障CPU 硬件屏障SMP 专用屏障ACQUIRE/RELEASE 单向屏障DMA/MMIO 专用屏障

为什么需要内存屏障

  • 编译器优化:会重排、合并、消除内存访问,破坏预期顺序。
  • CPU 乱序执行:现代 CPU 为性能会乱序执行访存指令,多核间可见顺序可能与代码顺序不一致。
  • 设备交互(MMIO/DMA):设备要求严格访问顺序,重排会导致硬件行为异常。

编译器屏障(仅影响编译器)

编译器屏障 = 只阻止【编译器】重排代码,不产生任何 CPU 指令,对 CPU 运行完全无影响,0 运行时开销。

  • barrier()__asm__ __volatile__("" ::: "memory"),阻止编译器跨屏障重排内存访问,不影响 CPU
  • READ_ONCE() / WRITE_ONCE():防止编译器对单变量的撕裂、合并、重排,保证单次访问的原子性与可见性假设 。

它到底解决什么问题?

编译器为了优化速度,会偷偷改变你的代码执行顺序,比如:

你写的代码:

a = 1; b = 2;

编译器优化后可能变成:

b = 2; a = 1;

只要单线程运行结果一样,编译器就敢乱排。

但在多核 / 无锁 / 驱动场景,这种重排会导致严重 bug!

编译器屏障就是用来禁止编译器做这种跨屏障的重排。

Linux 内核里的标准写法

barrier();

它的本质是一段内嵌汇编:

#define barrier() __asm__ __volatile__("": : :"memory")

作用:

  1. 告诉编译器:内存变了,你不知道发生了什么
  2. 编译器不能把屏障上方的内存访问重排到下方
  3. 编译器不能把屏障下方的内存访问重排到上方
  4. 编译器必须把寄存器里的缓存变量写回内存
  5. 不生成任何 CPU 指令

关键特性

1. 0 开销!

运行时不执行任何指令,不耗 CPU 周期。

2. 只管编译器,不管 CPU

  • ✅ 阻止编译期重排
  • 完全管不了 CPU 运行期的乱序执行

3. 强制 “内存同步”

  • 屏障前,寄存器里的变量必须写回内存
  • 屏障后,变量必须重新从内存读取
  • 防止编译器把变量 “锁在寄存器里”

4. 是所有 SMP 屏障的基础

smp_rmb() smp_wmb() smp_mb()

单核 CPU上,全部退化为barrier()

CPU 硬件屏障(影响 CPU + 编译器)

CPU 硬件屏障 = 强制 CPU 遵守内存访问顺序,禁止运行时乱序执行,会真实生成指令,有性能开销。

它和编译器屏障完全不是一个东西:

  • 编译器屏障:管编译阶段
  • CPU 硬件屏障:管CPU 实际执行阶段

现代 CPU 为了快,会:

  1. 乱序执行指令
  2. 把写操作先放进store buffer
  3. 把读操作放进load buffer
  4. 多核之间缓存同步有延迟

结果就是:代码顺序 ≠ CPU 执行顺序 ≠ 其他核看到的顺序

硬件屏障就是用来:让 CPU 停下来,把缓存 / 缓冲同步干净,再继续执行。

mb()—— 全内存屏障

  • 作用:屏障之前所有的读、写必须全部全局可见之后才能执行屏障之后的任何读、写
  • 对应指令:
    • x86:mfence
    • ARM64:dsb ish/dmb ish
  • 开销:最大

rmb()—— 读屏障

  • 只约束读 ↔ 读顺序
  • 屏障前的所有读 → 先完成
  • 屏障后的所有读 → 后执行
  • 不限制写
  • 开销:中等

wmb()—— 写屏障

  • 只约束写 ↔ 写顺序
  • 屏障前的所有写 → 先全局可见
  • 屏障后的所有写 → 后开始
  • 不限制读
  • 开销:中等偏小

SMP 专用屏障(多核场景)

SMP 屏障 = 多核环境下才生效的 CPU 硬件屏障;单核环境下,自动退化成 0 开销的编译器屏障。

内核里就是这 4 个:

smp_mb() 全屏障 smp_rmb() 读屏障 smp_wmb() 写屏障 smp_read_barrier_depends() 数据依赖屏障

内核里正常并发编程几乎都用这一组

  • smp_mb()
  • smp_rmb()
  • smp_wmb()

规则:

  • SMP 内核:等价于mb() / rmb() / wmb()
  • UP 单核内核:自动退化为编译器屏障 barrier ()

目的:在不需要硬件屏障的场景自动省掉开销,提高性能。

为什么要设计 SMP 专用屏障?

因为:

  1. 单核(UP):不存在多核乱序,不需要 CPU 屏障,只要阻止编译器重排就行。
  2. 多核(SMP):必须用CPU 硬件屏障才能保证顺序。

Linux 内核希望一套代码兼容单核 + 多核,于是发明了smp_*()系列屏障。

#ifdef CONFIG_SMP #define smp_mb() mb() // 多核:用真正的硬件屏障 #else #define smp_mb() barrier() // 单核:只保留编译器屏障(0开销) #endif

这就是 SMP 屏障的全部秘密!

SMP 屏障 vs 全局硬件屏障

屏障作用多核 SMP单核 UP适用场景
smp_mb()SMP 全屏障CPU 硬件屏障编译器屏障 (0 开销)多核并发、无锁、内核通用
mb()全局全屏障CPU 硬件屏障CPU 硬件屏障驱动 MMIO、DMA、硬件交互

关键区别:

  • smp_*():只为多核间同步服务
  • mb()/rmb()/wmb():不管单核多核,强制硬件屏障,用于CPU 和硬件设备通信
  • 内核并发、无锁、线程间同步 → 用 smp_*()
  • 驱动、硬件寄存器、DMA → 用 mb ()/rmb ()/wmb ()

本质区别:

  • smp_mb / smp_rmb / smp_wmb只解决多核之间乱序,单核时不生成 CPU 指令
  • mb / rmb / wmb无论单核多核,都强制生成 CPU 硬件屏障指令

定义上的区别(内核宏)

#ifdef CONFIG_SMP #define smp_mb() mb() #define smp_rmb() rmb() #define smp_wmb() wmb() #else #define smp_mb() barrier() // 单核 = 纯编译器屏障 #define smp_rmb() barrier() #define smp_wmb() barrier() #endif
  • smp_是条件宏*
  • mb/rmb/wmb 是真实硬件屏障

生效场景区别

smp_* 屏障

  • 多核 SMP 系统→ 等价于硬件屏障
  • 单核 UP 系统只做编译器优化禁止,不产生任何 CPU 指令
  • 目的:多核同步用,单核省性能

mb /rmb/wmb 硬件屏障

  • 不管单核还是多核
  • 永远生成硬件屏障指令(mfence、dmb ish 等)
  • 目的:控制 CPU 与硬件(MMIO、DMA、设备)的时序

作用对象区别

smp_*

只保证:CPU 核 ↔ CPU 核之间的内存可见顺序

mb/rmb/wmb

保证:CPU 核 ↔ 其他 CPU 核CPU 核 ↔ 总线 / 桥 / 设备 / DMA全部顺序都要遵守

性能开销区别

  • smp_mb:
    • 多核:有硬件开销
    • 单核:0 开销
  • mb:
    • 永远有硬件开销

使用场景分界线

用 smp_* 的场景(内核并发)

  • 多核之间无锁通信
  • 线程 / 进程之间共享数据
  • 自旋锁、RCU、无锁队列
  • 只要不碰硬件,都用 smp_*

用 mb/rmb/wmb 的场景(驱动 / 硬件)

  • 操作 MMIO 寄存器
  • DMA 缓冲区同步
  • 写设备寄存器后必须保证顺序
  • 任何CPU 和外设交互

什么时候必须用 smp_*()?

只要你写的是:

  1. 多核之间共享数据
  2. 无锁编程
  3. 内核同步逻辑(不是驱动)

一律用 smp_*(),不要用 mb ()!

经典例子:生产者 - 消费者(无锁)

// 生产者 CPU0 data = 100; smp_wmb(); // 多核:保证 data 先写完 ready = 1; // 消费者 CPU1 while (!ready); smp_rmb(); // 多核:保证后读 data print(data);

什么时候绝对不能用 smp_*()?

操作硬件寄存器、MMIO、DMA 时!

比如:

// 错误!驱动里不能用 smp_wmb() writel(1, REG_ADDR); smp_wmb(); // 单核下会变成0开销,硬件时序出错! // 正确!驱动必须用全局硬件屏障 wmb() writel(1, REG_ADDR); wmb();

原因:SMP 屏障只管 CPU 之间,不管 CPU 和硬件之间!

SMP 屏障的性能优势

  • 多核:提供必要的硬件安全
  • 单核:自动变成0 开销

这就是 Linux 高性能的秘密之一:不浪费任何一条指令!

不同架构开销差异

x86 /x86_64(强内存模型)

  • 天然不会:写→写重排、读→读重排
  • 所以:
    • smp_wmb()→ 通常只是编译器屏障
    • smp_rmb()→ 通常只是编译器屏障
    • 只有smp_mb()才是真实mfence屏障开销很低

ARM64 / RISC-V / PowerPC(弱内存模型)

  • CPU 允许大量重排
  • smp_rmb/smp_wmb/smp_mb都会生成真实的dmb / dsb指令→屏障开销明显更高

数据依赖屏障

这是Linux 内核里最轻量级的屏障只适用于一种极其特殊的场景:CPU 读取指针 -> 通过指针读取数据 之间的顺序保证

数据依赖屏障 = 专门保护 “指针读取 + 指针解引用” 的顺序

数据依赖屏障(Data Dependency Barrier)是最弱的读屏障,仅保证有数据依赖的读操作不被重排,比rmb轻量;

smp_read_barrier_depends():在多核SMP上等价于对应硬件屏障;在单核UP上退化为编译器屏障(barrier ()),兼顾性能与兼容性。

read_barrier_depends()数据依赖屏障,仅约束存在数据依赖的加载(如指针→数据)。

什么是数据依赖

数据依赖 = 后一次读的地址 / 值,完全由前一次读的结果决定

  • 典型模式:先读指针 → 再读指针指向的数据
  • 例:
    ptr = read_ptr(); // 读指针(Load 1) data = *ptr; // 读数据(Load 2,依赖 Load 1)
    这里Load 2的地址由Load 1决定,存在强数据依赖

屏障语义(严格约束)

  • 只约束:屏障前的读 → 屏障后有数据依赖的读,必须按序执行、按序可见。
  • 不约束:
    • 无依赖的读(如并行读两个独立变量)
    • 写操作
    • 读与写之间的顺序
  • 隐含:编译器屏障(阻止编译器重排依赖读)

强度关系(从弱到强)

smp_read_barrier_depends() < smp_rmb() < smp_mb()

  • smp_read_barrier_depends:仅约束依赖读
  • smp_rmb:约束所有读(隐含数据依赖屏障)
  • smp_mb:约束所有读写

它到底解决什么问题?

正常逻辑(我们期望的顺序)

  1. 生产者先写好数据
  2. 生产者再把数据地址赋值给指针
  3. 消费者先读指针
  4. 消费者通过指针读数据 →必须读到最新数据

可能出问题的情况

弱内存模型架构(如早期 Alpha)CPU 可能先读数据,后读指针,导致读到旧数据。

这时候就必须用:

ptr = READ_ONCE(shared_ptr); smp_read_barrier_depends(); // 数据依赖屏障 val = *ptr;

适用场景

条件 1:必须是指针依赖

必须是:

  • 先读一个指针变量
  • 再用这个指针去访问内存

不是指针依赖的场景,不能用

条件 2:必须是读 -> 读顺序

只保护两个读操作之间的顺序。写操作不能用它。

条件 3:必须是无锁编程

你在做无锁队列、RCU、无锁链表,不能用锁 / 全屏障,追求极致性能。

内核接口

// 全局数据依赖屏障(极少用) read_barrier_depends() // SMP专用(内核主流) smp_read_barrier_depends()

宏展开逻辑

// 内核定义(简化) #ifdef CONFIG_SMP // 多核:按架构实现依赖屏障(Alpha需指令,x86/ARM64多为空) #define smp_read_barrier_depends() __smp_read_barrier_depends() #else // 单核:完全空,0开销 #define smp_read_barrier_depends() do { } while (0) #endif

架构实现差异

  • DEC Alpha:必须生成硬件屏障指令(唯一强制架构)
  • x86/x86_64:硬件天然保证依赖读顺序 →smp_read_barrier_depends()为空
  • ARM64:硬件保证依赖读顺序 → 为空
  • 其他弱序架构:多数为空,仅编译器屏障

为什么需要它?(乱序风险)

1. 典型问题场景(生产者 - 消费者无锁)

// CPU1(生产者) data = 42; smp_wmb(); // 确保data先写完 ptr = &data; // 发布指针 // CPU2(消费者) while (!ptr); // 等待指针有效 // 风险:CPU2可能先读到ptr新值,却读到data旧值! val = *ptr; // 可能读到旧data(如0)

原因:在 Alpha 等架构,指针可见 ≠ 指针指向的数据可见,无屏障会出现 “指针新、数据旧” 的违反依赖的乱序。

2. 正确用法(插入依赖屏障)

// CPU2(消费者) while (!ptr); smp_read_barrier_depends(); // 依赖屏障:ptr可见后,*ptr才可见 val = *ptr; // 保证读到最新data

现代 CPU 上它的开销是多少?

x86 / ARM64 / RISC-V :数据依赖屏障 = 空指令,0 开销!这些架构硬件自动保证指针依赖顺序,不需要屏障指令。

只有古老的 Alpha CPU需要真正指令。

Linux 内核这么设计:

  • 现代 CPU:0 开销
  • 极端架构:自动提供保护
  • 代码可移植

ACQUIRE/RELEASE 单向屏障(锁语义)

这是现代 Linux 内核最推荐、性能最好、锁底层都在用的屏障模型,比smp_mb()轻得多,而且语义更清晰。

核心定义

  • ACQUIRE(获取):我读一个值,后面的内存访问不能跑到这次读前面
  • RELEASE(释放):我写一个值,前面的内存访问不能跑到这次写后面

它们是单向屏障,只限制一半重排,所以比全屏障快很多。

Linux 内核对应接口

// 读 + ACQUIRE 语义 smp_load_acquire(p); // 写 + RELEASE 语义 smp_store_release(p, val);

这两个是原子访问 + 屏障一体的接口,最常用。

  • smp_load_acquire()ACQUIRE,保证其后所有访存不被重排到屏障前。
  • smp_store_release()RELEASE,保证其前所有访存不被重排到屏障后。
  • 配对使用实现临界区 “获取 - 释放” 语义,比全屏障更高效。

ACQUIRE 语义(smp_load_acquire)

x = smp_load_acquire(&a); // ↓ 下面所有读写 不能 重排到 ↑ 上面

作用:

  • 保证读 a 之后的所有内存访问
  • 一定发生在读 a 完成之后

典型场景:拿到锁、拿到就绪标志、进入临界区

RELEASE 语义(smp_store_release)

// ↑ 前面所有读写 不能 重排到 ↓ 下面 smp_store_release(&a, x);

作用:

  • 保证写 a 之前的所有内存访问
  • 一定先全局可见,再执行写 a

典型场景:释放锁、标记就绪、退出临界区

为什么叫 “单向屏障”?

  • ACQUIRE:只限制后面 ← 往前跑
  • RELEASE:只限制前面 → 往后跑

不像smp_mb()前后都卡死,所以开销小很多

和 smp_mb () 的区别

屏障约束方向开销架构指令适用
smp_mb()双向全序mfence / dsb ish强顺序场景
ACQUIRE单向(后不超前)ldapr / ldar + acquire读锁、取标志
RELEASE单向(前不滞后)stlr + release写锁、发标志

ACQUIRE/RELEASE ≈ 轻量版半屏障

经典配对:生产者 - 消费者

// 生产者 data = 100; smp_store_release(&ready, 1); // RELEASE:前面写完再发标志 // 消费者 while (!smp_load_acquire(&ready)); // ACQUIRE:拿到标志再读后面 print(data);

这就是无锁编程最标准、最推荐写法

和锁的关系

自旋锁、mutex 底层本质就是:

// 加锁 lock(); smp_load_acquire(...) // 解锁 smp_store_release(...) unlock();

所以你用锁的时候,已经隐式用了 ACQUIRE/RELEASE

架构差异

  • x86:acquire/release 几乎0 额外开销x86 强内存模型天然保证大部分顺序,只需要编译器屏障。

  • ARM64

    • acquire →ldar/ldapr
    • release →stlr比 full barrier(dsb)快非常多。

什么时候用 ACQUIRE/RELEASE?

只要满足:

  1. 先发布数据,再设标志→ 用RELEASE
  2. 先读标志,再读数据→ 用ACQUIRE
  3. 临界区开始 / 结束 → 用ACQUIRE/RELEASE

90% 的无锁场景,都不需要 smp_mb (),用这俩就够。

DMA/MMIO 专用屏障

两个场景

  1. MMIO:读写设备寄存器(readl/writel
  2. DMA:CPU 写内存 → 设备去读;或设备写内存 → CPU 去读

这两种场景,CPU 乱序、桥片重排、写缓冲不刷新,都会直接硬件死机、数据错误

dma_rmb()/dma_wmb()

DMA 缓冲区专用轻量屏障

  • dma_wmb():保证 CPU 写到 DMA 内存的顺序,对 DMA 设备可见
  • dma_rmb():保证 CPU 从 DMA 内存读的顺序,和设备写入一致

特点:

  • rmb/wmb
  • 只保证设备可见性,不保证多核之间可见
  • 驱动操作 DMA 缓冲区标配

mmiowb()

MMIO 写屏障(MMIO Write Barrier)

专门保证:CPU 对设备寄存器的多次写操作,按代码顺序到达设备

很多 ARM/PCIe 桥片会重排 MMIO 写,不加mmiowb()会导致:

  • 设备配置顺序错乱
  • 驱动无法初始化
  • 硬件异常

全局硬件屏障

  • mb()全屏障
  • rmb()读屏障
  • wmb()写屏障

在驱动里,这三个是最强保证

  • 禁止 CPU 重排
  • 强制写缓冲排空
  • 对其他 CPU、对设备、对总线都强序

为什么不能用 smp_*()?

因为:

  • smp_wmb()单核内核下会变成纯编译器屏障,没有硬件指令
  • 但设备不管你是不是单核,必须强制顺序

所以:驱动 = 禁止使用 smp_rmb /smp_wmb/smp_mb

典型使用场景

场景 1:写 MMIO 寄存器

writel(REG1_VAL, addr1); mmiowb(); // 保证先写 addr1,再写 addr2 writel(REG2_VAL, addr2);

场景 2:DMA 缓冲区准备

/* CPU 填充 DMA 数据 */ buf[0] = 1; buf[1] = 2; dma_wmb(); // 保证数据写完,再通知硬件 /* 通知 DMA 引擎启动 */ writel(1, DMA_START_REG);

场景 3:读完 DMA 数据

/* 等待 DMA 完成 */ while (!(readl(DMA_STATUS_REG) & DMA_DONE)); dma_rmb(); // 保证先等完成,再读数据 /* 读取数据 */ val = buf[0];

整体总览(从弱到强)

编译器屏障 barrier() ↓ 数据依赖屏障 smp_read_barrier_depends() ↓ ACQUIRE / RELEASE 单向屏障 ↓ DMA 专用屏障 dma_rmb / dma_wmb ↓ 读/写屏障 smp_rmb / smp_wmb ↓ 全屏障 smp_mb ↓ 全局硬件屏障 rmb / wmb / mb(驱动/MMIO/DMA)

强度越强 → 约束越多 → 开销越大

一张表终极总结

屏障强度开销主要约束适用
barrier()10编译器单核、优化隔离
smp_read_barrier_depends()2≈0数据依赖读RCU、无锁指针
ACQUIRE/RELEASE3单向临界区锁、无锁队列
dma_rmb/dma_wmb4DMA 缓冲区驱动 DMA
smp_rmb/smp_wmb5较高全读 / 全写有序多核同步
smp_mb6全读写屏障强有序场景
mb/rmb/wmb7最高CPU + 设备全序驱动 MMIO/DMA

屏障语义与配对规则

  • 写屏障 ↔ 读屏障:生产者写数据→smp_wmb ()→写标志;消费者读标志→smp_rmb ()→读数据 。
  • 数据依赖屏障:读指针→smp_read_barrier_depends ()→读指针指向数据(Alpha 等宽松架构必需) 。
  • ACQUIRE ↔ RELEASE:锁的底层实现,临界区前 ACQUIRE、后 RELEASE。
  • 全屏障:读写混合强序场景,保证传递性 。

架构实现差异(底层指令)

  • x86mfence(mb)、lfence(rmb)、sfence(wmb)。
  • ARM64dsb ish(mb)、dmb ishld(rmb)、dmb ishst(wmb)。
  • RISC-Vfence指令组合。

隐式屏障(常用原语自带)

  • 自旋锁、互斥锁、信号量:隐含 ACQUIRE/RELEASE。
  • 原子操作(xchg、cmpxchg、atomic_*_return):隐含 SMP 全屏障。
  • schedule ()、wake_up ()、set_current_state ():隐含全屏障。

使用原则

  1. 优先 SMP 屏障:多核用smp_*,UP 自动退化。
  2. 设备交互用全局屏障:MMIO/DMA 必须用mb/rmb/wmb而非smp_*
  3. 优先高级原语:锁、原子、RCU 已内置屏障,尽量不手动加屏障。
  4. 配对使用:单侧屏障通常无效,需对端配合。

典型场景示例(生产者 - 消费者)

// 生产者 data = 100; smp_wmb(); // 确保data先写完 ready = 1; // 消费者 while (!ready); smp_rmb(); // 确保读ready后再读data print(data);

内存屏障会带来性能开销

会,而且开销不小,只是不同屏障、不同架构、不同场景下,开销差异极大。

简单结论先说:

  • 内存屏障一定会拖慢 CPU 执行效率
  • 越 “强” 的屏障,代价越大
  • ARM64 / RISC-V 这类弱序架构,屏障开销明显高于 x86
  • 多核高并发下,屏障会成为热点瓶颈

内存屏障本质是强制 CPU 放弃乱序优化,让流水线 “等一等”。

开销主要来自三件事:

  1. 清空 / 等待存储队列、加载队列CPU 内部有大量缓存的写缓冲、读缓冲、失效队列,屏障要等它们排空、同步完成才能继续。

  2. 打断流水线,阻塞后续指令屏障是 “串行点”,后面指令不能提前执行,流水线被堵。

  3. 触发缓存一致性同步(多核)SMP 屏障会触发 CPU 间缓存同步(MESI 协议),跨核通信本身就慢。

不同屏障的开销大小(从低到高)

1. 编译器屏障barrier()

几乎无开销

  • 只影响编译,不产生任何指令
  • 运行时代价 ≈ 0

2. 数据依赖屏障smp_read_barrier_depends()

开销极低

  • 大部分架构(x86/ARM64)直接是空操作
  • 只有 Alpha 这种极端架构才需要

3. ACQUIRE / RELEASE 屏障

smp_load_acquire/smp_store_release轻量级开销

  • 只限制单向重排,不强制全缓冲排空
  • 比 full barrier 快很多
  • 现代锁(mutex、spinlock)底层基本都用它

4. 读 / 写屏障rmb()/wmb()/smp_rmb()/smp_wmb()

中等开销

  • 只约束读或只约束写
  • 比全屏障轻,但仍然会阻塞流水线

5. 全内存屏障mb()/smp_mb()

开销最大

  • 强制所有读写按代码顺序全局可见
  • 排空所有写缓冲、等待读完成、同步缓存
  • 多核高并发下,连续smp_mb()性能会雪崩式下降

架构差异巨大(重点)

x86 / x86_64

  • 本身是强内存模型
  • 大部分读 / 写重排天然不会发生
  • smp_rmb()/smp_wmb()很多时候只是编译器屏障
  • 只有mfence这类全屏障才有真实开销

ARM64 / RISC-V / PowerPC

弱内存模型,屏障开销非常明显

  • 几乎所有smp_*屏障都会生成真实指令:
    • dmb ish
    • dsb ish
  • 频繁使用会显著影响吞吐量

Linux 内核为什么尽量少用全屏障?

内核开发者有个共识:

能不用 mb () 就不用,能用 smp_load_acquire/release 就不用 smp_mb ()

原因:

  1. 全屏障会序列化整个 CPU 流水线
  2. 多核下会引发缓存一致性风暴
  3. 高频路径(网络、调度、RCU)一旦滥用屏障,系统整体性能直接拉胯

什么时候开销可以忽略?

  • 屏障不在热点循环
  • 每秒执行次数很少(比如初始化、设备配置、慢速路径)
  • 单核 UP 环境下,smp_*屏障退化成编译器屏障,几乎无开销
http://www.cnnetsun.cn/news/2060003.html

相关文章:

  • AI Agent Harness Engineering 开源社区贡献指南:如何参与 LangChain 等项目开发
  • Red Panda Dev-C++:告别配置烦恼,3分钟开启高效C++编程
  • Video-Downloader终极指南:一键下载七大视频平台的完整教程
  • 基于Qwen2.5-VL的视觉定位应用:Chord模型快速部署与多场景实战案例
  • 微软ASP.NET Core更新引入严重安全漏洞,开发者需重新构建应用程序
  • 横评13款维普降aigc率工具实测,降重鸟稳居榜首
  • 智慧树自动刷课插件:3分钟实现高效学习的终极解决方案
  • Unity基础:游戏对象的激活与隐藏:SetActive方法详解
  • GPT-image-2 上手首测!超越 Banana 的它,凭什么是地表最强 AI 画师?
  • 保姆级教学:用FLUX.1-dev在ComfyUI中生成照片级真实感图片
  • 2026考什么互联网行业证书可以增加收入
  • 从memcpy到for循环:手把手教你解决C++ vector二维数组的拷贝崩溃问题
  • GhostTrack -- IP/phone/username查询工具
  • 离散数学核心三剑客:命题逻辑、谓词逻辑与集合关系的实战精解
  • Qwen3-14B生产环境部署:120GB内存保障+输出路径自定义实操
  • StreamCap:告别错过的直播,用这款开源工具自动录制40+平台直播内容
  • 470型角驰压瓦机
  • 别被代理忽悠了!程序员写给程序员的专利技术交底书避坑指南
  • 别再死记硬背Ceph架构图了!从PG、Pool到CRUSH,用大白话讲清数据到底怎么存的
  • 8088单板机NMI定时中断刷新8255动态显示
  • SCons构建MDK工程翻车实录:从‘No module named building’到完美运行的踩坑全指南
  • 记录生活&学习Day15深度强化学习第十六集:Advantage Actor-Critic(A2C)
  • Weka机器学习实战:鸢尾花分类完整教程
  • NVIDIA开发者课程:GPU加速AI与数据科学实战指南
  • 从密码学博士到造芯悍将:白剑的17年硬核之路
  • Real Anime Z新手教程:5分钟完成加载→输入提示→生成首图全流程
  • Vue 3 + Socket.io 实时聊天项目完整开发文档
  • Phi-3.5-mini-instruct入门必看:网页封装+参数详解+中文场景调优指南
  • Flux2-Klein-9B-True-V2多场景落地:政府宣传海报/公益广告图生成实践
  • Zotero AI插件完整指南:5分钟打造你的智能文献助手