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

Use interface rather than closure in *ctx to remove memory allocation #49277

Closed
Tracked by #47355
YangKeao opened this issue Dec 8, 2023 · 2 comments · Fixed by #49280
Closed
Tracked by #47355

Use interface rather than closure in *ctx to remove memory allocation #49277

YangKeao opened this issue Dec 8, 2023 · 2 comments · Fixed by #49280
Labels
type/enhancement The issue or PR belongs to an enhancement.

Comments

@YangKeao
Copy link
Member

YangKeao commented Dec 8, 2023

Enhancement

Using function pointer (actually a closure of struct method) will generate unexpected memory allocation, and harm the speed. Here is a little example to show the mechanism:

package main

type NoAlloc struct{ count int}

func (n *NoAlloc) fn() { n.count += 1 }

type iNoAlloc interface{ fn() }

func (n *NoAlloc) Fast() iNoAlloc {
    return n
}

func (n *NoAlloc) Slow() func() {
    return n.fn
}

func (n *NoAlloc) AlsoSlow() func() {
    return func() {
        n.fn()
    }
}

func main() {
	
}

You can explore these codes on https://godbolt.org/z/r19b1fz1c. You can find that the func (n *NoAlloc) Slow() will call runtime.newobject. Now .ErrCtx() uses the similar pattern, and will make the HandleError very slow. We can solve it by passing the interface.

@siddontang
Copy link
Member

except HandleError in your PR, which other places or allocation should we use this way instead? can you also list them so others can contribute?

btw, in this #49280, can you show a detailed profile or others to show that the unnecessary allocation is avoided or the gc has less work or the performance increased in some benchmarks?

ti-chi-bot bot pushed a commit that referenced this issue Dec 11, 2023
…nd reduce allocation in creation of statement context (#49280)

close #49277
@YangKeao
Copy link
Member Author

YangKeao commented Dec 11, 2023

except HandleError in your PR, which other places or allocation should we use this way instead? can you also list them so others can contribute?

Converting a struct method to raw function type, and returning it out will always make an avoidable heap allocation. It can be easily optimized by using an interface. However, I'm not sure whether this optimization is valuable for most scenarios.

For HandleError it's valuable because HandleError is used everywhere and is called really frequently. For example, in (*Index).GenIndexKey, it's called per rows. I'm reported that the HandleError can take nearly 20% or more in (*Index).GenIndexKey without optimization, which is really unexpected.

But for other scenarios, it's hard to say.

To avoid similar issues in the future, we can analyze the cases in which a struct method is assigned to a func type. Not all of them need to be modified (e.g. some are not escaped, some are not sensitive to a little allocation ...), so they need to be handled case by case.

btw, in this #49280, can you show a detailed profile or others to show that the unnecessary allocation is avoided or the gc has less work or the performance increased in some benchmarks?

Nice suggestion. I attach the profile flamegraph of BenchmarkErrCtx in #49280 (comment). The change of the profile is quite significant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type/enhancement The issue or PR belongs to an enhancement.
Projects
None yet
2 participants