Skip to content

Fix/Do not use Prm pointers in Storage Engine#1418

Merged
fyrchik merged 6 commits intonspcc-dev:masterfrom
carpawell:fix/do-not-use-PRM-RES-pointers-in-SE
Jun 3, 2022
Merged

Fix/Do not use Prm pointers in Storage Engine#1418
fyrchik merged 6 commits intonspcc-dev:masterfrom
carpawell:fix/do-not-use-PRM-RES-pointers-in-SE

Conversation

@carpawell
Copy link
Member

@carpawell carpawell commented May 20, 2022

Related to #1398.

@carpawell carpawell added the enhancement Improving existing functionality label May 20, 2022
@carpawell carpawell self-assigned this May 20, 2022
@codecov
Copy link

codecov bot commented May 20, 2022

Codecov Report

Merging #1418 (1597ed2) into master (da3ae20) will decrease coverage by 0.01%.
The diff coverage is 69.58%.

❗ Current head 1597ed2 differs from pull request most recent head 535597e. Consider uploading reports for the commit 535597e to get more accurate results

@@            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              
Impacted Files Coverage Δ
pkg/local_object_storage/blobstor/delete_big.go 0.00% <0.00%> (ø)
pkg/local_object_storage/blobstor/delete_small.go 0.00% <0.00%> (ø)
pkg/local_object_storage/blobstor/get_range_big.go 0.00% <0.00%> (ø)
...g/local_object_storage/blobstor/get_range_small.go 0.00% <0.00%> (ø)
pkg/local_object_storage/engine/delete.go 0.00% <0.00%> (ø)
pkg/local_object_storage/engine/dump.go 0.00% <0.00%> (ø)
pkg/local_object_storage/engine/restore.go 0.00% <0.00%> (ø)
pkg/local_object_storage/shard/container.go 0.00% <0.00%> (ø)
pkg/local_object_storage/shard/exists.go 0.00% <0.00%> (ø)
pkg/local_object_storage/shard/list.go 15.78% <0.00%> (-0.58%) ⬇️
... and 43 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update da3ae20...535597e. Read the comment docs.

@carpawell carpawell marked this pull request as ready for review May 23, 2022 15:41
//
// Should not be called in read-only configuration.
func (b *Blobovnicza) Delete(prm *DeletePrm) (*DeleteRes, error) {
func (b *Blobovnicza) Delete(prm DeletePrm) (DeleteRes, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you decide to return a value instead of a pointer?

Copy link
Member Author

@carpawell carpawell May 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member Author

@carpawell carpawell May 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Copy link
Contributor

@fyrchik fyrchik May 24, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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
  1. 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.
  2. 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?

Copy link
Contributor

@cthulhu-rider cthulhu-rider May 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 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
  1. 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.
  2. 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?

@carpawell
Copy link
Member Author

Must be synchronized (rebased) with #1462 and other SE changes.

storagelog.OpField("metabase DELETE"))
}
}
return new(DeleteRes), err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this commit

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can be beneficial to allocate declare them inside the callback now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what for?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because after removal of return value from With* functions, it can be allocated on stack.

Copy link
Contributor

@fyrchik fyrchik Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And it will remain correct if we decide to parallelize iteration over shards.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated to this commit.

}

return new(InhumeRes), nil
return nil, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this commit

}

var res ContainerSizeRes
res := new(ContainerSizeRes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this commit


// Object returns the requested object.
func (r *GetRes) Object() *objectSDK.Object {
func (r GetRes) Object() *objectSDK.Object {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this commit

blz, err := b.openBlobovnicza(prm.blobovniczaID.String())
if err != nil {
return nil, err
return res, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this commit


if err == nil {
res = new(DeleteSmallRes)
objectFound = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated to this commit


activeCache[dirPath] = struct{}{}

objectFound = err == nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

carpawell added 6 commits June 2, 2022 19:08
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>
@carpawell
Copy link
Member Author

@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.

Copy link
Contributor

@cthulhu-rider cthulhu-rider left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to remove nil-conditions in setters here or later?

@carpawell
Copy link
Member Author

carpawell commented Jun 2, 2022

Do we want to remove nil-conditions in setters here or later?

Do you mean removing if p != nil in *Prm? Dont we need it? If not, I would remove them in that PR.

@fyrchik fyrchik merged commit 021aa97 into nspcc-dev:master Jun 3, 2022
@carpawell carpawell deleted the fix/do-not-use-PRM-RES-pointers-in-SE branch June 3, 2022 07:02
@fyrchik fyrchik mentioned this pull request Jun 3, 2022
11 tasks
@carpawell carpawell changed the title Fix/Do not use Prm, Res pointers in Storage Engine Fix/Do not use Prm pointers in Storage Engine Jun 6, 2022
carpawell added a commit that referenced this pull request Jun 6, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit that referenced this pull request Jun 6, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit that referenced this pull request Jun 6, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
carpawell added a commit that referenced this pull request Jun 6, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
aprasolova pushed a commit to aprasolova/neofs-node that referenced this pull request Oct 19, 2022
Signed-off-by: Pavel Karpy <carpawell@nspcc.ru>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement Improving existing functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants