Fix/Do not use Prm pointers in Storage Engine#1418
Fix/Do not use Prm pointers in Storage Engine#1418fyrchik merged 6 commits intonspcc-dev:masterfrom carpawell:fix/do-not-use-PRM-RES-pointers-in-SE
Prm pointers in Storage Engine#1418Conversation
Codecov Report
@@ Coverage Diff @@
## master #1418 +/- ##
==========================================
- Coverage 36.37% 36.36% -0.02%
==========================================
Files 305 305
Lines 17800 17782 -18
==========================================
- Hits 6475 6466 -9
+ Misses 10783 10774 -9
Partials 542 542
Continue to review full report at Codecov.
|
| // | ||
| // Should not be called in read-only configuration. | ||
| func (b *Blobovnicza) Delete(prm *DeletePrm) (*DeleteRes, error) { | ||
| func (b *Blobovnicza) Delete(prm DeletePrm) (DeleteRes, error) { |
There was a problem hiding this comment.
Why did you decide to return a value instead of a pointer?
There was a problem hiding this comment.
what do you mean? what that PR is doing is removing pointers in the whole SE (since it was suggested in #1398 and I really do not know why we need pointers in that cases). so you asking why does that PR exist in general?
There was a problem hiding this comment.
I understand PR existence, and accepting parameters by value is really good. But what about return values? The common approach we use is returning (*T, error). What are pros to return (T, error)? I mean why don't we return just T which contains error? Maybe I've missed some discussion.
There was a problem hiding this comment.
The common approach we use is returning (*T, error).
i saw some examples in the SE where it already was as func(prm Prm) (Res, error). why is it common? to be able to return nil res?
I mean why don't we return just T which contains error
to support common practice in go: start handling results only if err == nil (and know that operation could be finished with error and simplify working with Res struct)
Maybe I've missed some discussion.
no, that was my decision
There was a problem hiding this comment.
What are pros to return
(T, error)?
If we return a pointer, the struct will definitely be allocated on heap (all these functions are big enough not to be inlined). And in case of T there is no allocation. It is more beneficial than passing parameters by value, actually, because parameters can still be allocated on stack as they do not outlive the called function (in most of our cases).
I mean why don't we return just T which contains error
This is possible, but would make the code non-idiomatic for no real reason IMO. We should still check for the error.
There was a problem hiding this comment.
Until the building itself, we do not know exactly how the data will be allocated since this is not visible from the source code. Are there any objective reasons for the change? If it's really a bottleneck, then we need to provide test proof.
We've already discussed this earlier, and previously I voted against return (*T, error) for the same GC optimization and suggested to accept result by pointer as a parameter. This will allow to reuse the same instance between several calls, and most definitely avoid heap allocation. But we've decided with @carpawell @alexvanin to not do it like this. At the same time I don't see returning (T, error) convenient.
I suggest to see what the profiler shows.
There was a problem hiding this comment.
- It is visible from the source if we return value and the function is big enough not to be inlined. The compiler has no option here. Consider this code:
package main
type X struct {
a int
}
func main() {
println(getX().a)
}
//go:noinline
func getX() *X {
return &X{a: 1}
}
go build -gcflags -m ./kek.go
# command-line-arguments
./kek.go:14:12: &X{...} escapes to heap
- Certainly, this isn't a bottleneck, because we allocate much more inside functions. But the whole point of the epic is to do small improvements here and there to reduce GC pressure.
- The benchmarks are needed here. Raw parameters can have no effect, but I expect replacing return value to have some benefits in terms of memory. @carpawell Could you write a benchmark for the (simplest) case of
metabase.Get?
There was a problem hiding this comment.
- There is no "heap" in terms of Go source code. In your example you make a decision according to the specific compiler. What does it mean "big enough not to be inlined"? How can I get it from the sources?
And again... I am aware and agree that returning a pointer leads rather to worse performance. Therefore, I propose a compromise: allocate a structure to record the results on the calling side and pass a pointer to it in the function.
package main
type X struct {
a int
}
func main() {
v := new(X)
initX(v)
println(v.a)
}
//go:noinline
func initX(v *X) {
v.a = 1
}
$ go build -gcflags -m ./main.go
# command-line-arguments
./main.go:7:6: can inline main
./main.go:15:12: v does not escape
./main.go:8:10: new(X) does not escape
There was a problem hiding this comment.
i reorganized that PR: it now only contains *Prm -> Prm changes
Res changes will be added via another PR
| // | ||
| // Should not be called in read-only configuration. | ||
| func (b *Blobovnicza) Delete(prm *DeletePrm) (*DeleteRes, error) { | ||
| func (b *Blobovnicza) Delete(prm DeletePrm) (DeleteRes, error) { |
There was a problem hiding this comment.
- It is visible from the source if we return value and the function is big enough not to be inlined. The compiler has no option here. Consider this code:
package main
type X struct {
a int
}
func main() {
println(getX().a)
}
//go:noinline
func getX() *X {
return &X{a: 1}
}
go build -gcflags -m ./kek.go
# command-line-arguments
./kek.go:14:12: &X{...} escapes to heap
- Certainly, this isn't a bottleneck, because we allocate much more inside functions. But the whole point of the epic is to do small improvements here and there to reduce GC pressure.
- The benchmarks are needed here. Raw parameters can have no effect, but I expect replacing return value to have some benefits in terms of memory. @carpawell Could you write a benchmark for the (simplest) case of
metabase.Get?
|
Must be synchronized (rebased) with #1462 and other SE changes. |
| storagelog.OpField("metabase DELETE")) | ||
| } | ||
| } | ||
| return new(DeleteRes), err |
There was a problem hiding this comment.
fixed and checked other places one more time
btw, it is one more argument for changing *Res -> Res: we do not have a uniformity in that cases, and it is not clear what to expect nil or allocated new(Res) as an empty result
|
|
||
| shPrm := new(shard.InhumePrm) | ||
| existsPrm := new(shard.ExistsPrm) | ||
| var shPrm shard.InhumePrm |
There was a problem hiding this comment.
it can be beneficial to allocate declare them inside the callback now.
There was a problem hiding this comment.
Because after removal of return value from With* functions, it can be allocated on stack.
There was a problem hiding this comment.
And it will remain correct if we decide to parallelize iteration over shards.
There was a problem hiding this comment.
Because after removal of return value from With* functions, it can be allocated on stack.
Isn't it still true for a one var declaration before the callback call?
And it will remain correct if we decide to parallelize iteration over shards.
agree here, moved inside the callback
| // | ||
| // Instance payload contains the requested range of the original object. | ||
| func (r *RngRes) Object() *object.Object { | ||
| func (r RngRes) Object() *object.Object { |
| } | ||
|
|
||
| return new(InhumeRes), nil | ||
| return nil, nil |
| } | ||
|
|
||
| var res ContainerSizeRes | ||
| res := new(ContainerSizeRes) |
|
|
||
| // Object returns the requested object. | ||
| func (r *GetRes) Object() *objectSDK.Object { | ||
| func (r GetRes) Object() *objectSDK.Object { |
| blz, err := b.openBlobovnicza(prm.blobovniczaID.String()) | ||
| if err != nil { | ||
| return nil, err | ||
| return res, err |
|
|
||
| if err == nil { | ||
| res = new(DeleteSmallRes) | ||
| objectFound = true |
|
|
||
| activeCache[dirPath] = struct{}{} | ||
|
|
||
| objectFound = err == nil |
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
|
@cthulhu-rider, @fyrchik, lets merge that, it becomes really difficult to support (rebase) it and #1460. It does not contain smth that we have not discussed yet. |
cthulhu-rider
left a comment
There was a problem hiding this comment.
Do we want to remove nil-conditions in setters here or later?
Do you mean removing |
Prm, Res pointers in Storage EnginePrm pointers in Storage Engine
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Related to #1398.