memcmp的性能分析

最近在优化软件运行速度,翻查C++代码的时候,发现了这么一处函数。功能很简单,就是比较内存值是否相同。

函数如下:

int cfunc_memcmp(char* src, char* dest, int n)
{
    while (n--)
        if (*src++ != *dest++)
            return 1;

    return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

看起来,这是一个很简单的函数,程序逻辑也很简短,似乎并没有什么可以优化的地方。

然而,对于这样一个简单的功能,在C Runtime库里,却有一个函数可以直接调用,效果完全相同。
代码如下:

#include <memory.h>
int cruntime_memcmp(void *src, void *dest, int n)
{
    return memcmp(src, dest, n);
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果将函数cfunc_memcmp替换成cruntime_memcmp,速度会变化多少呢?
我们写一个简单的计时程序测试一下。
代码如下:

#include <stdio.h>
#include <memory.h>
#include <time.h>

int cruntime_memcmp(void *src, void *dest, int n)
{
    return memcmp(src, dest, n);
}

int cfunc_memcmp(char* src, char* dest, int n)
{
    while (n--)
        if (*src++ != *dest++)
            return 1;

    return 0;
}

int main()
{
    #define SIZE_BUF 1000000000
    char *buf1 = new char[SIZE_BUF];
    char *buf2 = new char[SIZE_BUF];
    memset(buf1, 123, SIZE_BUF);
    memset(buf2, 123, SIZE_BUF);
    buf2[SIZE_BUF - 1] = (char)321;

    clock_t start, end;
    int nRet;

    start = clock();
    nRet = cfunc_memcmp(buf1, buf2, SIZE_BUF);
    end = clock();
    printf("The time clock was : %d \n", (end - start));

    start = clock();
    nRet = cruntime_memcmp(buf1, buf2, SIZE_BUF);
    end = clock();
    printf("The time clock was :%d \n", (end - start));

    delete buf1;
    delete buf2;

    return 0;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

输出结果为:

The time clock was : 435
The time clock was : 136

可以看到,两者之间速度相差有3倍之多。如果在大循环的内部,调用了这个函数,那么程序总体耗时就有明显的差异了。


为了充分研究memcmp为什么这么高效的原因,我们尝试直接编写汇编代码,去比较一下速度。

第一个版本,使用repz cmpsb指令,可以按字节比较内存
代码如下:

.code

asm_memcmp  proc src:QWORD, dest:QWORD, n:DWORD
    push rsi
    push rdi

    mov rsi, rcx
    mov rdi, rdx
    mov rcx, r8

    repz cmpsb
    jnz no_equal
equal:
    mov rax, 0
    jmp done
no_equal:
    mov rax, 1

done:
    pop rdi
    pop rsi
    ret
asm_memcmp  endp

END
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

然后在C++代码修改如下:

extern "C" {
    int asm_memcmp(void *src, void *dest, int n);
}
int main()
{
//...
    start = clock();
    nRet = asm_memcmp(buf1, buf2, SIZE_BUF);
    end = clock();

    printf("The time clock was : %d \n", (end - start));
//...
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

输出结果如下:

The time clock was : 568

这个汇编级别的repz cmpsb指令,居然比C++里的字节比较函数cfunc_memcmp还要慢。让人不可思议。

接下来,我们不使用repz cmpsb指令,直接按地址循环取值,每次以8 bytes为单位比较。
代码如下:

.code

asm_memcmp  proc src:QWORD, dest:QWORD, n:DWORD
    push rsi
    push rdi

    mov rsi, rcx
    mov rdi, rdx
    mov rcx, r8

start:
    test rcx, rcx
    jz equal
    mov rax, [rsi]
    mov rdx, [rdi]
    cmp rax, rdx
    jnz no_equal
    add rsi, 8
    add rdi, 8
    sub rcx, 8
    jmp start
equal:
    mov rax, 0
    jmp done
no_equal:
    mov rax, 1

done:
    pop rdi
    pop rsi
    ret
asm_memcmp  endp

END
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

输出结果如下:

The time clock was : 141

这个结果也出乎意料。这种直接编写的汇编代码,竟然和C Runtime库的效率差不多,甚至还要慢一些。


至此,我们对库函数memcmp到底是如何处理的,产生了浓厚的兴趣。

于是,我们通过调试器进入了memcmp的反汇编代码,跟踪进去查看。
代码如下:

00007FFEEA64C286  nop         word ptr [rax+rax]  
00007FFEEA64C290  sub         rdx,rcx  
00007FFEEA64C293  cmp         r8,8  
00007FFEEA64C297  jb          00007FFEEA64C2BB  
00007FFEEA64C299  test        cl,7  
00007FFEEA64C29C  je          00007FFEEA64C2B2  
00007FFEEA64C29E  xchg        ax,ax  
00007FFEEA64C2A0  mov         al,byte ptr [rcx]  
00007FFEEA64C2A2  cmp         al,byte ptr [rdx+rcx]  
00007FFEEA64C2A5  jne         00007FFEEA64C2D3  
00007FFEEA64C2A7  inc         rcx  
00007FFEEA64C2AA  dec         r8  
00007FFEEA64C2AD  test        cl,7  
00007FFEEA64C2B0  jne         00007FFEEA64C2A0  
00007FFEEA64C2B2  mov         r9,r8  
00007FFEEA64C2B5  shr         r9,3  
00007FFEEA64C2B9  jne         00007FFEEA64C2DA  
00007FFEEA64C2BB  test        r8,r8  
00007FFEEA64C2BE  je          00007FFEEA64C2CF  
00007FFEEA64C2C0  mov         al,byte ptr [rcx]  
00007FFEEA64C2C2  cmp         al,byte ptr [rdx+rcx]  
00007FFEEA64C2C5  jne         00007FFEEA64C2D3  
00007FFEEA64C2C7  inc         rcx  
00007FFEEA64C2CA  dec         r8  
00007FFEEA64C2CD  jne         00007FFEEA64C2C0  
00007FFEEA64C2CF  xor         rax,rax  
00007FFEEA64C2D2  ret  
00007FFEEA64C2D3  sbb         eax,eax  
00007FFEEA64C2D5  sbb         eax,0FFFFFFFFh  
00007FFEEA64C2D8  ret  
00007FFEEA64C2D9  nop  
00007FFEEA64C2DA  shr         r9,2  
00007FFEEA64C2DE  je          00007FFEEA64C317  
00007FFEEA64C2E0  mov         rax,qword ptr [rcx]  
00007FFEEA64C2E3  cmp         rax,qword ptr [rdx+rcx]  
00007FFEEA64C2E7  jne         00007FFEEA64C344  
00007FFEEA64C2E9  mov         rax,qword ptr [rcx+8]  
00007FFEEA64C2ED  cmp         rax,qword ptr [rdx+rcx+8]  
00007FFEEA64C2F2  jne         00007FFEEA64C340  
00007FFEEA64C2F4  mov         rax,qword ptr [rcx+10h]  
00007FFEEA64C2F8  cmp         rax,qword ptr [rdx+rcx+10h]  
00007FFEEA64C2FD  jne         00007FFEEA64C33C  
00007FFEEA64C2FF  mov         rax,qword ptr [rcx+18h]  
00007FFEEA64C303  cmp         rax,qword ptr [rdx+rcx+18h]  
00007FFEEA64C308  jne         00007FFEEA64C338  
00007FFEEA64C30A  add         rcx,20h  
00007FFEEA64C30E  dec         r9  
00007FFEEA64C311  jne         00007FFEEA64C2E0  
00007FFEEA64C313  and         r8,1Fh  
00007FFEEA64C317  mov         r9,r8  
00007FFEEA64C31A  shr         r9,3  
00007FFEEA64C31E  je          00007FFEEA64C2BB  
00007FFEEA64C320  mov         rax,qword ptr [rcx]  
00007FFEEA64C323  cmp         rax,qword ptr [rdx+rcx]  
00007FFEEA64C327  jne         00007FFEEA64C344  
00007FFEEA64C329  add         rcx,8  
00007FFEEA64C32D  dec         r9  
00007FFEEA64C330  jne         00007FFEEA64C320  
00007FFEEA64C332  and         r8,7  
00007FFEEA64C336  jmp         00007FFEEA64C2BB  
00007FFEEA64C338  add         rcx,8  
00007FFEEA64C33C  add         rcx,8  
00007FFEEA64C340  add         rcx,8  
00007FFEEA64C344  mov         rcx,qword ptr [rcx+rdx]  
00007FFEEA64C348  bswap       rax  
00007FFEEA64C34B  bswap       rcx  
00007FFEEA64C34E  cmp         rax,rcx  
00007FFEEA64C351  sbb         eax,eax  
00007FFEEA64C353  sbb         eax,0FFFFFFFFh  
00007FFEEA64C356  ret 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

可以看到,汇编代码做了8字节对齐,并利用了冗余指令减少寄存器运算,把一个10行左右的指令,写成了70行。
我们简要分析一下这些代码的逻辑。

首先,把地址做8字节对齐,通过test cl,7指令判断。不对齐的部分,单字节(byte)比较。

00007FFEEA64C299  test        cl,7  
00007FFEEA64C29C  je          00007FFEEA64C2B2  
00007FFEEA64C29E  xchg        ax,ax  
00007FFEEA64C2A0  mov         al,byte ptr [rcx]  
00007FFEEA64C2A2  cmp         al,byte ptr [rdx+rcx]  
00007FFEEA64C2A5  jne         00007FFEEA64C2D3  
00007FFEEA64C2A7  inc         rcx  
00007FFEEA64C2AA  dec         r8  
00007FFEEA64C2AD  test        cl,7  
00007FFEEA64C2B0  jne         00007FFEEA64C2A0  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

然后,以32个字节为单位,通过冗余指令,做4次8字节(qword)比较

00007FFEEA64C2DA  shr         r9,2  
00007FFEEA64C2DE  je          00007FFEEA64C317  
00007FFEEA64C2E0  mov         rax,qword ptr [rcx]  
00007FFEEA64C2E3  cmp         rax,qword ptr [rdx+rcx]  
00007FFEEA64C2E7  jne         00007FFEEA64C344  
00007FFEEA64C2E9  mov         rax,qword ptr [rcx+8]  
00007FFEEA64C2ED  cmp         rax,qword ptr [rdx+rcx+8]  
00007FFEEA64C2F2  jne         00007FFEEA64C340  
00007FFEEA64C2F4  mov         rax,qword ptr [rcx+10h]  
00007FFEEA64C2F8  cmp         rax,qword ptr [rdx+rcx+10h]  
00007FFEEA64C2FD  jne         00007FFEEA64C33C  
00007FFEEA64C2FF  mov         rax,qword ptr [rcx+18h]  
00007FFEEA64C303  cmp         rax,qword ptr [rdx+rcx+18h]  
00007FFEEA64C308  jne         00007FFEEA64C338  
00007FFEEA64C30A  add         rcx,20h  
00007FFEEA64C30E  dec         r9  
00007FFEEA64C311  jne         00007FFEEA64C2E0  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

再然后,不足32个字节的部分,以8个字节为单位,做8字节(qword)比较

00007FFEEA64C313  and         r8,1Fh  
00007FFEEA64C317  mov         r9,r8  
00007FFEEA64C31A  shr         r9,3  
00007FFEEA64C31E  je          00007FFEEA64C2BB  
00007FFEEA64C320  mov         rax,qword ptr [rcx]  
00007FFEEA64C323  cmp         rax,qword ptr [rdx+rcx]  
00007FFEEA64C327  jne         00007FFEEA64C344  
00007FFEEA64C329  add         rcx,8  
00007FFEEA64C32D  dec         r9  
00007FFEEA64C330  jne         00007FFEEA64C320  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

最后,剩余不足8个字节的,做单字节(byte)比较

00007FFEEA64C2BB  test        r8,r8  
00007FFEEA64C2BE  je          00007FFEEA64C2CF  
00007FFEEA64C2C0  mov         al,byte ptr [rcx]  
00007FFEEA64C2C2  cmp         al,byte ptr [rdx+rcx]  
00007FFEEA64C2C5  jne         00007FFEEA64C2D3  
00007FFEEA64C2C7  inc         rcx  
00007FFEEA64C2CA  dec         r8  
00007FFEEA64C2CD  jne         00007FFEEA64C2C0  
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

应该说,这一系列的指令序列非常繁琐,但是却是非常符合x64指令系统内部结构的。所以,在效率上,确实更胜一筹。


最后,我们把各种代码方案,做一个速度的对比,按照耗时排列。

名次方案耗时
1cruntime_memcmp136
2asm_memcmp141
3cfunc_memcmp435
4asm_memcmp (repz cmpsb)568

总结,现代软件工程已经不需要我们详细深入汇编层编写程序了。而且,即便我们自己编写汇编代码,往往效率还远不如编译器优化后的指令序列。
所以,我们可以尽量使用现有C Runtime库,或者STL库。既可以简化我们的开发过程,还可以加快程序的运行效率。这是一个又快又好的结果。

<think>好的,用户想了解STM32中memcmp函数的用法或相关问题。首先,我需要回忆一下memcmp的基本用法。这个函数用于比较两个内存块的内容,返回值为0表示相等,正数或负数表示差异。在STM32的嵌入式开发中,memcmp常用于数据校验,比如比较接收到的数据是否正确。 接下来,结合用户提供的引用内容,特别是引用[1]提到的数据类型大小,这里需要注意不同数据类型在STM32中的占用空间。比如unsigned char是1字节,int是4字节,这可能会影响memcmp的比较结果,特别是当结构体或联合体存在对齐问题时。例如,结构体可能有填充字节,导致memcmp比较时出现意外结果。 然后,用户提供的引用[3]提到USART发送数据时出现乱码,可能与内存操作有关。如果使用memcmp比较接收到的数据,但数据中存在乱码,可能导致校验失败。需要检查数据传输的稳定性,是否有缓冲区溢出或数据未正确接收的情况。 另外,引用[2]提到DMA与USART的数据传输优化,使用DMA时,内存操作更高效,但也要注意内存对齐和访问权限。例如,DMA传输的数据是否在内存中正确对齐,否则memcmp比较时可能出现问题。 用户的问题可能涉及如何正确使用memcmp,或者遇到比较结果不符的情况。需要提醒用户注意内存对齐、数据类型大小以及结构体填充的问题。此外,在比较字符串或数据时,确保数据正确接收和存储,避免因传输错误导致比较失败。 可能的解决方案包括:检查数据长度是否正确,使用显式比较代替memcmp处理结构体,确保内存对齐,以及验证数据来源是否正确。同时,可以建议在调试时使用十六进制打印数据,以便观察实际内存内容。 总结用户需求,他们可能需要了解如何在STM32中正确使用memcmp,以及解决因内存布局或数据传输问题导致的比较错误。需要结合具体场景,如USART通信中的数据校验,提供针对性的建议。</think>### STM32中`memcmp`函数用法及常见问题解决 `memcmp`是C标准库中的内存比较函数,用于比较两个内存块的前$n$个字节的内容是否一致。在STM32开发中,常用于数据校验、通信协议验证等场景。以下是详细说明及常见问题解决方案: --- #### 1. **基本用法** 函数原型: ```c int memcmp(const void *s1, const void *s2, size_t n); ``` - **参数**: - `s1`:第一个内存块指针 - `s2`:第二个内存块指针 - `n`:需要比较的字节数 - **返回值**: - 0:两内存块前$n$字节完全相同 - 正数:`s1`第一个差异字节值大于`s2` - 负数:`s1`第一个差异字节值小于`s2` **示例**: ```c uint8_t buffer1[] = {0x01, 0x02, 0x03}; uint8_t buffer2[] = {0x01, 0x02, 0x04}; int result = memcmp(buffer1, buffer2, 3); // 返回负数,因0x03 < 0x04 ``` --- #### 2. **常见问题及解决** ##### **(1) 结构体对齐导致的误判** **问题**:STM32中结构体默认对齐(如4字节对齐),可能产生填充字节,导致`memcmp`误判。 **示例**: ```c typedef struct { uint8_t a; // 占1字节,后可能有3字节填充 uint32_t b; // 占4字节 } MyStruct; ``` **解决**: - 显式比较每个字段,或使用`#pragma pack(1)`取消对齐(可能影响性能)[^1]。 - 手动初始化填充字节: ```c memset(&struct1, 0, sizeof(MyStruct)); // 填充字节清零 ``` ##### **(2) 浮点数比较问题** **问题**:浮点数(`float`/`double`)在内存中的表示可能因精度误差导致`memcmp`失败。 **示例**: ```c float a = 1.0f, b = 1.0f; if (memcmp(&a, &b, sizeof(float)) != 0) { // 可能因舍入误差进入此分支 } ``` **解决**: - 使用误差范围比较(如`fabs(a - b) < 1e-6`)代替内存比较。 ##### **(3) 通信数据校验失败** **问题**:USART/DMA传输中数据出现乱码(如引用[3]所述),导致`memcmp`校验失败[^3]。 **解决**: - 检查传输配置(波特率、时钟源)是否匹配。 - 添加数据校验机制(如CRC)辅助验证。 - 使用调试工具(如逻辑分析仪)捕获实际传输的数据。 ##### **(4) 数据类型大小混淆** **问题**:STM32中`int`占4字节,`short`占2字节(引用[1]),若比较时长度参数错误,会导致结果异常[^1]。 **示例**: ```c uint16_t arr1[2] = {0x1234, 0x5678}; uint16_t arr2[2] = {0x1234, 0x5678}; memcmp(arr1, arr2, 2); // 错误!实际应比较4字节(2个元素×2字节) ``` **解决**: - 使用`sizeof`计算总字节数: ```c memcmp(arr1, arr2, sizeof(arr1)); ``` --- #### 3. **优化建议** - **DMA结合`memcmp`**:在USART接收中使用DMA传输(引用[2]),减少CPU占用,再通过`memcmp`批量比较数据[^2]。 - **内存安全性**:确保比较的内存区域已初始化,避免野指针或越界访问。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值