【问题标题】:Why the first time memory copy runs is slow?为什么第一次内存拷贝运行很慢?
【发布时间】:2017-08-09 07:07:14
【问题描述】:

我发现了什么:

我打印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. 运行所有三个案例:

TestCopyLoop1x32M : 0.023s

TestCopyLoopOnex32M : 0.0038s

TestCopyDiffLoop1x32M : 0.0038s

  1. 运行第一种和第二种情况:

TestCopyLoop1x32M : 0.023s

TestCopyLoopOnex32M : 0.0038s

  1. 运行第一种和第三种情况:

TestCopyLoop1x32M : 0.023s

TestCopyLoop1x32M : 0.023s

我的问题:

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

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

  3. 1234563 /li>
  4. 为什么“copyLoopDiffDst”在两个case后会加速?

我的猜测:

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

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

【问题讨论】:

  • 我对go一无所知,但如果你在函数内部动态创建和释放/释放,很可能当第二个函数想要写入数据时,它们已经在缓存。如果您想测试是否是这种情况,请尝试为这两个功能全局创建 2 个不同的输入/输出。这样第二个函数就没有上述优势了,你应该会看到减速
  • 谢谢!在我将数据移出测试功能后,正如你所说,它变慢了。但是在我的代码中,“TestCopyLoop1x32M”和“TestCopyLoopOnex32M”做同样的事情,为什么只有第二个会有缓存?他们都在新的内存地址上创建了新数据,不是吗?
  • @IsuruH 谢谢你的想法。我想我已经找到了答案,但缓存的力量确实让我印象深刻。只有少数数据可能会发生写入命中,但它可以极大地提高性能

标签: performance caching go io cpu-cache


【解决方案1】:

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

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

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

我打印了 in&out []byte 的地址(在 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

你会发现程序重用了一些内存地址,所以写命中发生在下一个复制动作中。

如果我创建 in/out 函数,重用将不会发生,并且速度会变慢。

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-02
    • 2021-11-13
    • 1970-01-01
    • 2020-03-20
    • 1970-01-01
    • 2016-05-12
    • 1970-01-01
    • 2011-11-19
    相关资源
    最近更新 更多