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

Fix regression in do_write(s) causing significant performance issues when using large (>10meg) writes #706

Merged

Conversation

jaymzjulian
Copy link

@jaymzjulian jaymzjulian commented Dec 12, 2023

Adjust the buffer write function to clear the buffer once, rather than piece by piece. This avoids a case where a large write (in our case, around 70mbytes) will consume 100% of CPU. This takes a webrick GET request via SSL from around 200kbyts/sec and consuming 100% of a core, to line speed on gigabit ethernet and 6% cpu utlization.

(The reason was that removing the head of the buffer was a full copy every single write block, which turned out to be 16k for us, hence every 16k copying the 77meg of data that went in the front of it).

Passes tests, and has been tested on our systems with chef-zero

lib/openssl/buffering.rb Outdated Show resolved Hide resolved
@jaymzjulian jaymzjulian force-pushed the fix-large-buffered-write-regression branch from 2dee91d to d114c5e Compare January 1, 2024 04:30
lib/openssl/buffering.rb Outdated Show resolved Hide resolved
@rhenium
Copy link
Member

rhenium commented Jan 13, 2024

Could you share the benchmark?

I'm not sure how this change can be a massive performance improvement, since

(The reason was that removing the head of the buffer was a full copy every single write block, which turned out to be 16k for us, hence every 16k copying the 77meg of data that went in the front of it).

this is not supposed to happen.

@jaymzjulian jaymzjulian force-pushed the fix-large-buffered-write-regression branch from d114c5e to 0140ba8 Compare January 14, 2024 04:34
@jaymzjulian
Copy link
Author

jaymzjulian commented Jan 14, 2024 via email

@jaymzjulian
Copy link
Author

jaymzjulian commented Jan 14, 2024 via email

@jaymzjulian jaymzjulian force-pushed the fix-large-buffered-write-regression branch 4 times, most recently from d3b3d14 to 5b008f8 Compare January 14, 2024 05:07
@junaruga
Copy link
Member

junaruga commented Jan 14, 2024

Apart from the main topic, the code block on the comment above is not displayed properly on the page. Perhaps the code block syntax may not work when replying via email.

@jaymzjulian
Copy link
Author

Apart from the main topic, the code block on the comment above is not displayed properly on the page. Perhaps the code block syntax may not work when replying via email.

Apologies, it does not

require 'webrick'
require 'webrick/https'

class FileReader < WEBrick::HTTPServlet::AbstractServlet
  def do_GET(request, response)
    response.content_type = "binary/octet-stream"
    response.body = IO.read(File.expand_path("~/webroot/test.tgz"))
  end
end

root = File.expand_path '~/webroot'
cert_name = 'CN=localhost'
server = WEBrick::HTTPServer.new :Port => 8443, :DocumentRoot => root,
:SSLEnable => true, :SSLCertName => cert_name
server.mount "/", FileReader

trap 'INT' do server.shutdown end
server.start

is what I was going for with the test case code

@ioquatix
Copy link
Member

ioquatix commented Feb 3, 2024

@s7nfo
Copy link

s7nfo commented Feb 3, 2024

Very cool, this looks exactly like what I ran into.

this is not supposed to happen.

I suggest running ‘ltrace -e memcpy’ against a reproducer like I did in the blog post, the issue becomes very apparent.

@ioquatix
Copy link
Member

ioquatix commented Feb 3, 2024

Ruby Strings for IO are surprisingly hard to get correct / "efficient" and there are a bunch of land mines we need to avoid...

@rhenium
Copy link
Member

rhenium commented Feb 5, 2024

I reproduced it with the webrick example using an older Ruby version. This is a regression introduced in commit acc8079, which went to ruby/openssl v2.1.3 and Ruby 2.6. #484 also reported this, but I didn't realize it was this bad.

This occurs in Ruby <= 3.1 where string[0, n] = "" would copy the content if string is already a shared string. It's fixed in Ruby 3.2.

I didn't merge this PR right away because this costs one extra string object allocation per write, but we should fix it if it affects Ruby 3.1 which has more than 1 year until EOL.

Ruby Strings for IO are surprisingly hard to get correct / "efficient" and there are a bunch of land mines we need to avoid...

Agreed...

@rhenium
Copy link
Member

rhenium commented Feb 5, 2024

I think this should be applied to stable branches too.

@jaymzjulian could you rebase this on top of maint-3.0 branch (the oldest branch we fix non-secuirty bugs) and fix commit message formatting?

begin
while nwrote < buffer_size do
begin
nwrote += syswrite(@wbuffer[nwrote..buffer_size])
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
nwrote += syswrite(@wbuffer[nwrote..buffer_size])
nwrote += syswrite(@wbuffer[nwrote, buffer_size - nwrote])

can save one Range object allocation in the loop.

Copy link
Author

Choose a reason for hiding this comment

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

Oh hey, this is totally better - updated!

@jaymzjulian jaymzjulian changed the base branch from master to maint-3.0 February 5, 2024 23:03
@jaymzjulian jaymzjulian force-pushed the fix-large-buffered-write-regression branch from 5b008f8 to 0768730 Compare February 5, 2024 23:03
This causes significant performance issues when using large (>10meg) writes

Fix by adjusting the buffer write function to clear the buffer once, rather than
piece by piece, avoiding a case where a large write (in our case, around
70mbytes) will consume 100% of CPU. This takes a webrick GET request via SSL
from around 200kbyts/sec and consuming 100% of a core, to line speed on gigabit
ethernet and 6% cpu utlization.
@jaymzjulian jaymzjulian force-pushed the fix-large-buffered-write-regression branch from 0768730 to d4389b4 Compare February 5, 2024 23:03
@jaymzjulian
Copy link
Author

I think this should be applied to stable branches too.

@jaymzjulian could you rebase this on top of maint-3.0 branch (the oldest branch we fix non-secuirty bugs) and fix commit message formatting?

This is now rebased to target maint-3.0, as well as with better formatting in the commit message. Thanks!

@ioquatix
Copy link
Member

ioquatix commented Feb 6, 2024

@rhenium are you good to merge this?

@rhenium rhenium merged commit 1bb0ce2 into ruby:maint-3.0 Mar 21, 2024
19 checks passed
@rhenium
Copy link
Member

rhenium commented Mar 21, 2024

(Sorry for the long delay)

Yes, it looks good to me. Thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants