Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cmd/compile: optimize struct copy #12841

Closed
tdewolff opened this issue Oct 5, 2015 · 1 comment
Closed

cmd/compile: optimize struct copy #12841

tdewolff opened this issue Oct 5, 2015 · 1 comment

Comments

@tdewolff
Copy link

tdewolff commented Oct 5, 2015

Go1.5.1 windows/amd64

It seems that manually copying all the fields of a struct to a new location is faster than copying through assignment (which invokes memmove).

BenchmarkMoveSmallStruct-8      2000000000               0.62 ns/op
BenchmarkSetSmallStruct-8       2000000000               0.50 ns/op
BenchmarkMoveBigStruct-8        1000000000               2.68 ns/op
BenchmarkSetBigStruct-8         1000000000               2.20 ns/op

Using

package main

import "testing"

type Small struct {
    a int
}

type Big struct {
    a, b, c, d, e, f, g, h int
}

////////////////

func moveSmall(item *Small) {
    *item = Small{0}
}

func setSmall(item *Small) {
    item.a = 0
}

func moveBig(item *Big) {
    *item = Big{0, 1, 2, 3, 4, 5, 6, 7}
}

func setBig(item *Big) {
    item.a, item.b, item.c, item.d, item.e, item.f, item.g, item.h = 0, 1, 2, 3, 4, 5, 6, 7
}

////////////////

func BenchmarkMoveSmallStruct(b *testing.B) {
    item := Small{0}
    for i := 0; i < b.N; i++ {
        moveSmall(&item)
    }
}

func BenchmarkSetSmallStruct(b *testing.B) {
    item := Small{0}
    for i := 0; i < b.N; i++ {
        setSmall(&item)
    }
}

func BenchmarkMoveBigStruct(b *testing.B) {
    item := Big{0, 1, 2, 3, 4, 5, 6, 7}
    for i := 0; i < b.N; i++ {
        moveBig(&item)
    }
}

func BenchmarkSetBigStruct(b *testing.B) {
    item := Big{0, 1, 2, 3, 4, 5, 6, 7}
    for i := 0; i < b.N; i++ {
        setBig(&item)
    }
}

I think the assignment of the struct first allocates the struct on the stack and then memmoves it to the destination. The struct can be set on the destination immediately.

@bradfitz bradfitz added this to the Unplanned milestone Oct 5, 2015
@randall77
Copy link
Contributor

Just took a look at this on the SSA branch.
You're right, there are two copies in the bad case, global data -> stack and stack -> destination.

The copies themselves are ok, if you do *dst = *src vs. dst.a, ... = src.a, ... the copy (using duffcopy) is slightly faster. It's the x2 that is the problem.

There doesn't seem to be an easy fix for this. For instance, if you do:

func moveBigX(item *Big, x int) {
    *item = Big{0, 1, 2, x, 4, 5, 6, 7}
}

then the intermediate stack copy is necessary so you can modify the stack data before writing it. Direct assignments are still better in this case, but if the struct gets large enough the code size becomes prohibitive.

A rule like:
(Move dst inter (Move inter src mem)) -> (Move dst src mem)
might work. It's not quite that simple, as we have to make sure the intermediate memory isn't used by anyone else. And drop the inter variable if it is no longer used. And probably lots of other conditions I'm missing.

Long story short, thinking about it. Should be doable for the completely constant case.

@golang golang locked and limited conversation to collaborators Apr 18, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants