如何使用rdtsc和C/C++来测量运行时间(如何使用内联汇编和获取CPU的TSC时钟频率)

news2024/9/23 23:34:24

本文主要是一个实验和思维扩展,除非你有特殊用途,不然不要使用汇编指令来实现这个功能。扩展阅读就列出了一些不需要内联汇编实现的

写本文是因为为了《Windows上的类似clock_gettime(CLOCK_MONOTONIC)的高精度测量时间函数》这篇文章找资料的时候,发现了一些关于测量时间如何实现的内容,所以出于好奇心进行了一些研究和实践。

本文是一个初步的尝试,旨在研究一些时间测量实现和在 C/C++ 中内联汇编和汇编函数的使用方法。

本文只尝试了 X86 的 macOS。因为没有使用系统内核,而是使用的内联汇编,所以理论上 Linux 也可以使用 macOS 的方法,只不过我没进行尝试(手上没有 X86 的 Linux 设备)。

如果你想在 Windows 中实现,那么请导入intrin.h,使用__rdtsc()__cpuid()(这两个 intrinsic 函数作用和本文写的大部分内联汇编一样或者可以等价替换),而不是使用内联汇编。因为 Visual Studio 内联汇编比 GCC 和 Clang 的复杂许多,考虑到多个兼容性,还是这样的好。本文虽然没有列出 Windows 的方法,但是在扩展阅读中尽量列出所需的资料,便于读者自己尝试。

本打算写一下 Windows 部分的,但是出于篇幅,还是不写了,博客太长对于读者和作者都不太好(个人喜好罢了)。

思路

这种测量时间的思路是通过计时器晶体的时钟计数做差,然后除以计时器的频率来获取时间,这种方法的计时误差主要取决于晶体质量。类似clock(),不过要复杂的多,因为这里的频率并不是clock()中简单的 1 MHz。

X86 上最常用的计时器是 CPU 上的 TSC,不过由于 BIOS 设置、CPU 是否支持 TSC 等诸多因素,常用的计时函数往往是使用了多个计时器来消除误差。本文并不会做到这种程度,只使用 TSC 计数和频率来实现计时。

我看了国内外很多文章,可以明显感觉到 x86 的 TSC 这个技术在 14 年前后(个人感觉)有了巨大变化,因为早期的博客、文章、代码等提及的一系列方法和问题与今天的情况几乎可以说是完全不同。这个时间段也与 Intel 官方文档中提及的一些处理器的更新和内容相关。

内联汇编实现

由于日常开发并不会出现纯汇编,所以使用内联汇编来降低复杂度和增加可读性。而且内联汇编也比较能展现汇编的结构,如果后续需要实现纯汇编,也比较方便。

确定设备有TSC,并且是不变的

第一步是要确定设备有 TSC,并且是不变的。如果是可变的,也就是会随着睿频、过热、核心等情况变化频率,那么这个方法是无法可靠地测量时间的。如果没有 TSC,那也就没必要下面的步骤了。

查看是否有 TSC 的方法是CPUID.1:EDX.TSC[bit 4] = 1,也就是使用0x1初始化,检查 CPUID 返回的EDX寄存器中第 4 位的值,如果为1则有 TSC。

查看是否有 TSC 的方法是使用0x80000007初始化,检查 CPUID 返回的EDX寄存器中第 8 位的值,如果为1则可以使用不变 TSC。

内联汇编如下:

#define BIT(nr) (1UL << (nr)) //方便确定哪一位的宏:获取第nr位的内容,右端为最小端

static inline void isTSC(void)
{
    uint32_t a=0x1, b, c, d;
    asm volatile ("cpuid"
         : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
         : "a" (a), "b" (b), "c" (c), "d" (d)
         );
    if ((d & BIT(4))) {
        printf("TSC exist!\n");
    } else 
    a=0x80000007;
    asm volatile ("cpuid"
         : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
         : "a" (a), "b" (b), "c" (c), "d" (d)
         );
    if ((d & BIT(8))) {
        printf("Invariant TSC available!\n");
    }
}
  • static inline这个是静态内联是为了提高程序性能,不过鉴于编译转换流程,有时候写了反而降低性能,所以写不写看情况。
  • asm volatile ("cpuid\n\t" : "=a" (a), "=b" (b), "=c" (c), "=d" (d) : "a" (a), "b" (b), "c" (c), "d" (d));这行就是内联汇编了。这部分的内容使用冒号:分隔,最多有三个部分,第一个部分是汇编指令,第二个部分是输出,第三个部分是输入(需要注意先是输出,再是输入)。比如这里的"a" (a), "b" (b), "c" (c), "d" (d)就是汇编的输出,输出eaxebxecxedx4 个寄存器(由于不同位的机器上寄存器名称不一样,但是是兼容的,所以直接使用不同处abcd表示寄存器)的值给了abcd4个变量。
  • 虽然这里只使用了eaxedx,但是最好还是都声明和输入一下,不然程序可能会闪退,因为有的指令需要使用其他的寄存器。比如说如果在 macOS 上确定是否不变 TSC 的时候,没有输入和输出ebxecx寄存器,那么会出现访问内存超出的错误。

输出为:

TSC exist!
Invariant TSC available!

需要注意的是,这里是使用打印信息来说明是否有二者,但是实际程序中,需要返回值来判断是否可以使用 TSC 来计时,最后的完整代码中会加入返回值和判断部分。

这时候就可以开始准备使用 TSC 来计时了。

使用内联汇编通过rdtsc获取 TSC 计数

处理器时间戳计数器的值是使用rdtsc指令从 64 位的 MSR(Model-specific register,特定模块寄存器)中读取到EDX:EAX两个寄存器中的。

如果你好奇 MSR,可以看看我的这篇博客:MSR是个什么寄存器 - ZhongUncle CSDN

包装内联汇编的 C/C++ 函数如下:

static inline unsigned long long rdtsc(void)
{
    unsigned long long low, high;
    asm volatile ("rdtsc" : "=a" (low), "=d" (high));
    return low | (high << 32);
}

low | (high << 32)是因为edx寄存器加载了 MSR 的高阶32位,eax寄存器加载了低阶32位。所以为了得到 64 位的 TSC 计数,要将高位的部分左移 32 位,然后再将二者进行或|运算得到 64 位的无符号数。

输出一个值看看效果:

printf("%llu\n", rdtsc());
printf("%llu\n", rdtsc());

输出结果为:

13207359930699
13207359964314

使用内联汇编获取cpuid中 TSC 频率

知道了 TSC 计数的值,如何获取计算时间所需的频率呢?

这是整篇文章中最困难的一段:因为出于理解、安全等诸多原因,直接暴露 TSC 值是不好的。这就导致需要一定的操作才能获得 TSC 频率,并不像计数一样可以轻松获得。

简单例子

先列出代码,然后再进行说明,因为这里情况实在是有点复杂。代码实现如下(下面的24000000需要根据不同的处理器设置不同的值,看完下面的内容你就知道是怎么得到的了,并且最后会弄出一个比较通用的办法):

static inline uint32_t tsc_freq(void)
{
    uint32_t a=0x15, b, c=0, d;
    asm volatile ("cpuid"
         : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
         : "0" (a), "1" (b), "2" (c), "3" (d)
         );
    return b/a*24000000;
}

代码解释比较长,没法写成一个列表,所以请仔细看下面的内容。

TSC 频率是如何获取的呢?

为什么说情况有些复杂呢?因为早期处理器和现在的处理器的 TSC 频率获取不是一回事。

很多文章、博客、书籍获取 TSC 频率由于获取 TSC 的复杂性,都是通过内核的/sys/devices/system/cpu/cpu0/tsc_freq_khz实现的,本文不使用这个。如果你对此感兴趣,在“参考资料/扩展阅读”部分我会列出一些使用这种方法等博客和帖子。

(这里我删了一大段,因为我需要确定一件事去问了几位业内人士,如果得到确定会在这里进行补充)

首先在官方文档 Intel® 64 and IA-32 Architectures Software Developer Manuals(2021-04)中可以看到CPUID.15H的返回信息部分有以下内容:

不同版本的 Intel® 64 and IA-32 Architectures Software Developer Manuals 表格数可能会不一样。

请添加图片描述

CPUID 在 EAX、EBX、ECX 和 EDX 寄存器中返回处理器识别和功能信息。指令的输出内容在执行时取决于 EAX 寄存器的值(在某些情况下还会附带上 ECX 的值)。

我们代码中使用的EAX值为0x25,而ECX0x0。你也可以使用mov指令设置寄存器的值,也可以像上面一样通过输入来设置几个寄存器的值。

从上图中可以看到EAXTSC/core crystal clock的分母,而EBXTSC/core crystal clock的分子,二者组成一个比例,这个比例有什么用呢?

这个值乘以core crystal clock就是 TSC 频率了,但是该如何获得core crystal clock呢?

如何获取核心晶体时钟频率(core crystal clock)

在 18.7.3 中有以下内容:
请添加图片描述

这个图包含了一些信息:

  • 名义核心晶体时钟频率在ecx中列出。核心晶体时钟运行在固定频率上,与整个系统的总时钟相协调。现在的绝大部分处理器(大约是从 Skylake 开始)或者使用缩放频率的处理器都不会在ecx列出核心晶体时钟频率了。如果ecx中没有说明核心晶体时钟频率,那么需要按照表 18-85 中的值计算 TSC 频率。
  • CPUID.15H 的返回值是核心晶体时钟比例的编码。
  • 中间的这个公式计算的是名义上 TSC 频率。

简而言之就是,对于CPUID.15H.EBX[31:0] ÷ CPUID.0x15.EAX[31:0]值存在,但是CPUID.15H.ECX没有值的情况,CPUID.15H.ECX的值应该如下:请添加图片描述

如何使用晶体时钟来计算TSC频率

那么该如何使用上面获取的那些值呢?

我用 Intel i5 8500B 测的CPUID.15H返回各寄存器的值如下:

eax=2
ebx=250
ecx=0
edx=0

按照这个计算来说,ebx/eax=125ecx=0,也就是需要从表格中获取值,这里使用24 Mhz(我怀疑是文档没更新,应该包含 8 代等的处理器的,因为其他两行都有说明 CPUID 签名,中间没说明应该就是其余所有这种处理器)。

这时候 TSC 频率 = 24 Mhz * ebx/eax=24 Mhz * 125 = 3000 Mhz,刚好就是 i5 8500B 的基础频率,这时候计算得到的时间精度挺高的。

如下是两次测试测到的结果,time是使用这种方法计算出的时间,下面的duration是使用clock_gettime()计算出的时间:

请添加图片描述

请添加图片描述

多次测试发现误差最多不到 500 ns(也不保证一定就是 500 ns 以内,因机器和环境而异)

要判断 TSC 频率是多少,从上面的信息知道需要考虑 2 点:

  1. ECX寄存器是否已经有频率信息了,如果有的话,直接使用;
  2. ECX寄存器没有频率信息的情况下,需要判断 CPUID 签名,从而得知频率是哪个。

第一个很好解决,一个判断语句。第二个 CPUID 签名是什么呢?

什么是 CPUID 签名

CPUID 签名就是当EAX=1时,cpuidEAX中的返回值。内容如下:

请添加图片描述

实际上并不会用到整个EAX的内容,只要取得处理器的Model部分即可。

为什么只要取得处理器的Model部分

只要取得处理器的Model部分即可有两个原因。

第一,因为前面那个表格中,两个列出的签名都是06H开头的,这是 Family ID,这部分包含以下系列的处理器:

请添加图片描述

可以看到现在使用的整个 Intel 酷睿(Core)系列和绝大部分志强(Xeon)系列都是6H开头的。这时候你会说,上面不是06H嘛?还有一位十六进制呢?这就是Extended Model ID的作用,和在一起就表示06H。可以说在过去三十年里,绝大部分 Intel 处理器的 Family ID都是6H(或者06H),只有早期处理器、Itanium 系列和极个别例外,具体情况自己搜一下“Processor Family IDs”即可。

