最快的字节级比较方法memcmp.c反汇编分析
来源:互联网 发布:端口波特率修改工具 编辑:程序博客网 时间:2024/06/05 18:05
在stackoverflow上有关于在C#中最快的位判断的方法的讨论。
https://stackoverflow.com/questions/43289/comparing-two-byte-arrays-in-net
据讨论的网友统计,最快的方法是通过[Dllimport]第三方调用的memcmp.c。
源码:
/* * memcmp.c -- * * Source code for the "memcmp" library routine. * * Copyright (c) 1998 Sun Microsystems, Inc. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * SCCS: @(#) memcmp.c 1.2 98/01/19 10:48:58 */#include "tcl.h"#include "tclPort.h"/* * Here is the prototype just in case it is not included * in tclPort.h. */int memcmp _ANSI_ARGS_((CONST VOID *s1, CONST VOID *s2, size_t n));/* *---------------------------------------------------------------------- * * memcmp -- * * Compares two bytes sequences. * * Results: * compares its arguments, looking at the first n * bytes (each interpreted as an unsigned char), and returns * an integer less than, equal to, or greater than 0, accord- * ing as s1 is less than, equal to, or * greater than s2 when taken to be unsigned 8 bit numbers. * * Side effects: * None. * *---------------------------------------------------------------------- */intmemcmp(s1, s2, n)CONST VOID *s1; /* First string. */CONST VOID *s2; /* Second string. */size_t n; /* Length to compare. */{ unsigned char u1, u2; for ( ; n-- ; s1++, s2++) { u1 = * (unsigned char *) s1; u2 = * (unsigned char *) s2; if ( u1 != u2) { return (u1-u2); } } return 0;}
看到源码后最好奇的还是return (u1-u2)这里,如果说此方法的目的是进行最快的字节级相等比较,那么return 1应该是更好的选择,为什么要返回一个没有太大意义的u1-u2值。出于好奇,将memcmp.c进行了编译,并进行了反汇编分析。
首先看下Xcode Assembler对memcmp直接翻译成未优化的汇编代码版本:
...Ltmp20: movzbl -33(%rbp), %edx//取一个指针 .loc 1 86 18 is_stmt 0 ## /Users/.../Desktop/test3/test3/main.c:86:18 movzbl -34(%rbp), %esi//再取一个指针 .loc 1 86 16 ## /Users/.../Desktop/test3/test3/main.c:86:16 cmpl %esi, %edx//比较:u1!=u2Ltmp21: .loc 1 86 14 ## /Users/.../Desktop/test3/test3/main.c:86:14 je LBB1_4//如果 u1等于u2, 跳到LBB1-4,重新开始循环## BB#3: .loc 1 87 21 is_stmt 1 ## /Users/.../Desktop/test3/test3/main.c:87:21Ltmp22: movzbl -33(%rbp), %eax//否则进行u1-u2计算。先取u1。 .loc 1 87 24 is_stmt 0 ## /Users/.../Desktop/test3/test3/main.c:87:24 movzbl -34(%rbp), %ecx//再取u2。 .loc 1 87 23 ## /Users/.../Desktop/test3/test3/main.c:87:23 subl %ecx, %eax//u1-u2。subl S,D等于源码里的 D-S,顺序是反的,计算结果会存到D处。 .loc 1 87 13 ## /Users/.../Desktop/test3/test3/main.c:87:13 movl %eax, -4(%rbp)//准备将u1-u2的值出栈 jmp LBB1_7Ltmp23:LBB1_4: ## in Loop: Header=BB1_1 Depth=1 .loc 1 89 5 is_stmt 1 ## /Users/.../Desktop/test3/test3/main.c:89:5 jmp LBB1_5...
在这个版本里可以看到u1!=u2在汇编层面是使用cmpl %esi, %edx。cmp的实现是基于减法。cmp(比较)与sub(减法)指令在指令的执行阶段都会进入CPU ALU逻辑单元进行u1-u2的计算,然后根据结果更改条件码,唯一不同的是在指令的写回阶段,sub指令将会把计算结果存入到寄存器,而cmp指令不保存计算结果(https://docs.oracle.com/cd/E19455-01/806-3773/instructionset-23/index.html)。这也就导致了在源码层return (u1-u2)处,汇编层还要进行一次减法运算(subl %ecx, %eax)。
再看下在OSX终端用gcc编译器进行优化编译后的反汇编代码版本:
00000000000000b0 pushq %rbp00000000000000b1 movq %rsp, %rbp00000000000000b4 jmp 0xc900000000000000b6 nopw %cs:_main(%rax,%rax)00000000000000c0 decq %rdx00000000000000c3 incq %rdi00000000000000c6 incq %rsi00000000000000c9 testq %rdx, %rdx//检查n!=000000000000000cc je 0xda//如果n==0,跳到0xda处,直接返回000000000000000ce movzbl _main(%rdi), %eax//取u100000000000000d1 movzbl _main(%rsi), %ecx//取u200000000000000d4 subl %ecx, %eax//u1-u2,结果存入%eax00000000000000d6 je 0xc0//如果u1-u2=0,也既是u1==u2,跳到0xc0再次开始循环00000000000000d8 jmp 0xdc//否则,既是u1!=u2,跳到0xdc,将%eax出栈00000000000000da xorl %eax, %eax//循环结束,返回000000000000000dc popq %rbp00000000000000dd retq
此版本有两处巧妙的优化,第一是将源码层if ( u1!=u2)与return (u1-u2)在汇编层合并成了一次subl %ecx, %eax。上面提过sub指令在指令的执行阶段会根据ALU的计算结果更改条件码,所以subl %ecx, %eax在指令的执行阶段既完成了u1!=u2的判断,并在指令的写回阶段完成了u1-u2。第二处优化,是由于sub指令在写回阶段会将计算结果存入D(subl S,D),同时一个方法的返回值需要存在%eax寄存器内,所以subl %ecx, %eax相当于把u1-u2的结果放到了一个随时可以出栈(返回)的状态。因此,如果是return 1,还需要增加一条将一个立即数传送到eax%的汇编指令,return (u1-u2)比return 1还要快。
将return (u1-u2)改为return 1 后的版本:
00000000000000b0 pushq %rbp00000000000000b1 movq %rsp, %rbp00000000000000b4 jmp 0xc900000000000000b6 nopw %cs:_main(%rax,%rax)00000000000000c0 decq %rdx00000000000000c3 incq %rdi00000000000000c6 incq %rsi00000000000000c9 testq %rdx, %rdx00000000000000cc je 0xe100000000000000ce movl $0x1, %eax//此处多了一个立即数传送,并且还是在循环之内00000000000000d3 movzbl _main(%rsi), %r8d00000000000000d7 movzbl _main(%rdi), %ecx00000000000000da cmpl %r8d, %ecx//此处由sub变为了cmp比较00000000000000dd je 0xc000000000000000df jmp 0xe300000000000000e1 xorl %eax, %eax00000000000000e3 popq %rbp00000000000000e4 retq
总结, if ( u1 != u2) return (u1-u2) 这种判断与返回形式通过利用了sub指令在指令的执行阶段进行的判断(修改条件码)和在写回阶段将计算结果直接存入栈返回默认寄存器(%eax)的特点,从而达到了在一个指令序列中就完成了判断与计算的高效优化。当然除了return处,其他地方,例如u1 = * (unsigned char *) s1;在汇编层变为movzbl _main(%rdi), %eax这种高效转化也值得研究。
————————————————————————————
参考:
https://stackoverflow.com/questions/43289/comparing-two-byte-arrays-in-net
https://docs.oracle.com/cd/E19455-01/806-3773/instructionset-23/index.html –Oracle
深入理解计算机系统 –R.E.Bryant,D.R.O’Hallaron
————————————————————————————
日志:
2017-7-4: 修改了标题
2017-8-22:将“..最快的方法是memcmp.c。”改为“..最快的方法是通过[Dllimport]第三方调用的memcmp.c。”
- 最快的字节级比较方法memcmp.c反汇编分析
- C/C++代码分析时的一些反汇编方法
- C语言memcmp()函数:比较内存前n个字节
- 反汇编一个简单的C程序,分析汇编代码
- C反汇编示例分析
- c程序的启动过程的反汇编分析
- C语言程序的反汇编分析2
- C语言流程控制语句的反汇编分析
- C调用简单函数的反汇编分析记录
- 反汇编一个简单的C程序并分析
- 1_简单的C程序反汇编及分析
- Linux汇编代码学习,反汇编简单的c及分析汇编代码工作过程
- 【面试题】C语言:模拟实现memcmp,试比较memcmp与strcmp,strncmp的区别
- C中数据的比较简介(strcmp、memcmp)
- C语言的反汇编
- C库字符串反汇编分析
- C程序反汇编代码分析
- 反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的
- java中Scanner类nextLine()和next()的区别和使用方法
- 开篇介绍和工程目录结构-(从零开始搭建android框架系列(1))
- eventbus打包时报错
- springboot+freemarker 增加自定义变量和自定义
- android studio 升级后,经常会对gradle升级,然后编译原来程序会出现gradle版本太老的问题
- 最快的字节级比较方法memcmp.c反汇编分析
- /usr/lib/x86_64-linux-gnu/libopencv_videostab.so.2.4.8
- Longest Palindromic Substring
- optee os 中的系统调用
- 实现简单UDP服务器客户端模型
- 谷歌浏览器web worker出现cannot be accessed from origin 'null'错误
- B.FRIENDit壁虎忍者RF1430K无线键盘鼠标套装 静音超薄键盘 台式电脑笔记本外接巧克力键盘鼠标套装银白色
- 不要滥用SharedPreference
- php curl 类