Skip to content

msg: Code cleanup and optimizations#60220

Merged
batrick merged 10 commits intoceph:mainfrom
MaxKellermann:msg_optimizations
Oct 22, 2024
Merged

msg: Code cleanup and optimizations#60220
batrick merged 10 commits intoceph:mainfrom
MaxKellermann:msg_optimizations

Conversation

@MaxKellermann
Copy link
Member

Checklist

  • Tracker (select at least one)
    • References tracker ticket
    • Very recent bug; references commit where it was introduced
    • New feature (ticket optional)
    • Doc update (no ticket needed)
    • Code cleanup (no ticket needed)
  • Component impact
    • Affects Dashboard, opened tracker ticket
    • Affects Orchestrator, opened tracker ticket
    • No impact that needs to be tracked
  • Documentation (select at least one)
    • Updates relevant documentation
    • No doc update is appropriate
  • Tests (select at least one)

@MaxKellermann MaxKellermann requested a review from a team as a code owner October 9, 2024 13:23
@github-actions github-actions bot added the core label Oct 9, 2024
Copy link
Member

@batrick batrick left a comment

Choose a reason for hiding this comment

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

The locking changes are the ones I'm least certain about. I'd request those go in a separate PR too since they will require more rigorous review and broad QA. The other changes are uncontroversial and could be merged quickly.

@@ -305,12 +305,12 @@ ssize_t AsyncConnection::read_bulk(char *buf, unsigned len)
ssize_t AsyncConnection::write(ceph::buffer::list &bl,
std::function<void(ssize_t)> callback,
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
std::function<void(ssize_t)> callback,
std::function<void(ssize_t)>&& callback,

also appropriate?

Copy link
Member Author

Choose a reason for hiding this comment

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

Would it be better?
Without &&, the std::function is created on the caller's stack which gets passed to the method.
With the &&, the std::function is created on the caller's stack, and additionally, a pointer to this stack address gets passed to the method.
This is only useful if you already have a reference which you forward, so you don't allocate any stack space.

Copy link
Member

Choose a reason for hiding this comment

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

Just conventional, I suppose. I don't think it matters much.

do {
if (connection->is_queued()) {
if (r = connection->_try_send(); r!= 0) {
connection->write_lock.unlock();
Copy link
Member

Choose a reason for hiding this comment

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

msg/async/ProtocolV[12]: unlock write_lockbefore calling_try_send()`` please expand the commit message why this is appropriate/safe.

Copy link
Member Author

Choose a reason for hiding this comment

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

That's going to be difficult, because I have to explain the absence of something - try_send() does not access anything that needs write_lock protection. If somebody thinks otherwise, I'd like to hear about it.

But anyway, the code doesn't make much sense - it calls dispatch_event_external() which is a function designed to be called from outside the I/O thread, but by definition, we must be inside the I/O thread (or else we wouldn't be allowed to access outgoing_bl). This is confusing, but I guess I'm here to clean up the mess.

Copy link
Member

Choose a reason for hiding this comment

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

That's going to be difficult, because I have to explain the absence of something - try_send() does not access anything that needs write_lock protection. If somebody thinks otherwise, I'd like to hear about it.

I think that's what makes this challenging to review too. We would want to be certain your supposition is correct.

You won't hear much argument from here that this code can be messy. I'm glad you're here!

Copy link
Member Author

Choose a reason for hiding this comment

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

The problem is that while locks are extremely important, documentation on what they protect is nowhere to be found. My theory is that write_lock really protects nothing at all. At least I couldn't figure out anything. So I'm trying to remove it one-by-one from calls that look obviously ok without that lock; most importantly to calls that do I/O, and you should never to I/O while holding locks.

My profiler says that the MDS spends 20% of the CPU time only in sendmsg() - and that's actual CPU time, without the off-cpu time that must be added on top. That means write_lock is kept locked at least 20% of the time, for no reason at all.

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 forgot: AsyncConnection::write_lock does protect something - just not in class AsyncConnection. It protects ProtocolV[12]::out_queue (and others in these two classes). This mutex should probably be moved there, out of AsyncConnection, to avoid confusion.

Copy link
Member

Choose a reason for hiding this comment

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

@MaxKellermann your profile of sengmsg() is correect, at least from the profiling I've done. It's a major issue and one of the reasons I was looking at reducing the number of async messenger threads talking to the kernel.

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 have several patches which reduce the number of sendmsg() system calls, but I can only submit them only after some of the other PRs are merged because they build on top of them.
Anyway, the number of I/O threads isn't a performance problem; but a real performance problem is all the bouncing between workers, dispatcher, finisher, and submit thread. One single request engages all of them, and that causes a lot of inter-thread communication overhead which is very visible in my profiler.
Reducing lock contention is really important, and several of my PRs do that; but it would be much better to stay in one thread for handling a request.

@MaxKellermann
Copy link
Member Author

The locking changes are the ones I'm least certain about. I'd request those go in a separate PR too since they will require more rigorous review and broad QA.

Dropped for now. (These two patches are part of the patch set that make the MDS faster. Reducing lock contention is one important piece.)

@batrick
Copy link
Member

batrick commented Oct 9, 2024

/home/jenkins-build/build/workspace/ceph-pull-requests/src/msg/async/ProtocolV1.cc:1257:58: error: no member named 'second' in 'ProtocolV1::out_q_entry_t'
      ldout(cct, 20) << __func__ << " discard " << entry.second << dendl;
                                                   ~~~~~ ^
/home/jenkins-build/build/workspace/ceph-pull-requests/src/msg/async/ProtocolV1.cc:1258:13: error: no member named 'second' in 'ProtocolV1::out_q_entry_t'
      entry.second->put();
      ~~~~~ ^
2 errors generated.

Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
This allows eliminating one lookup in `_get_next_outgoing()` because
we can pass the iterator instead of the key to `erase()`.

Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
Since `std::function` is nullable and as an `operator bool()`, we can
easily eliminate the `std::optional` overhead.

Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
@batrick
Copy link
Member

batrick commented Oct 10, 2024

jenkins test make check

@batrick
Copy link
Member

batrick commented Oct 10, 2024

jenkins test make check arm64

@markhpc markhpc changed the title Code cleanup and optimizations in "msg" msg: Code cleanup and optimizations Oct 10, 2024
@batrick
Copy link
Member

batrick commented Oct 19, 2024

This PR is under test in https://tracker.ceph.com/issues/68629.

batrick added a commit to batrick/ceph that referenced this pull request Oct 19, 2024
* refs/pull/60220/head:
	msg/async/AsyncConnection: move the writeCallback instead of copying it
	msg/async/AsyncConnection: do not wrap writeCallback in `std::optional`
	msg/async/frames_v2: use zero-initialization instead of memset()
	msg/async/Event: use zero-initialization instead of memset()
	msg/Message: use zero-initialization instead of memset()
	msg/async/ProtocolV2: eliminate redundant std::map lookups
	msg/async/ProtocolV[12]: reverse the std::map sort order
	msg/async/ProtocolV[12]: use `auto`
	msg/async/ProtocolV[12]: use range-based `for`
	msg/async/ProtocolV1: use zero-initialization instead of memset()
@batrick
Copy link
Member

batrick commented Oct 22, 2024

@batrick batrick merged commit bb13534 into ceph:main Oct 22, 2024
@MaxKellermann MaxKellermann deleted the msg_optimizations branch October 22, 2024 05:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants