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

DO NOT MERGE: less ref counting #1

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

weissi
Copy link

@weissi weissi commented Nov 24, 2016

this is a mess, sorry

but

before:

$ .build/release/bench
Benchmark result:
Took 7.7 seconds to run
648582.28 req/sec

after:

$ .build/release/bench
Benchmark result:
Took 5.27 seconds to run
948171.8 req/sec

@weissi weissi changed the title less ref counting DO NOT MERGE: less ref counting Nov 24, 2016
func on_headers_complete() -> Int
func on_body(at: UnsafePointer<UInt8>, length: Int) -> Int
func on_message_complete() -> Int
public struct http_parser_delegate {
Copy link

Choose a reason for hiding this comment

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

Using escaping closures instead of a protocol was way slower in my tests.

Unrelated, missing in the original too: the callbacks need the parser as the first argument (like in the original) to provide the necessary context (w/o requiring capturing closures).

Copy link
Member

Choose a reason for hiding this comment

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

struct http_parser_delegate combined with struct http_parser is indeed faster. I tried protocol http_parser_delegate with struct http_parser and it was slower than using protocol http_parser_delegate and class http_parser due to extra thunking inserted by the compiler.

@helje5, as you point out, the bench.c program does nothing, so it does not measure any penalty referencing a required context. What we should consider is creating a benchmark where the 'C' and 'Swift' version both use the context to perform something more meaningful (like count callbacks and byte counts). We should see the benchmark results get closer.

Another approach would be wrap the 'C' version with a Swift class like Kitura's HTTPParser wrapper and measure the performance with a pure Swift implementation. This would be closer to where we need to eventually be.

Copy link

Choose a reason for hiding this comment

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

I would really like to know why a protocol would be any slower. What is that 'thunking', is that documented somewhere? I would expect a (non objc Swift) protocol to be essentially a plain vtable. That should be way faster than escaping closures.

I don't understand the HTTPParser wrapper you are pointing to. That looks entirely pointless/superfluous to me.

Copy link
Member

Choose a reason for hiding this comment

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

Here is a performance comparison with the two scenarios I mentioned above. I cannot explain why protocol http_parser_delegate + struct http_parser is the slowest combination, but it seems like extra retain/release calls are involved.
protocol

let on_chunk_header: () -> Int
let on_chunk_complete: () -> Int

public init(on_message_begin: @escaping () -> Int,
Copy link

Choose a reason for hiding this comment

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

In the original the parser itself doesn't maintain the callbacks, it is kept in a separate struct which is passed to execute(). This makes sense because the parser is reentrant/stateless and the mapping is usually static (only one copy needed). Remember that the idea is to keep the per-connection overhead as small as possible (in the original only 40 bytes per connection!).

@@ -176,7 +312,7 @@ private let F_SKIPBODY: http_flags = 1 << 6
private let F_CONTENTLENGTH: http_flags = 1 << 7

/* Map for errno-related constants */
public enum http_errno: String, Error {
public enum http_errno: StaticString, Error {
Copy link

Choose a reason for hiding this comment

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

This should not be a String in the first place but just an Int num. You never use those cases as Strings anyways but the description property.

Copy link
Member

Choose a reason for hiding this comment

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

The following function uses the String (StaticString) to provide the name of the error via .rawValue:

/* Return a string name of the given error */
public class func errno_name(_ err: http_errno) -> String {
  return err.rawValue
}

This function is not used in bench.c/.swift so I thought it would not effect performance. However I replaced StaticString with String and the benchmark did slow down. More investigation is required.

Copy link

Choose a reason for hiding this comment

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

Actually I was kinda wrong on this one. I was assuming that a Swift String enum would use Strings at runtime to match cases. That actually doesn't seem to be the case. The enum is always some small integer and it the compiler generates the switch to do the raw value mapping from/to string.

But for the specific function you mention, that kinda works for any enum: "(err)" and matches what the C variant does.

@@ -254,17 +390,18 @@ public enum http_errno: String, Error {
}
}

public class http_parser {
private func LIKELY(_ X: Bool) -> Bool { return X }
Copy link

Choose a reason for hiding this comment

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

I would @inline(__always) such until the compiler crashes :-)

      (__)
    /  .\/.     ______
   |  /\_|     |      \
   |  |___     |       |
   |   ---@    |_______|
*  |  |   ----   |    |
 \ |  |_____
  \|________|

CompuCow Discovers Bug in Compiler

Copy link
Member

Choose a reason for hiding this comment

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

I tried this on a number of functions like LOWER(), IS_ALPHA(), IS_NUM(), IS_ALPHANUM() and I did not see any performance improvement in Release mode.

@@ -845,15 +861,15 @@ private func IS_HEADER_CHAR(_ ch: UInt8) -> Bool {
return (ch == CR || ch == LF || ch == 9 || (ch > 31 && ch != 127))
}

private func STRICT_TOKEN(_ c: UInt8) -> UInt8 {
private mutating func STRICT_TOKEN(_ c: UInt8) -> UInt8 {
Copy link

Choose a reason for hiding this comment

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

This isn't mutating?

Copy link
Member

Choose a reason for hiding this comment

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

Mutating is required on 10 or so functions if using struct http_parser. This is not required using class http_parser.

The compiler error is:
Mark method 'mutating' to make 'self' mutable

Copy link

Choose a reason for hiding this comment

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

Not sure what you are trying to say here. This specific function (STRICT_TOKEN) isn't mutating. It is just: return tokens[Int(c)]. If the compiler throws an error on this, it really looks like a bug?

Copy link
Member

Choose a reason for hiding this comment

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

You are correct. STRICT_TOKEN does not require mutating. My mistake.

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

Successfully merging this pull request may close these issues.

3 participants