为什么第一次内存复制运行缓慢?

时间:2017-03-17 16:13:31

标签: performance caching go io cpu-cache

我找到了什么:

我打印golang副本的时间成本,它显示内存复制的第一次很慢。但即使我在不​​同的内存地址上运行“复制”,第二次也要快得多。

这是我的测试代码:

func TestCopyLoop1x32M(t *testing.T) {
    copyLoopSameDst(32*1024*1024, 1)
}
func TestCopyLoopOnex32M(t *testing.T) {
    copyLoopSameDst(32*1024*1024, 1)
}
func copyLoopSameDst(size, loops int) {
    in := make([]byte, size)
    out := make([]byte, size)
    rand.Seed(0)
    fillRandom(in) // insert random byte into slice
    now := time.Now()
    for i := 0; i < loops; i++ {
        copy(out, in)
    }
    cost := time.Since(now)
    fmt.Println(cost.Seconds() / float64(loops))
   }

func TestCopyDiffLoop1x32M(t *testing.T) {
    copyLoopDiffDst(32*1024*1024, 1)
}

func copyLoopDiffDst(size, loops int) {
    ins := make([][]byte, loops)
    outs := make([][]byte, loops)
    for i := 0; i < loops; i++ {
        out := make([]byte, size)
        outs[i] = out
        in := make([]byte, size)
        rand.Seed(0)
        fillRandom(in)
        ins[i] = in
    }

    now := time.Now()
    for i := 0; i < loops; i++ {
        copy(outs[i], ins[i])
    }
    cost := time.Since(now)
    fmt.Println(cost.Seconds() / float64(loops))
}

结果(在i5-4278U上):

  1. 运行所有三种情况:
  2. TestCopyLoop1x32M:0.023s

    TestCopyLoopOnex32M:0.0038s

    TestCopyDiffLoop1x32M:0.0038s

    1. 运行第一个和第二个案例:
    2. TestCopyLoop1x32M:0.023s

      TestCopyLoopOnex32M:0.0038s

      1. 运行第一个和第三个案例:
      2. TestCopyLoop1x32M:0.023s

        TestCopyLoop1x32M:0.023s

        我的问题:

        1. 他们有不同的内存地址和不同的数据,下一个案例如何从第一个案例中受益?

        2. 为什么Result3与Result2不同?他们不是做同样的事吗?

        3. 如果我在“copyLoopSameDst”中添加循环,我知道下次因为缓存会更快,但是我的cpu的L3 Cache只有3MB,我无法解释这个巨大的改进

          < / LI>
        4. 为什么“copyLoopDiffDst”会在两个案例后加速?

        5. 我的猜测:

          1. 指令缓存有助于提高性能,但无法解释question2

          2. cpu缓存超出了我的想象,但无法解释问题2

1 个答案:

答案 0 :(得分:0)

经过更多的研究和测试,我想我可以回答部分问题。

缓存在下一个测试用例中工作的原因是Golang(也许其他语言会做同样的事情,因为malloc内存是一个系统调用)内存分配。

当数据很大时,内核将重用刚刚释放的块。

我打印输入和输出[]字节的地址(在Golang中,切片的前8个字节是它的内存地址,所以我写了一个程序集来获取地址):

地址:[0 192 8 32 196 0 0 0] [0 192 8 34 196 0 0 0]

费用:0.019228028

地址:[0 192 8 36 196 0 0 0] [0 192 8 32 196 0 0 0]

费用:0.003770281

地址:[0 192 8 34 196 0 0 0] [0 192 8 32 196 0 0 0]

费用:0.003806502

你会发现程序重用了一些内存地址,因此在下一次复制操作中会发生写入命中。

如果我创建输入/输出功能,重复使用将不会发生,并且速度会慢下来。

但是如果你将块设置得非常小(例如,在32KB以下)你会发现再次加速,尽管内核给你一个新的内存地址。在我看来,主要原因是数据没有对齐64字节,因此下一个循环数据(它的位置在第一个附近)将被捕获到缓存中,同时,第一个循环浪费了很多时间来填充缓存。并且下一个循环可以获取指令缓存和其他数据缓存以运行该函数。当数据很小时,这些小缓存会产生很大的影响。

我仍然感到惊讶,数据大小是我的cpu缓存大小的10倍,但缓存仍然可以帮助很多。无论如何,这是另一个问题。