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

本文主要是一个实验和思维扩展,是一个初步的尝试,旨在研究一些时间测量实现和在 C/C++ 中内联汇编和汇编函数的使用方法。除非你有特殊用途,不然不要使用汇编指令来实现这个功能。在“扩展阅读”部分会列出了一些不需要内联汇编实现的方法。

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

平台和系统方面,本文只尝试了 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 这个技术在 2014 年前后(个人感觉)有了巨大变化,因为早期的博客、文章、代码等提及的一系列方法和问题与今天的情况几乎可以说是完全不同。并且 2014 年前后这个时间段也与 Intel 官方文档中一些处理器的更新和内容的时间段对应上了。

早期有很多文章写 TSC 的,包括在 CSDN 上。这是因为当时汇编还是一个比较流行的编程手段,有很多情况下会用到,很多人都会研究和写博客。而今天各种高级语言可以满足几乎所有开发场景,汇编已经慢慢淡出视野了,所以今天没有多少人会去研究汇编和基础设施的实现了,这样的文章也就少了很多。

内联汇编实现

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

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

第一步是要确定设备有 TSC,并且是不变的。

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

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

查看是否是稳定不变的 TSC 的方法是使用0x80000007初始化EAX,然后检查 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 频率出现的精度问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

猫头虎分享已解决Bug || TypeError: Cannot read property ‘map‘ of undefined**

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通鸿蒙》 …

4.25 C高级

思维导图 作业 2.输入两个数&#xff0c;实现两个数的排序 3.输入一个数&#xff0c;计算是否是水仙花 if ((g*g*gs*s*sb*b*bnum)) then echo YES else echo no fi 4.输入一个成绩实现登记判断 90-100A 80-89B 70-79C 60-69D 0-59E

“追忆似水年华 展望美好未来”——生命故事小组忆童趣活动

在人生的长河中&#xff0c;童年是明亮色彩的日子&#xff0c;但随着岁月的流逝&#xff0c;这些回忆有时会变得模糊&#xff0c;为唤起他们对美好童年的回忆&#xff0c;2024年4月9日上午9点&#xff0c;由成都市社会组织社区和社工人才服务中心支持&#xff0c;新都区民政局指…

OpenHarmony语言基础类库【@ohos.util.HashMap (非线性容器HashMap)】

HashMap底层使用数组链表红黑树的方式实现&#xff0c;查询、插入和删除的效率都很高。HashMap存储内容基于key-value的键值对映射&#xff0c;不能有重复的key&#xff0c;且一个key只能对应一个value。 HashMap和[TreeMap]相比&#xff0c;HashMap依据键的hashCode存取数据&…

文旅元宇宙解决方案|人工智能、虚拟数字人、导览系统深度应用

随着数字技术的飞速发展&#xff0c;文旅行业正迎来一场前所未有的变革。道可云文旅元宇宙平台以其前瞻性的技术视野和创新的解决方案&#xff0c;为各级文旅主管部门、旅游景区、博物馆、艺术展览馆等单位提供了全新的智慧景区导览、元宇宙场景搭建、AR场景开发以及数字人导游…

市场上免费且高效的云渲染平台,渲染100邀请码7788

在当今数字化时代&#xff0c;云渲染服务因其便捷性和高效性而日益受到追捧&#xff0c;广泛应用于建筑设计、影视制作和视觉艺术等多个领域。它不仅能够显著缩短项目完成的时间&#xff0c;还能大幅提升工作效率。 接下来&#xff0c;我们将探讨一些市场上公认的优质且免费的…

【Qt常用控件】—— QWidget 核心属性

目录 &#xff08;一&#xff09;控件概述 1.1 关于控件体系的发展 &#xff08;二&#xff09;QWidget 核心属性 2.1 核心属性概览 2.2 enabled 2.3 geometry 2.4 windowTitle 2.5 windowIcon 2.6 windowOpacity 2.7 cursor 2.8 font 2.9 toolTip 2.10 focus…

数据结构四:线性表之带头结点的单向循环循环链表的设计

前面两篇介绍了线性表的顺序和链式存储结构&#xff0c;其中链式存储结构为单向链表&#xff08;即一个方向的有限长度、不循环的链表&#xff09;&#xff0c;对于单链表&#xff0c;由于每个节点只存储了向后的结点的地址&#xff0c;到了尾巴结点就停止了向后链的操作。也就…

MySQL统计一个表的行数,使用count(1), count(字段), 还是count(*)?

为什么要使用count函数&#xff1f; 在开发系统的时候&#xff0c;我们经常要计算一个表的行数。比如我最近开发的牛客社区系统&#xff0c;有一个帖子表&#xff0c;其中一个功能就是要统计帖子的数量&#xff0c;便于分页显示计算总页数。 CREATE TABLE discuss_post (id i…

展览模型一般怎么打灯vray---模大狮模型网

在展览模型的设计中&#xff0c;灯光的运用是至关重要的&#xff0c;它不仅能够增强展品的视觉效果&#xff0c;还可以营造出独特的氛围和情感。在利用V-Ray进行灯光设置时&#xff0c;有一些常用的技巧和方法可以帮助设计师实现理想的展览效果。在本文中&#xff0c;我们将介绍…

漏洞修复优先级考虑-不错的思路

权威说法&#xff1a; 漏洞利用预测评分系统 &#xff08;EPSS&#xff09; 是一项数据驱动的工作&#xff0c;用于估计软件漏洞在野外被利用的可能性&#xff08;概率&#xff09; https://www.first.org/epss/ GitHub - TURROKS/CVE_Prioritizer: Streamline vulnerability…

在windows上安装MySQL数据库全过程

1.首先在MySQL的官网找到其安装包 在下图中点击MySQL Community(gpl) 找到MySQL Community Server 选择版本进行安装包的下载 2.安装包&#xff08;Windows (x86, 64-bit), MSI Installer&#xff09;安装步骤 继续点击下一步 继续进行下一步&#xff0c;直到出现此界面&#…

基于小程序实现的惠农小店系统设计与开发

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 技术选型 【后端】&#xff1a;Java 【框架】&#xff1a;spring…

leetcode-比较版本号-88

题目要求 思路 1.因为字符串比较大小不方便&#xff0c;并且因为需要去掉前导的0&#xff0c;这个0我们并不知道有几个&#xff0c;将字符串转换为数字刚好能避免。 2.当判断到符号位的时候加加&#xff0c;跳过符号位。 3.判断数字大小&#xff0c;来决定版本号大小 4.核心代…

探索直播+电商系统中台架构:连接消费者与商品的智能纽带

随着直播电商的崛起&#xff0c;电商行业进入了全新的智能时代。直播形式的互动性和即时性为消费者提供了全新的购物体验&#xff0c;而电商平台则为商品的展示、销售和配送提供了强大的支持。在这一背景下&#xff0c;直播电商系统中台架构成为了连接消费者与商品的智能纽带&a…

ABTest如何计算最小样本量-工具篇

如果是比例类指标&#xff0c;有一个可以快速计算最小样本量的工具&#xff1a; https://www.evanmiller.org/ab-testing/sample-size.html 计算样本量有4个要输入的参数&#xff1a;①一类错误概率&#xff0c;②二类错误概率 &#xff08;一般是取固定取值&#xff09;&…

【JavaScript】内置对象 ③ ( Math 内置对象 | Math 内置对象简介 | Math 内置对象的使用 )

文章目录 一、Math 内置对象1、Math 内置对象简介2、Math 内置对象的使用 二、代码示例1、代码示例 - Math 内置对象的使用2、代码示例 - 封装 Math 内置对象 一、Math 内置对象 1、Math 内置对象简介 JavaScript 中的 Math 内置对象 是一个 全局对象 , 该对象 提供了 常用的 数…

名家采访:国家级中国茶文化首席非遗传承人——罗大友

“崇高的理想是一个人心中的太阳,能照亮生活中的每一步。”罗大友&#xff0c;性别&#xff1a;男&#xff0c;国家级中国茶文化首席非遗传承人•中国茶文化研究院院长、美国巴拿马太平洋万国博览会终身评委兼中国区联合主席&#xff0c;大学文化&#xff0c;高级政工师。 “第…

【算法】删除有序数组中的重复项

本题来源---《删除有序数组中的重复项》 题目描述 给你一个 非严格递增排列 的数组 nums &#xff0c;请你删除重复出现的元素&#xff0c;使每个元素 只出现一次 &#xff0c;返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 示…

ZDOCK linux 下载(无需安装)、配置、使用

ZDOCK 下载 使用 1. 下载1&#xff09;教育邮箱提交申请&#xff0c;会收到下载密码2&#xff09;选择相应的版本3&#xff09;解压 2. 使用方法Step 1&#xff1a;将pdb文件处理为ZDOCK可接受格式Step 2&#xff1a;DockingStep 3&#xff1a;创建所有预测结构 1. 下载 1&…