下表是用到Extended Family ID部分的处理器:

请添加图片描述

比如 Zen 3 和 Zen 4 的 Family ID就是19H,那么就是Extended Family ID [1] + Family ID [9]

第二,其他处理器都在ECX中有核心晶体频率,只有这部分列出的没有,而这些都是06H为 Family ID 的处理器,所以只要考虑 Model 部分即可。但是处理器的 Model 依旧有扩展部分,所以需要获取两个部分再组合。

获取处理器 Model 部分的方法

获取处理器 Model 部分的方法为:

static inline unsigned int cpu_model(void)
{
    uint32_t a=0x1, b, c, d;
    asm volatile ("cpuid"
         : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
         : "a" (a), "b" (b), "c" (c), "d" (d)
         );
    uint32_t model = (a>>4)&0b1111;
    uint32_t extend_model = (a>>16)&0b1111;
    
    return (extend_model<<4) | model;
}

这里提示一下,不要使用a&0b11110000,因为这样获取的会带上最后四位,而我们并不需要这 4 位。

我的处理器输出结果为:

9e

接下来我们只需要判断是否可以是表格中的那两个就行。这里就不列出判断的代码了,我将其结合到完整代码中。

注意事项

在某些文章中提到 TSC 无法测量多核并行任务的时间,我猜测这是某些系统内核实现的问题或者早期一些处理器的特性。所以要根据自己的处理器来判断是否可以使用 TSC 实现计时。

因为我依旧使用 ISPC 编写多核并行任务,得到的时间和clock_gettime()非常相近。如下:

请添加图片描述

所以在现如今的处理器上,应该是可以测量并行程序的。

至于有些人提到执行时,可能会将rdtsc挤滞后的,在现在的文档中,提到这么一段话:

请添加图片描述

不过也这段话提到:RDTSC 并非串行指令,不一定要等到所有前置指令执行完才能读取计数器。RDTSC 的后续指令也可能会在 RDTSC 读取完就开始执行。关于执行顺序分了三种情况,所以具体情况还是要看软件程序的要求。

完整代码

使用 C++ 的标准库iostream,这样可以使用uint32_tbool等类型的数据,稍微方便一些。如果你需要使用纯 C,那么自己替换成合适的格式即可,以及引入<timer.h>

我将 C 和 C++ 两个版本的整理到TSC_Timer - Github ZhongUncle了,你可以克隆下来直接试试看。

下面是完整的代码:

#include <iostream>

#define BIT(nr) (1UL << (nr)) //获取第nr位的内容,右端为最小端

static inline unsigned long long rdtsc(void)
{
    unsigned long long low, high;
    asm volatile ("rdtsc" : "=a" (low), "=d" (high));
    return low | (high << 32);
}

static inline bool isTSC(void)
{
    uint32_t a=0x1, b, c, d;
    asm volatile ("cpuid"
         : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
         : "a" (a), "b" (b), "c" (c), "d" (d)
         );
    if ((d & BIT(4))) {
        // TSC exist!
        a=0x80000007;
        asm volatile ("cpuid"
             : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
             : "a" (a), "b" (b), "c" (c), "d" (d)
             );
        if ((d & BIT(8))) {
            // Invariant TSC available!
            return true;
        }
    } else {
        // TSC not exist
        return false;
    }
    return false;
}

static inline unsigned int cpu_model(void)
{
    uint32_t a=0x1, b, c, d;
    asm volatile ("cpuid"
         : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
         : "a" (a), "b" (b), "c" (c), "d" (d)
         );
    uint32_t model = (a>>4)&0b1111;
    uint32_t extend_model = (a>>16)&0b1111;
    
    return (extend_model<<4)|model;
}

static inline unsigned long tsc_freq(void)
{
    uint32_t model = cpu_model();
    
    uint32_t a=0x15, b, c, d;
    asm volatile ("cpuid"
         : "=a" (a), "=b" (b), "=c" (c), "=d" (d)
         : "0" (a), "1" (b), "2" (c), "3" (d)
         );
    
    if (c != 0) {
        return b / a * c;
    }
    
    if (model == 0x55) {
        return b / a * 25000000;
    }
    
    if (model == 0x5c) {
        return b / a * 19200000;
    }
    
    return b/a*24000000;
}

int main(int argc, const char * argv[]) {
    //判断是否有可靠的TSC
    if (!isTSC()) {
        printf("TSC is not exist or variant!");
    }
    
    //获取TSC频率
    unsigned long freq = tsc_freq();
    
    // 使用clock_gettime辅助验证
    struct timespec start;
        clock_gettime(CLOCK_MONOTONIC, &start);
    //获取TSC计数作为开始
    uint64_t rdtsc1 = rdtsc();
    
    //用于计时的测试代码
    for (int i=0; i<100; i++) {
        std::cout << "Hello, World!\n";
    }
    
    //获取TSC计数作为结束
    uint64_t rdtsc2 = rdtsc();
    struct timespec end;
        clock_gettime(CLOCK_MONOTONIC, &end);
    
    //获取TSC的时间
    double time = (double)(rdtsc2-rdtsc1)/(double)freq*1e9;
    //打印时钟、频率和TSC时间
    printf("clock\t = %llu cycles\n", rdtsc2-rdtsc1);
    printf("freq\t = %lu Hz\n", freq);
    printf("TSC time = %.0f ns\n", time);
    
    //计算clock_gettime辅助验证的时间
    double duration = (double)(end.tv_nsec-start.tv_nsec) + (double)(end.tv_sec-start.tv_sec)*((double) 1e9);
    //打印clock_gettime辅助验证的时间
    printf("duration = %.0f ns\n", duration);
    
    return 0;
}

希望能帮到有需要的人~

扩展阅读

How to get the CPU cycle count in x86_64 from C++? - Stack Overflow:这篇帖子的最高赞回答解释了 Linux 和 Windows 使用 C++ 内建的__rdtsc获取 x86_64 计数的方法。

__rdtsc - Microsoft Learn:__rdtsc的微软官方文档。

TSC Frequency For All: Better Profiling and Benchmarking - Trail of Bits:这篇博客就是使用/sys/devices/system/cpu/cpu0/tsc_freq_khz来获取 TSC 频率的。

Getting TSC rate from x86 kernel - Stack Overflow:这个帖子的这个回答,介绍了很多“高层级”获取 TSC 频率的方法。

Using and Preserving Registers in Inline Assembly - Microsoft Learn:微软官方介绍 Windows 使用内联汇编的方法。

How to do cpuid and rdtsc on 32 bit and 64 bit Windows - By Stephen Kellett:这篇博客介绍了如何在 Windows 的内联汇编中使用cpuidrdtsc的方法。

参考资料

研究的时候被很多资料误导了,所以列出使用到的和一些我觉得还不错的资料。

Intel® 64 and IA-32 Architectures Software Developer Manuals:Intel 处理器的官方文档,介绍了很多指令和架构信息。唯一缺陷就是太太太长了,所以如果你想自己看看,那么建议下一本看看大概,看的差不多了之后,后续只用看更新部分,这样大大降低时间。如果更新一本换一本,那么看不完的。

6.48.2 Extended Asm - Assembler Instructions with C Expression Operands - GNU GCC: GNU GCC 内联汇编的官方文档。

Inline Assembly/Examples - OS Dev:这篇 wiki 提供了许多内联汇编的例子。

建议下面 4 篇结合着看!

CPUID — CPU Identification - felixcloutier:介绍和解释了 CPUID 的指令使用方法和返回值含义等内容。这个可以当做 Intel SDM 的在线版,他把指令和内容解释做成了网页的。

CPUID - OS Dev:这篇 wiki 非常详细的介绍了如何通过汇编和内联汇编来使用 CPUID 的案例,但是对内容没有进行什么解释。

CPUID - Wikipedia:这里比较方便查看 CPUID 家族等一些寄存器的信息,但不是全部。

Query CPUID with Inline Assembly - Jack Henschel’s Blog:这篇博客是作者写学士论文时,研究内联汇编请求 CPUID 内容的总结。个人感觉例子比上面的 OS Dev 的例子好一些(个人喜好)。

RDTSC — Read Time-Stamp Counter - felixcloutier:介绍和解释了 CPUID 的指令使用方法和返回值含义等内容。

Function asm volatile(“rdtsc”); - Stack Overflow:这篇帖子讨论了如何使用内联汇编使用rdtsc命令获取 TSC 计数。

TSC frequency computation - Intel Community:这个帖子讨论了早期 CPU 计算 TSC 频率出现的精度问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1616283.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

arm架构,django4.2.7适配达梦8数据库

【Python相关包版本信息】 Django 4.2.7 django-dmPython 3.1.7 dmPython 2.5.5 【达梦数据库版本】 DM Database Server 64 V8 DB Version: 0x7000c 适配过程中发现的问题如下&#xff1a; 错误一&#xff1a;d…

Opencv | 图像卷积与形态学变换操作

这里写目录标题 一. 滤波 / 卷积操作1. 平滑均值滤波/卷积2. 平滑中值滤波/卷积3. 平滑高斯滤波/卷积3.1 关注区域3.2 分解特性 二. 形态学变换1. 常用核2. cv.erode ( ) 腐蚀操作3. cv.dilate ( ) 膨胀操作4. Open 操作5. Close 操作6. Morphological Gradient 形态梯度操作7.…

为什么单片机控制电机需要加电机驱动

通常很多地方只是单纯的单片机MCU没有对电机的驱动能力&#xff0c;或者是介绍关于电机驱动的作用&#xff0c;如&#xff1a; 提高电机的效率和精度。驱动器采用先进的电子技术和控制算法&#xff0c;能够精准控制电机的参数和运行状态&#xff0c;提高了电机的效率和精度。拓…

vos3000外呼系统客户端无法安装如何解决?

如果 VOS3000 外呼系统客户端无法安装&#xff0c;可以尝试以下解决方法&#xff1a; 检查系统要求&#xff1a; 确保你的计算机满足 VOS3000 外呼系统客户端的系统要求&#xff0c;包括操作系统版本、内存、处理器等。如果系统不符合要求&#xff0c;可能会导致安装失败或者运…

[BT]BUUCTF刷题第20天(4.22)

第20天 Web [GWCTF 2019]我有一个数据库 打开网站发现乱码信息&#xff08;查看其他题解发现显示的是&#xff1a;我有一个数据库&#xff0c;但里面什么也没有~ 不信你找&#xff09; 但也不是明显信息&#xff0c;通过dirsearch扫描得到robots.txt&#xff0c;然后在里面得…

echarts 双堆叠柱状图(数据整理)

