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

src: track memory retainer fields #26161

Merged
merged 1 commit into from
Feb 28, 2019

Conversation

gireeshpunathil
Copy link
Member

If retainers are embedded in retainers, direct tracking
those lead to double tracking. Instead, use the base tracker
to track the field objects.

cc @addaleax [ I should admit that I don't have full understanding of this feature, so let me know if this needs rewrite ]

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@nodejs-github-bot nodejs-github-bot added the c++ Issues and PRs that require attention from people who are familiar with C++. label Feb 17, 2019
@@ -81,6 +81,10 @@ void MemoryTracker::TrackFieldWithSize(const char* edge_name,
if (size > 0) AddNode(GetNodeName(node_name, edge_name), size, edge_name);
}

void MemoryTracker::TrackInlineField(const MemoryRetainer& field) {
field.MemoryInfo(this);
Copy link
Member

Choose a reason for hiding this comment

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

I feel like if we want to provide this as an utility, it might make sense to perform something similar to Track() and/or re-use it? That would be nice if we do want to see these fields as full “objects” on their own, and want to show them that way in heap dumps.

Then again, I think you’re making a good point about this approach being a good way to address the problem itself; I just didn’t think of a direct approach like this. 😄 Adding 2 calls à la thread_stopper_.MemoryInfo(tracker); in #26099 would also be a good way to address this, as far as that PR is concerned in particular?

@gireeshpunathil
Copy link
Member Author

@addaleax - pushed a change; ptal. I tested the two inline fields in question using test/pummel/test-heapdump-worker.js and got this. Please note the size in reduction for the Worker from 344 to 200 through the new logic. does this approch sound reasonable to you?

  { name: 'Node / Worker',
    isRoot: true,
    size: 200,
    edges:
     [ { name: 'wrapped',
         to:
          { name: '<JS Node>',
            isRoot: false,
            size: 0,
            edges: [Array],
            value: [Worker] } },
       { name: 'parent_port',
         to:
          { name: 'Node / MessagePort',
            isRoot: true,
            size: 88,
            edges: [Array] } },
       { name: 'on_thread_finished_',
         to:
          { name: 'Node / AsyncRequest',
            isRoot: false,
            size: 72,
            edges: [Array] } },
       { name: 'thread_stopper_',
         to:
          { name: 'Node / AsyncRequest',
            isRoot: false,
            size: 72,
            edges: [Array] } } ] }

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

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

Thanks, LGTM!

@gireeshpunathil
Copy link
Member Author

@BridgeAR BridgeAR added the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Feb 20, 2019
@addaleax
Copy link
Member

CI is green … Maybe somebody has time to give this a second review?

// be one, of the container object which the current field is part of.
// Reduce the size of memory from the container so that retentions
// are accounted properly.
inline void TrackInlineField(const MemoryRetainer* retainer,
Copy link
Member

@joyeecheung joyeecheung Feb 22, 2019

Choose a reason for hiding this comment

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

I am not sure what this is trying to achieve, is there a use case of this API?

Normally the size of the pointer to the retainer should be accounted into the self size of the parent but self size of the retainer should not be accounted, so I can't tell when it's necessary to do a subtraction - maybe there is a misuse of the API?

Copy link
Member

@joyeecheung joyeecheung Feb 22, 2019

Choose a reason for hiding this comment

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

If this is trying to address the issues that came up from #26099 there are two ways to solve that:

  1. Do not use SET_SELF_SIZE(Worker), which is merely a convenience macro that implements Worker::SelfSize() with sizeof(Worker) to calculate the self size of the worker - that's where we encounter the first tracking of the inline field. Instead, override Worker::SelfSize() to exclude the size of the non-pointer fields, and track them later in Worker::MemoryInfo().
  2. Keep using SET_SELF_SIZE(Worker) for the initial calculation, but rename this method into something like SplitNonPointerField() (SplitStackAllocatedField()?), because this does not necessarily add more memory into the tracker - instead it's splitting off existing memory that have already been tracked, and potentially adding more (if the retainer overrides MemoryInfo() to add additional fields that are not accounted by a sizeof())

Copy link
Member Author

Choose a reason for hiding this comment

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

@joyeecheung - thanks.

Probably these many scenarios exists (with a component cmp and a container cnt with cnt a retainer):

  • cmp is a normal primitive field: self size of cnt covers size of cmp
  • cmp is a composite object (with or without primitive fields): self size of cnt covers size of cmp
  • cmp is a pointer to a composite object: self size of cnt covers size of cmp (size of a pointer)
  • cmp is a composite object (with pointer type fields): self size of cnt covers size of cmp, not the dynamic memory [ example: messageport in worker ]
  • cmp is a composite retainer object (with pointer types): [ current use case ]
  • cmp is a pointer to a retainer object (with pointer types): [ none at the moment? ]

We have Worker with 200 bytes excluding 2 inline AsyncRequest fields of 72 bytes each. So the question is: should we add those to the worker (because those belongs to worker) or add separately - as they are retainers themselves.

If I follow your approach 1, we need to perform that in all such scenarios? in which case arguably the parent retainer (Worker's referencing object if any) could do the same for worker, and potentially render the Retainer abstraction meaningless?

second proposal looks fair to me, but would love to hear from @addaleax as well. Can SplitStackAllocatedField be somewhat misleading, as there is no memory that is stack-allocated here?

Copy link
Member

Choose a reason for hiding this comment

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

because this does not necessarily add more memory into the tracker - instead it's splitting off existing memory that have already been tracked, and potentially adding more (if the retainer overrides MemoryInfo() to add additional fields that are not accounted by a sizeof())

I’m not sure, but I don’t really see this as a issue/as not “tracking” these fields? Then again, re-naming is not a big deal, so I’m okay with anything.

Copy link
Member

Choose a reason for hiding this comment

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

should we add those to the worker (because those belongs to worker) or add separately - as they are retainers themselves.

That depends on whether we want to display AsyncRequest separately as a Node referenced by the Worker node, and any other fields referenced by those AsyncRequests recursively.

If I follow your approach 1, we need to perform that in all such scenarios? in which case arguably the parent retainer (Worker's referencing object if any) could do the same for worker, and potentially render the Retainer abstraction meaningless?

The abstraction helps adding nodes recursively instead of tracking everything from the root, allowing the MemoryRetainer implementations define how they want their fields to be tracked.

Can SplitStackAllocatedField be somewhat misleading, as there is no memory that is stack-allocated here?

Right, the fields are not really stack allocated if the whole thing is not stack allocated.

I’m not sure, but I don’t really see this as a issue/as not “tracking” these fields?

I am fine with the current naming if the comments are changed to reflect when this method should be used and what issue it is trying to address. Use when a retainer is embedded in another. and Reduce the size of memory from the container so that retentions are accounted properly. is too ambiguous IMO, the core of the issue this method is trying to solve arise from that the parent does not implement its SelfSize() correctly to exclude the memory that is supposed to be tracked as the self size of another node.

Copy link
Member Author

Choose a reason for hiding this comment

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

@joyeecheung - thanks, I will amend the comments, aligning with above conversation.

Copy link
Member Author

Choose a reason for hiding this comment

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

@joyeecheung - amended the comment, ptal!

@gireeshpunathil gireeshpunathil removed the author ready PRs that have at least one approval, no pending requests for changes, and a CI started. label Feb 28, 2019
If retainers are embedded in retainers, direct tracking
those lead to double tracking. Instead, use a special tracker
that adjusts the tracking for the container object.

PR-URL: nodejs#26161
Reviewed-By: Anna Henningsen <[email protected]>
@gireeshpunathil
Copy link
Member Author

landed as 17b4949

@gireeshpunathil gireeshpunathil merged commit 17b4949 into nodejs:master Feb 28, 2019
addaleax pushed a commit that referenced this pull request Mar 1, 2019
If retainers are embedded in retainers, direct tracking
those lead to double tracking. Instead, use a special tracker
that adjusts the tracking for the container object.

PR-URL: #26161
Reviewed-By: Anna Henningsen <[email protected]>
@BridgeAR BridgeAR mentioned this pull request Mar 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c++ Issues and PRs that require attention from people who are familiar with C++.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants