长期被认为"天生不适合做实时"的 Linux,在合理调试配置与核心隔离之后,也能获得接近裸机级的确定性。本文用一套可复现的 TSC 抖动测试,把这场吵了很多年的争论变成一条可以精确测量的频谱:在 AMD Ryzen 9 9950 的完全隔离核心上,1 小时、数千亿次采样中,最大抖动仅约 10 纳秒,超过 100 个时钟周期(约 20 纳秒)的抖动事件一次都没有。

一、这场争论本身,可能就问错了

实时系统圈子里,"Linux 到底能不能做实时操作系统",是一个吵了很多年的问题。

反对者的理由看起来相当充分:Linux 本质上是为吞吐量而生的通用操作系统,它拥有庞大的调度子系统、复杂的锁机制、深度的电源管理栈,以及无处不在的内核线程和延迟工作队列。即便打上 PREEMPT_RT 补丁、把内核中大部分临界区转化为可抢占代码,也常被看作"在一个天生不具备确定性的系统之上,强行施加插队机制"。

支持者同样有理有据:PREEMPT_RT 早已合入主线内核,约八成代码就位;高频交易、工业控制、机器人等领域已有大量成功部署,调度延迟稳定在几十微秒以内。

但这场争论,可能本身就问错了——实时性的本质,从来不是"能不能"的二选一

二、实时性不是"开关",而是一条"频谱"

任何操作系统,只要它管理并发、仲裁资源、响应中断,就必然会引入时间上的不确定性。Linux 的复杂度高于传统 RTOS,意味着同等条件下它的抖动上界确实更高——但这不等于"不能做实时",而是意味着"需要更多工程手段来逼近确定性"。

反过来看,传统 RTOS 一旦迁移到多核平台,同样要面对核间中断、全局锁、缓存一致性协议、核间负载均衡等复杂机制,其复杂度并不亚于一个精简配置的 Linux。把传统 RTOS 等同于"确定性的保证",其实是一种幸存者偏差。

世界上唯一能做到"绝对确定"的执行环境,只有裸机:没有调度器、没有中断框架、没有锁,代码就是指令序列本身,执行路径完全可预测。

所以,正确的提问方式不是"哪个系统能做实时",而是:经过特定的工程配置后,你的系统抖动能逼近裸机到什么程度?用什么样的方法,才能把这段距离精确地测出来?

三、从 nohz_full 到"完全隔离":把核心"隔离到绝"

Linux 社区其实早就给出过一个答案——nohz_full 核心隔离:当隔离核心上只有一个可运行任务时,内核停止向它发送周期性的时钟中断(tick),从而消除最大的周期性噪声源。

但这并不彻底。根据 Linux 内核官方文档(cpu-isolation.rst)与社区实践,nohz_full 隔离的核心仍会遭受至少五类"残留干扰":

  1. 1Hz 残余 tick:housekeeping 核心仍会代表隔离核心执行每秒一次的远程调度统计,某些情况下仍会落在隔离核心上;
  2. 硬件中断未隔离干净:网卡、NVMe 等设备中断默认由 irqbalance 分配,即便设置了 irq affinity,部分 managed IRQ 仍可能落在隔离核心上;
  3. 核间中断(IPI):TLB 刷新、函数调用 IPI(smp_call_function)、调度迁移等机制会无条件中断目标核心——这是 nohz_full 从未解决的盲区;
  4. RCU 回调噪声:RCU 状态机仍可能在隔离核心上产生干扰;
  5. SMI(系统管理中断):来自 BIOS 层的"隐形"中断,对操作系统完全透明,任何内核参数都无法控制它。

一句话概括:这就像给运动员清好了跑道,却仍有陌生人随时闯进来。

望获OS 的做法,是把隔离"做绝":

  1. 禁用系统中断:不再只是"尽量不分配",而是从根本上禁止隔离核心响应任何系统中断;
  2. 禁用核间中断 IPI:阻断来自其它核心的 TLB flush、函数调用 IPI 等一切核间干扰;
  3. 彻底清除残余 tick:消除 1Hz 远程调度 tick 的干扰;
  4. BIOS 层面规避 SMI:通过配置限制深 C-state,降低 SMI 发生频率。

处理之后,隔离核心上不再存在任何操作系统层面的异步干扰源——它表现得像裸机一样确定。这是一句可以验证的工程声明,而验证它,需要一把比传统工具更硬的"尺子"。

四、为什么 cyclictest 不够?

cyclictest 是 Linux 实时性评测的事实标准:让一个高优先级线程按固定间隔唤醒,再测量实际唤醒时间与预期时间的偏差,从而统计调度延迟。它简单、易用,但也有几个结构性局限:

  1. 测的是调度行为的确定性,而非系统抖动的确定性。cyclictest 的核心路径是"定时器到期→硬中断→唤醒线程→调度器调度→线程执行测量",得到的是调度层面的延迟。而"调度延迟低"不等于"CPU 执行指令时不会被 SMI、IPI、缓存迁移等异步事件打断"——后者才是指令流的真实抖动,cyclictest 对此完全"无感"。
  2. 结果偏乐观。cyclictest 使用 clock_nanosleep 唤醒,定时器到期路径直接在硬中断上下文执行;而真实应用通常经过线程化中断上下文,存在额外间接开销,因此测得的是"下界"而非真实延迟。
  3. 黑盒测量,无法定位根因。它只告诉你延迟是多少,不告诉你为什么。借助 ftrace 等工具排查时,tracing 本身的开销又会掩盖真正的延迟源。
  4. 周期性唤醒会漏测非周期性干扰。如果 SMI、stop_machine、模块加载恰好发生在两次唤醒之间,其影响可能被完全错过。

五、TSC 抖动测试:用一把硬件级的"尺子"直接量抖动

如果目标是评估"隔离核心上的执行环境是否像裸机一样确定",就要直接在指令执行层面测量时间不确定性。我们选择的基准,是 x86 处理器的 TSC(Time Stamp Counter,时间戳计数器)。

TSC 是处理器内部的 64 位计数器,每个时钟周期自增一次,通过 RDTSC 指令读取。它天然具备四个非常适合当"标尺"的特性:

  1. 恒定频率(Invariant TSC):不随 CPU 频率缩放和电源状态变化;
  2. 跨核同步:多核处理器上,所有核心的 TSC 保持一致;
  3. 极低读取开销:RDTSC 指令本身仅需几十个时钟周期;
  4. 不可被软件干扰:计数过程是纯硬件的,不存在软件层面的干扰。

方法本身非常朴素:在一个紧凑循环里,背靠背连续执行两次 RDTSC,计算差值。 两次读取之间的指令序列是固定的,如果执行环境绝对确定,这个差值应当纹丝不动;任何异步干扰——中断、IPI、SMI、调度抢占——都会让差值"跳"一下。于是,差值的波动,就成了环境抖动的直接度量。

为了让这把"尺子"足够灵敏,测试代码做了几个刻意设计:

  1. 循环内全部使用寄存器操作、不访问内存:消除内存访问延迟波动对测量的污染;
  2. 不插入任何串行化指令(lfence/cpuid/rdtscp):传统实践会在两次读取间插入串行化指令以保证顺序,这里反其道而行——故意让 CPU 的乱序执行引擎把两次 RDTSC 尽量重叠执行,把差值压向硬件极限;
  3. 只比较低 32 位:减少指令数量,缩短两次读取之间的测量窗口;
  4. 连续运行 1 小时:覆盖足够大的统计样本量,捕捉罕见的长尾抖动;
  5. 单独统计超过 100 个时钟周期的事件数:量化"异常抖动"的频率,而不只是看极值。

测试代码还通过 CPUID 0x15 硬件接口读取 TSC 频率(处理器不支持时回退到 clock_gettime 标定法),保证结果能以纳秒为单位、跨机器可比。代价是两次 RDTSC 之间无数据依赖、架构上不保证读取顺序,极端情况下差值可能回绕成一个较大的值——但 1 小时内数千亿次采样中,这种事件的实际概率低到可以忽略。

六、测试代码解读

以下是我们的TSC抖动测试代码任何x86平台均可编译运行。

  1. /* 
  2.  * tsc.c - x86 TSC (Time Stamp Counter) 读取开销与抖动测量工具 
  3.  * 
  4.  * 功能: 
  5.  *   在指定时长(RUN_SECONDS)内,反复执行"背靠背两次 rdtsc", 
  6.  *   统计两次读取之间差值(diff,单位:TSC 周期数)的: 
  7.  *     - 最小值 min  :理论上代表 rdtsc 指令本身的最小执行开销 
  8.  *     - 最大值 max  :代表测量期间出现过的最坏抖动(中断、SMI、 
  9.  *                     频率切换、缓存/流水线停顿等都可能导致) 
  10.  *     - 大抖动计数 big:diff > 100 周期的次数,用于衡量系统实时性 
  11.  * 
  12.  * 用途:评估 x86 平台(尤其是工控/实时场景)的时钟读取开销与 
  13.  *       系统抖动水平,常用于实时性(latency)摸底测试。 
  14.  */  
  15.   
  16. #include <stdio.h>  
  17. #include <stdint.h>  
  18. #include <inttypes.h>  
  19. #include <time.h>  
  20.   
  21. #define VERSION "2.5"          /* 程序版本号,打印时输出便于追溯 */  
  22. #define RUN_SECONDS 3600       /* 测量总时长:3600 秒 = 1 小时 */  
  23.   
  24. /* 
  25.  * rdtsc - 读取 CPU 的 TSC 寄存器(64 位时间戳计数器) 
  26.  * 
  27.  * RDTSC 指令把 TSC 的低 32 位放入 EAX,高 32 位放入 EDX。 
  28.  * 这里用 "=a"/"=d" 约束直接绑定到 rax/rdx 寄存器, 
  29.  * 再拼成完整的 64 位返回值。 
  30.  * 
  31.  * 注意:RDTSC 不是串行化指令,乱序引擎可能让它提前/延后执行。 
  32.  * 本程序恰恰利用这一点来测"最小开销",详见 main() 中的注释。 
  33.  */  
  34. static inline uint64_t rdtsc(void)  
  35. {  
  36.     uint32_t lo, hi;  
  37.     __asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));  
  38.     return ((uint64_t)hi << 32) | lo;  
  39. }  
  40.   
  41. /* 
  42.  * now_sec - 获取 CLOCK_MONOTONIC 单调时钟,转换为秒(double) 
  43.  * 
  44.  * 只用于 tsc_freq() 的回退测量路径(用墙上时间标定 TSC 频率), 
  45.  * 不参与核心测量循环,因此精度要求不高。 
  46.  */  
  47. static double now_sec(void)  
  48. {  
  49.     struct timespec ts;  
  50.     clock_gettime(CLOCK_MONOTONIC, &ts);  
  51.     return ts.tv_sec + ts.tv_nsec / 1e9;  
  52. }  
  53.   
  54. /* 
  55.  * tsc_freq - 获取 TSC 频率(单位 Hz) 
  56.  * 
  57.  * 优先路径:CPUID.15H(Time Stamp Counter and Nominal Core 
  58.  *   Crystal Clock Information Leaf) 
  59.  *   - EAX = 分母 denominator 
  60.  *   - EBX = 分子 numerator 
  61.  *   - ECX = 晶体时钟频率(Hz) 
  62.  *   TSC 频率 = ECX * EBX / EAX 
  63.  *   这是 Intel SDM 规定的标准方法,Skylake 及以后的 CPU 支持。 
  64.  *   若任一返回值为 0,说明该 CPU 不支持此叶子,走回退路径。 
  65.  * 
  66.  * 回退路径:软件测量法 
  67.  *   记录起始 TSC 值 c0 和起始单调时钟 t0,忙等约 0.1 秒, 
  68.  *   用 (TSC 增量) / (时间增量) 估算频率。 
  69.  *   0.1 秒的窗口足以把计时误差压到可接受范围(<0.1%)。 
  70.  */  
  71. static double tsc_freq(void)  
  72. {  
  73.     uint32_t a, b, c, d;  
  74.     /* "c"(0) 表示 ECX 输入为 0(选择子叶 0) */  
  75.     __asm__ volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d)  
  76.              : "a"(0x15), "c"(0));  
  77.     if (a && b && c)  
  78.         return (double)c * b / a;  
  79.   
  80.     double t0 = now_sec();  
  81.     uint64_t c0 = rdtsc();  
  82.     double t1;  
  83.     do {  
  84.         t1 = now_sec();  
  85.     } while (t1 - t0 < 0.1);  
  86.     return (rdtsc() - c0) / (t1 - t0);  
  87. }  
  88.   
  89. int main(void)  
  90. {  
  91.     double freq = tsc_freq();      /* TSC 频率 (Hz),用于把周期数换算成时间 */  
  92.   
  93.     /* 统计变量,全部强制驻留寄存器(见下方汇编约束 "+r"): 
  94.      *   max:观测到的最大 diff(最坏抖动) 
  95.      *   min:观测到的最小 diff(rdtsc 最小开销),初值取最大便于比较 
  96.      *   big:diff > 100 周期的事件计数 
  97.      *   end:结束时刻的 TSC 值 = 当前 TSC + 频率 * 秒数 */  
  98.     uint64_t max = 0, big = 0, min = UINT64_MAX;  
  99.     uint64_t end = rdtsc() + (uint64_t)(freq * RUN_SECONDS);  
  100.   
  101.     printf("TSC freq: %.3f MHz [v%s]\n", freq / 1e6, VERSION);  
  102.   
  103.     /* 
  104.      * ============ 核心测量循环(手写内联汇编) ============ 
  105.      * 
  106.      * 设计目标:测出"背靠背两次 rdtsc"的最小间隔,以此逼近 
  107.      * rdtsc 指令的硬件极限开销。为此做出如下权衡: 
  108.      * 
  109.      * 1) 不插入任何串行化指令(lfence / cpuid / rdtscp)。 
  110.      *    串行化会强制排空流水线,两次 rdtsc 之间至少隔几十 
  111.      *    个周期,测到的是"串行化开销"而非 rdtsc 本身开销。 
  112.      *    不加串行化时,乱序引擎可以把两次读取重叠发射, 
  113.      *    diff 可压到接近硬件下限(现代 Intel 通常 20~30 周期)。 
  114.      * 
  115.      * 2) 全部统计量(max/min/big)和循环控制都在寄存器中 
  116.      *    完成,循环体内零内存访问,避免缓存 miss / 栈访问 
  117.      *    引入额外抖动,保证测到的 diff 纯粹反映 rdtsc 行为。 
  118.      * 
  119.      * 代价与风险: 
  120.      *    两次 rdtsc 之间没有数据依赖,架构上不保证执行先后。 
  121.      *    若乱序引擎让第二次 rdtsc 先于第一次执行,减法结果 
  122.      *    会回绕成接近 2^32 的大正数,污染 max/big 统计。 
  123.      *    实际硬件上 rdtsc 基本按程序序退休,发生概率极低, 
  124.      *    且这种污染一眼就能从 max 值(巨大)识别出来。 
  125.      * 
  126.      * 寄存器分配(由编译器保证,约束见下方): 
  127.      *   %[max] %[min] %[big] %[end] —— 通用寄存器("+r"/"r") 
  128.      *   eax/edx —— rdtsc 的固定输出(低/高 32 位) 
  129.      *   esi/ecx —— 暂存第一次/第二次 rdtsc 的低 32 位 
  130.      *   r11     —— 临时计算 big 增量(0 或 1) 
  131.      * 
  132.      * 为什么比较低 32 位就够? 
  133.      *   两次背靠背读取的间隔远小于 2^32 个周期(在 GHz 级 
  134.      *   TSC 下也对应数秒),diff 不可能溢出 32 位,因此只 
  135.      *   比较低 32 位即可,省去 64 位拼接,进一步压缩循环体。 
  136.      */  
  137.     __asm__ volatile(  
  138.         "1:\n\t"                     /* 循环顶标签(局部标签,用 1b 回跳) */  
  139.         "rdtsc\n\t"                  /* 第一次读 TSC:eax=低32位, edx=高32位 */  
  140.         "movl %%eax, %%esi\n\t"      /* 保存第一次的低 32 位到 esi */  
  141.         "rdtsc\n\t"                  /* 第二次读 TSC(背靠背,无串行化) */  
  142.         "movl %%eax, %%ecx\n\t"      /* 保存第二次的低 32 位到 ecx */  
  143.         "subl %%esi, %%ecx\n\t"      /* ecx = 第二次 - 第一次 = diff(32位) */  
  144.   
  145.         /* --- 更新 max:无分支,用 cmov 避免条件跳转引起的流水线气泡 --- */  
  146.         "cmpq %[max], %%rcx\n\t"     /* 比较 diff 与当前 max */  
  147.         "cmovaq %%rcx, %[max]\n\t"   /* diff > max 时更新 max(a = above,无符号) */  
  148.   
  149.         /* --- 更新 min:同理,无分支 --- */  
  150.         "cmpq %[min], %%rcx\n\t"     /* 比较 diff 与当前 min */  
  151.         "cmovbq %%rcx, %[min]\n\t"   /* diff < min 时更新 min(b = below,无符号) */  
  152.   
  153.         /* --- 统计大抖动(diff > 100 周期):setcc 无分支实现 --- 
  154.          * 等价于:big += (diff > 100) ? 1 : 0 */  
  155.         "xorl %%r11d, %%r11d\n\t"    /* r11 = 0 */  
  156.         "cmpl $100, %%ecx\n\t"       /* 比较 diff 与阈值 100 */  
  157.         "seta %%r11b\n\t"            /* diff > 100 则 r11b = 1,否则 0 */  
  158.         "addq %%r11, %[big]\n\t"     /* 累加到 big */  
  159.   
  160.         /* --- 循环终止判断:用第二次 rdtsc 的完整 64 位值 --- 
  161.          * 拼接 rdx:rax(高32:低32)得到完整 TSC,与结束值比较。 
  162.          * 用完整 64 位是防止 1 小时测量内低 32 位回绕导致提前退出。 */  
  163.         "shlq $32, %%rdx\n\t"        /* rdx 高 32 位左移到位 */  
  164.         "orq %%rax, %%rdx\n\t"       /* 拼入低 32 位,rdx = 完整 TSC */  
  165.         "cmpq %[end], %%rdx\n\t"     /* 与结束时刻比较 */  
  166.         "jb 1b\n\t"                  /* 未到结束时间则跳回循环顶 */  
  167.   
  168.         /* 输出/输入约束:"+r" 表示读写(统计量),"r" 表示只读(end) */  
  169.         : [max] "+r" (max), [min] "+r" (min), [big] "+r" (big)  
  170.         : [end] "r" (end)  
  171.         /* clobber 列表:告诉编译器这些寄存器被破坏,不要在里面存值 */  
  172.         : "rax""rcx""rdx""rsi""r11""cc");  
  173.   
  174.     /* --- 结果换算与输出:周期数 / 频率 = 秒,再换算成 ns/us --- */  
  175.     double ns = max / freq * 1e9;  
  176.     double ns_min = min / freq * 1e9;  
  177.     printf("min diff: %" PRIu64 " cycles (%.2f ns)\n", min, ns_min);  
  178.     printf("max diff: %" PRIu64 " cycles (%.2f ns, %.3f us)\n",  
  179.            max, ns, ns / 1e3);  
  180.     printf("big jitter (>100 cycles): %" PRIu64 " events\n", big);  
  181.     return 0;  
  182. }  

代码解读

1TSC频率获取(tsc_freq 函数):

优先使用CPUID.15H叶节点直接读取处理器报告的TSC频率。这是Intel/AMD在较新处理器上提供的硬件接口,能给出精确的TSC频率比值。如果处理器不支持,则回退到标定法:用clock_gettime测量一段已知时间(0.1秒)内的TSC增量来计算频率。

2核心测量循环(内联汇编):

循环体由以下指令组成:

  1.     "rdtsc\n\t"                 /* 第一次读 TSC:eax=低32位, edx=高32位 */  
  2.     "movl %%eax, %%esi\n\t"     /* 保存第一次的低 32 位到 esi */  
  3.     "rdtsc\n\t"                 /* 第二次读 TSC(背靠背,无串行化) */  
  4.     "movl %%eax, %%ecx\n\t"     /* 保存第二次的低 32 位到 ecx */  
  5.     "subl %%esi, %%ecx\n\t"     /* ecx = 第二次 - 第一次 = diff(32位) */  

随后用cmp / cmov指令组更新maxmin统计,并检查diff是否超过100个cycle(若超过则big 计数器+1,用于统计"大抖动事件"次数)。最后用第二次rdtsc的完整64位值判断是否到达测试时长。

3关键设计点:

  1.      * 设计目标:测出"背靠背两次 rdtsc"的最小间隔,以此逼近 
  2.      * rdtsc 指令的硬件极限开销。为此做出如下权衡: 
  3.      * 
  4.      * 1) 不插入任何串行化指令(lfence / cpuid / rdtscp)。 
  5.      *    串行化会强制排空流水线,两次 rdtsc 之间至少隔几十 
  6.      *    个周期,测到的是"串行化开销"而非 rdtsc 本身开销。 
  7.      *    不加串行化时,乱序引擎可以把两次读取重叠发射, 
  8.      *    diff 可压到接近硬件下限(现代 Intel 通常 20~30 周期)。 
  9.      * 
  10.      * 2) 全部统计量(max/min/big)和循环控制都在寄存器中 
  11.      *    完成,循环体内零内存访问,避免缓存 miss / 栈访问 
  12.      *    引入额外抖动,保证测到的 diff 纯粹反映 rdtsc 行为。 
  13.      * 
  14.      * 代价与风险: 
  15.      *    两次 rdtsc 之间没有数据依赖,架构上不保证执行先后。 
  16.      *    若乱序引擎让第二次 rdtsc 先于第一次执行,减法结果 
  17.      *    会回绕成接近 2^32 的大正数,污染 max/big 统计。 
  18.      *    实际硬件上 rdtsc 基本按程序序退休,发生概率极低, 
  19.      *    且这种污染一眼就能从 max 值(巨大)识别出来。 

、实测:1 小时、数千亿次采样,最大抖动约 10 纳秒

测试环境为 AMD Ryzen 9 9950(TSC 频率约 5 GHz),在望获OS 完全隔离的核心上连续运行 1 小时:

  1. 最大差值约 43 个时钟周期(约 10 纳秒)——1 小时内最大抖动;
  2. 超过 100 个时钟周期(约 20 纳秒)的"大抖动"事件:0 次。

操作系统的确定性不是"有和无"的问题,而是一条连续的频谱——你的隔离做到什么程度,抖动就降到什么程度。

、10 纳秒的确定性,意味着什么?

对绝大多数实时控制场景来说,10 纳秒级的确定性几乎是"奢侈"的:微秒级的伺服控制环、高频交易的下单链路、精密测量仪器,它们的控制周期通常在微秒到毫秒级,抖动裕量高出需求两到三个数量级。

更关键的是,这套 TSC 抖动测试方法是操作系统无关的——只要是 x86 平台,无论 Linux、RT-Linux、传统 RTOS 还是裸机,都能运行同一套代码、产出完全可比的指标。这也让它成为横评"软件确定性"的一把客观标尺。

、结语:确定性,是工程能力

回到开头那个吵了很多年的问题:Linux 能不能做实时操作系统?答案是——。但前提是:你的隔离做到了什么程度,你的测试测到了什么层面。

望获OS 的实践证明:通过完全隔离一个核心、禁用其系统中断与核间中断,完全可以在操作系统环境下获得接近裸机级的确定性。我们想做的,不只是做出一款"能实时"的操作系统,更是让"国产芯片 + 国产实时操作系统"这条路线,也能把确定性做到极致。

相关镜像和测试代码即将在望获OS v3.0 版本中发布,欢迎关注我们,后续还会分享更多的技术干货

附:面向大众的"术语小课堂"

  1. 实时操作系统(RTOS):必须在限定时间内完成响应的操作系统,常见于工业控制、医疗设备、航空航天等对时间敏感的领域。
  2. PREEMPT_RT:Linux 内核的实时化补丁,已随主线内核发布,通过让内核临界区可抢占来改善调度延迟。
  3. TSC(时间戳计数器):处理器内部的时间戳计数器,每个时钟周期自增一次,是 x86 平台上最准的"秒表"之一。
  4. tick:操作系统的心跳节拍,内核靠它来指挥调度;心跳越频繁,对隔离核心的干扰也越多。
  5. nohz_full:Linux 提供的一种核心隔离机制,让隔离核心停止周期性心跳。
  6. IPI(核间中断):一颗核心请求另一颗核心"搭把手"的信号,是隔离核心最难防的干扰源之一。
  7. SMI(系统管理中断):来自 BIOS 层的隐蔽中断,操作系统看不见也管不了,几乎无法用软件消除。
  8. cyclictest:Linux 实时性评测的"行业标准"工具,但测的是调度延迟,对指令执行层面的抖动并不敏感。

https://www.bilibili.com/video/BV1QwtP6vEZp/?spm_id_from=333.1387.homepage.video_card.click

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