Bytes.equal,代码中为什么会出现这样的时间差异

从输出来看c和c之间的比较相对于b和b来说要慢,下面是我看汇编的一些注释,先用大循环比较后用小循环比较。看不出来造成这种差异的原因,我运行了很多次,感觉差异还是很明显的。我唯一能设想的是CPU对纯的内存比较要比有数据的要慢一些?

package main

import (
    "bytes"
    "fmt"
    "time"
)

func main() {
    b := make([]byte, 100000)
    for i := 0; i < 100000; i++ {
        b[i] = byte(i)
    }
    c := make([]byte, 100000)
    for i := 0; i < 100; i++ {
        c[i] = byte(i)
    }
    a := make([]byte, 100000)
    for i := 0; i < 100; i++ {
        a[i] = byte(i)
    }
    var n, d time.Time
    n = time.Now()
    bytes.Equal(b, c)
    d = time.Now()
    fmt.Println("dur ", d.Sub(n).Nanoseconds())
    n = time.Now()
    bytes.Equal(c, c)
    d = time.Now()
    fmt.Println("dur ", d.Sub(n).Nanoseconds())
    n = time.Now()
    bytes.Equal(b, b)
    d = time.Now()
    fmt.Println("dur ", d.Sub(n).Nanoseconds())
}




//NOSPLIT表示的是一个选项位,具体的作用暂时没搞清楚,没选项就用NOSPLIT
//后面的$0-49的格式表示的是页的大小和参数的大小,页大小暂时不知道干什么的
//因为传入的两个参数是切片,在amd64上是24个字节,一个字节表示地址,一个字节表示长度,一个字节表示容量
//还有返回的一个bool值用一个字节表示,所以是49个字节
//SB表示的内存地址空间的首地址的伪寄存器
TEXT bytes·Equal(SB),NOSPLIT,$0-49
    //a_len+8(FP)就是表示FP的地8个字节,也就是长度的内容,+8表示便宜8个单位,a_len只是一个表意符号
    //FP是伪寄存器页指针,应该是参数空间的首地址
    MOVQ    a_len+8(FP), BX
    MOVQ    b_len+32(FP), CX
    //AX自己和自己异或能够变成全0x00,也就是false
    XORQ    AX, AX 
    //比较BX和CX就是BX-CX将结果放到一个寄存器里
    CMPQ    BX, CX
    //如果这个寄存器不为0就跳转,这里就首先比较长度
    JNE eqret
    //如果长度相等就比较内存的内容,把两个切片的首地址交给runtime.memeqbody(SB),通过SI和DI传进去
    MOVQ    a+0(FP), SI
    MOVQ    b+24(FP), DI
    //把SB推入堆栈,赚取执行runtime·memeqbody
    CALL    runtime·memeqbody(SB)
    //把AX赋值给返回值,最后返回函数,函数都被称做proccedure
eqret:
    MOVB    AX, ret+48(FP)
    RET
// a in SI
// b in DI
// SI 是源变址寄存器 DI是目的变址寄存器,就是用来存放操作数的偏移地址的
// count in BX

TEXT runtime·memeqbody(SB),NOSPLIT,$0-0
    //把AX设置成0x00
    XORQ    AX, AX
    //比较BX
    CMPQ    BX, $8
    //JS == JNAE:jump not above and equal 就是小于的意思
    //如果长度小于一个字节
    //跳转到small
    //JUMP IF BELOW
    JB  small
    // 64 bytes at a time using xmm registers
    // 每次用xmm寄存器比较64个字节,每个xmm是128bit的
hugeloop:
    CMPQ    BX, $64
    JB  bigloop
    //OU不晓得什么意思,O应该是overflow,U应该是unaligned.
    //每两个寄存器之间要比较128位,总共8个XMM寄存器
    MOVOU   (SI), X0
    MOVOU   (DI), X1
    MOVOU   16(SI), X2
    MOVOU   16(DI), X3
    MOVOU   32(SI), X4
    MOVOU   32(DI), X5
    MOVOU   48(SI), X6
    MOVOU   48(DI), X7
    //比较xmm寄存器的字节是否相同,相同则目标寄存器全为1
    PCMPEQB X1, X0
    PCMPEQB X3, X2
    PCMPEQB X5, X4
    PCMPEQB X7, X6
    //位与简化逻辑,最后用X0判断结果是否相等
    PAND    X2, X0
    PAND    X6, X4
    PAND    X4, X0
    //将高8位字节掩码移动通用寄存器
    PMOVMSKB X0, DX
    //移到下一个64位
    ADDQ    $64, SI
    ADDQ    $64, DI
    SUBQ    $64, BX
    CMPL    DX, $0xffff
    JEQ hugeloop
    RET
    // 8 bytes at a time using 64-bit register
bigloop:
    CMPQ    BX, $8
    JBE leftover
    MOVQ    (SI), CX
    MOVQ    (DI), DX
    ADDQ    $8, SI
    ADDQ    $8, DI
    SUBQ    $8, BX
    CMPQ    CX, DX
    JEQ bigloop
    RET

    // remaining 0-8 bytes
    //8个字节之内的比较   
leftover:
    //猜是相对于SI的(-8+BX)的位置
    MOVQ    -8(SI)(BX*1), CX
    MOVQ    -8(DI)(BX*1), DX
    CMPQ    CX, DX
    SETEQ   AX
    RET
small:
    //如果BX长度是0就跳转到equal
    CMPQ    BX, $0
    JEQ equal
    LEAQ    0(BX*8), CX
    //求补
    NEGQ    CX
    CMPB    SI, $0xf8
    //JUMP IF ABOVE 
    JA  si_high
    // load at SI won't cross a page boundary.
    MOVQ    (SI), SI
    JMP si_finish
si_high:
    // address ends in 11111xxx.  Load up to bytes we want, move to correct position.
    MOVQ    -8(SI)(BX*1), SI
    SHRQ    CX, SI
si_finish:
    // same for DI.
    CMPB    DI, $0xf8
    JA  di_high
    MOVQ    (DI), DI
    JMP di_finish
di_high:
    MOVQ    -8(DI)(BX*1), DI
    SHRQ    CX, DI
di_finish:
    SUBQ    SI, DI
    SHLQ    CX, DI
equal:
    SETEQ   AX
    RET

共 1 个回复


ggaaooppeenngg

TEXT bytes·Equal(SB),NOSPLIT,$0-49
    MOVQ    a_len+8(FP), BX
    MOVQ    b_len+32(FP), CX
    XORQ    AX, AX
    CMPQ    BX, CX
    JNE eqret
    MOVQ    a+0(FP), SI
    MOVQ    b+24(FP), DI
    CALL    runtime·memeqbody(SB)
eqret:
    MOVB    AX, ret+48(FP)
    RET

// a in SI
// b in DI
// count in BX
TEXT runtime·memeqbody(SB),NOSPLIT,$0-0
    XORQ    AX, AX

    CMPQ    BX, $8
    JB  small

    // 64 bytes at a time using xmm registers
hugeloop:
    CMPQ    BX, $64
    JB  bigloop
    MOVOU   (SI), X0
    MOVOU   (DI), X1
    MOVOU   16(SI), X2
    MOVOU   16(DI), X3
    MOVOU   32(SI), X4
    MOVOU   32(DI), X5
    MOVOU   48(SI), X6
    MOVOU   48(DI), X7
    PCMPEQB X1, X0
    PCMPEQB X3, X2
    PCMPEQB X5, X4
    PCMPEQB X7, X6
    PAND    X2, X0
    PAND    X6, X4
    PAND    X4, X0
    PMOVMSKB X0, DX
    ADDQ    $64, SI
    ADDQ    $64, DI
    SUBQ    $64, BX
    CMPL    DX, $0xffff
    JEQ hugeloop
    RET

    // 8 bytes at a time using 64-bit register
bigloop:
    CMPQ    BX, $8
    JBE leftover
    MOVQ    (SI), CX
    MOVQ    (DI), DX
    ADDQ    $8, SI
    ADDQ    $8, DI
    SUBQ    $8, BX
    CMPQ    CX, DX
    JEQ bigloop
    RET

    // remaining 0-8 bytes
leftover:
    MOVQ    -8(SI)(BX*1), CX
    MOVQ    -8(DI)(BX*1), DX
    CMPQ    CX, DX
    SETEQ   AX
    RET

small:
    CMPQ    BX, $0
    JEQ equal

    LEAQ    0(BX*8), CX
    NEGQ    CX

    CMPB    SI, $0xf8
    JA  si_high

    // load at SI won't cross a page boundary.
    MOVQ    (SI), SI
    JMP si_finish
si_high:
    // address ends in 11111xxx.  Load up to bytes we want, move to correct position.
    MOVQ    -8(SI)(BX*1), SI
    SHRQ    CX, SI
si_finish:

    // same for DI.
    CMPB    DI, $0xf8
    JA  di_high
    MOVQ    (DI), DI
    JMP di_finish
di_high:
    MOVQ    -8(DI)(BX*1), DI
    SHRQ    CX, DI
di_finish:

    SUBQ    SI, DI
    SHLQ    CX, DI
equal:
    SETEQ   AX
    RET

汇编的相关代码,可惜不会汇编,有时间研究一下,到底怎么算相等的.

# 0