1.后台返回的数据格式 {"code": "0000","message": "","messageCode": "操作成功","sign": null,"detail": null,"data": {"pieChart": [{"key": "产品…

C++之写时复制(CopyOnWrite)

设计模式专栏&#xff1a;http://t.csdnimg.cn/4j9Cq 目录 1.简介 2.实现原理 3.QString的实现分析 3.1.内部结构 3.2.写入时复制 4.示例分析 5.使用场景 6.总结 1.简介 CopyOnWrite (COW) 是一种编程思想&#xff0c;用于优化内存使用和提高性能。COW 的基本思想是&am…

编译支持播放H265的cef控件

接着在上次编译的基础上增加h265支持编译支持视频播放的cef控件&#xff08;h264&#xff09; 测试页面&#xff0c;直接使用cef_enhancement,里边带着的那个html即可&#xff0c;h265视频去这个网站下载elecard,我修改的这个版本参考了里边的修改方式&#xff0c;不过我的这个…

Reactor 模式

目录 1. 实现代码 2. Reactor 模式 3. 分析服务器的实现具体细节 3.1. Connection 结构 3.2. 服务器的成员属性 3.2. 服务器的构造 3.3. 事件轮询 3.4. 事件派发 3.5. 连接事件 3.6. 读事件 3.7. 写事件 3.8. 异常事件 4. 服务器上层的处理 5. Reactor 总结 1…

开源啦!一键部署免费使用!Kubernetes上直接运行大数据平台!

市场上首个K8s上的大数据平台&#xff0c;开源啦&#xff01; 智领云自主研发的首个 完全基于Kubernetes的容器化大数据平台 Kubernetes Data Platform (简称KDP) 开源啦&#x1f680;&#x1f680; 开发者只要准备好命令行工具&#xff0c;一键部署 Hadoop&#xff0c;Hi…

JavaScript(二)

JavaScript的语法 1.JavaScript的大小写 在JavaScript中&#xff0c;大小写是敏感的&#xff0c;这意味着大小写不同的标识符被视为不同的变量或函数。例如&#xff0c;myVariable 和 myvariable 被视为两个不同的变量。因此&#xff0c;在编写JavaScript代码时&#xff0c;必…

函数声明与调用:接口原型、参数传递顺序、返回值

示例&#xff1a; /*** brief how about function-declare-call? show you here.* author wenxuanpei* email 15873152445163.com(query for any question here)*/ #define _CRT_SECURE_NO_WARNINGS//support c-library in Microsoft-Visual-Studio #include <stdio.h&…

上位机图像处理和嵌入式模块部署(树莓派4b实现多进程通信)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 和mcu固件、上位机软件不太一样的地方&#xff0c;嵌入式设备上面上面的工业软件一般都是多进程的形式。相比较多线程而言&#xff0c;整个系统就不…

springcloudgateway集成knife4j

上篇我们聊聊springboot是怎么继承knife4j的。springboot3 集成knife4j-CSDN博客 本次我们一起学习springcloudgateway集成knife4j。 环境介绍 java&#xff1a;17 SpringBoot&#xff1a;3.2.0 SpringCloud&#xff1a;2023.0.0 knife4j &#xff1a; 4.4.0 引入maven配置…

# 从浅入深 学习 SpringCloud 微服务架构(四)Ribbon

从浅入深 学习 SpringCloud 微服务架构&#xff08;四&#xff09;Ribbon 段子手168 一、ribbon 概述以及基于 ribbon 的远程调用。 1、ribbon 概述&#xff1a; Ribbon 是 Netflixfa 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。 在 SpringCloud 中 Eureka …

就业班 第三阶段(负载均衡) 2401--4.19 day3 nginx3

二、企业 keepalived 高可用项目实战 1、Keepalived VRRP 介绍 keepalived是什么keepalived是集群管理中保证集群高可用的一个服务软件&#xff0c;用来防止单点故障。 ​ keepalived工作原理keepalived是以VRRP协议为实现基础的&#xff0c;VRRP全称Virtual Router Redundan…

用python selenium实现短视频一键推送

https://github.com/coolEphemeroptera/VIVI 效果如下 demo 支持youtube视频搬运

iPerf 3 测试UDP和TCP方法详解

文章目录 前言一、What is iPerf / iPerf3 ?二、功能1. TCP and SCTP2. UDP3. 其他 三、 Iperf的使用1.Iperf的工作模式2. 通用指令3. 服务端特有选项4. 客户端特有选项5. -t -n参数联系 四、Iperf使用实例1. 调整 TCP 连接1. 1TCP 窗口大小调节1. 2 最大传输单元 (MTU)调整 2…

【python项目推荐】键盘监控--统计打字频率

原文&#xff1a;https://greptime.com/blogs/2024-03-19-keyboard-monitoring 代码&#xff1a;https://github.com/GreptimeTeam/demo-scene/tree/main/keyboard-monitor 项目简介 该项目实现了打字频率统计及可视化功能。 主要使用的库 pynput&#xff1a;允许您控制和监…

kafka 命令行使用 消息的写入和读取 quickstart

文章目录 Intro命令日志zookeeper serverkafka servercreate topic && describe topic Intro Kafka在大型系统中可用作消息通道&#xff0c;一般是用程序语言作为客户端去调用kafka服务。 不过在这之前&#xff0c;可以先用下载kafka之后就包含的脚本文件等&#xff0…