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

Generate DecodeFrom() methods for Golang #71

Merged
merged 12 commits into from
Nov 15, 2021
Merged

Conversation

2opremio
Copy link
Contributor

@2opremio 2opremio commented Nov 11, 2021

This is the decoding counterpart of #65

There is a dramatic performance improvement: ~13x speedup and and ~11x allocation reduction.

Check stellar/go#4064 for details on the speedup and the generated code.

TODO:

  • Make EncodeTo()-related code consistent with the changes in this PR. I intentionally didn't touch EncodeTo-code here in order to avoid noise (otherwise it will be more difficult to read the generated code at xdr: update using xdrgen#71 go#4064 ). I will update them in a separate PR.

@2opremio
Copy link
Contributor Author

2opremio commented Nov 11, 2021

It seems there is still a minor problem (decoding an empty value instead of nil in some cases). I will look into that soon

@2opremio
Copy link
Contributor Author

2opremio commented Nov 11, 2021

@2opremio
Copy link
Contributor Author

2opremio commented Nov 12, 2021

@bartekn could you please take a quick look at the failure above?

ERRO[2021-11-12T00:24:58.012Z] Error in ingestion state machine              current_state="verifyRange(fromLedger=10000063, toLedger=10000127, verifyState=true)" error="Error preparing range: Error fast-forwarding to 10000063: stellar core exited unexpectedly" next_state=stop pid=430 service=ingest
INFO[2021-11-12T00:24:58.012Z] Shut down                                     pid=430 service=ingest
Error preparing range: Error fast-forwarding to 10000063: stellar core exited unexpectedly

I don't understand why stellar core fails and there is no context.

@2opremio
Copy link
Contributor Author

OK, I think I know what's happening. I shouldn't be using ReadAll()

There was a bug in the captive core backend hiding the error stellar/go#4066

@2opremio
Copy link
Contributor Author

OK, the verify-range problem is fixed. I am going to run verify-range in the last 100k ledgers 🤞

@2opremio
Copy link
Contributor Author

Note for reviewers, please check the code generated at stellar/go#4064 before reviewing the code here.

In fact, it's vital to review the code generated before reviewing this PR itself.

@2opremio
Copy link
Contributor Author

verify range succeeded! 🥳

Status of auto-second-half-job28279:
{
  "STARTING": 0,
  "FAILED": 0,
  "RUNNING": 0,
  "SUCCEEDED": 9,
  "RUNNABLE": 0,
  "SUBMITTED": 0,
  "PENDING": 0
}

lib/xdrgen/generators/go.rb Outdated Show resolved Hide resolved
out.puts " for i := 0; i < len(#{var}); i++ {"
element_var = "#{var}[i]"
optional_within = type.is_a?(AST::Identifier) && type.resolved_type.sub_type == :optional
if optional_within
Copy link
Contributor

Choose a reason for hiding this comment

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

Intuitively, it seems like this optional block (and above) should be able to be handled with a recursive data-type, like a Option<Foo> type-thing, and having a render_optional method... 🤔 but this is probably good enough for now.

Copy link
Contributor

@paulbellamy paulbellamy Nov 15, 2021

Choose a reason for hiding this comment

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

I think I'm thinking something like:

def render_decode_from_body(out, var, type, declared_variables:, self_encode:)
  # ...
  if type.sub_type == :optional
    return render_optional(out, var, type, type, declared_variables: declared_variables, self_encode: self_encode) do |inner_type|
      # Recurse into the unwrapped inner type
      render_decode_from_body(out, var, inner_type, declared_variables: declared_variables, self_encode: self_encode)
    end
  end
  # ...
end

def render_optional(out, var, type, declared_variables:, self_encode:)
  render_variable_declaration(out, "  ", 'b', "bool", declared_variables: declared_variables)
  out.puts "  b, nTmp, err = d.DecodeBool()"
  out.puts tail
  out.puts "  #{var} = nil"
  out.puts "  if b {"
  out.puts "     #{var} = new(#{name type.resolved_type.declaration.type})"
  # TODO: Unwrap the inner_type (without the optional wrapper) here
  yield inner_type
  out.puts "  }"
end

But, actually you could do a similar block thing for arrays as well, I think...

Copy link
Contributor

Choose a reason for hiding this comment

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

Either way, probably outside the scope of this PR.

@paulbellamy
Copy link
Contributor

paulbellamy commented Nov 15, 2021

also, unrelated to this PR, but wdyt of, instead of doing puts " some spaces for indentation", just doing puts "no indentation spaces", and running gofmt on it at the end?

@2opremio
Copy link
Contributor Author

also, unrelated to this PR, but wdyt of, instead of doing puts " some spaces for indentation", just doing puts "no indentation spaces", and running gofmt on it at the end?

I think it's probably a good idea. we in fact do run gofmt when generating the code in stellar-go. However, that requires a bigger refactoring

@2opremio
Copy link
Contributor Author

@bartekn PTAL

Copy link
Contributor

@bartekn bartekn left a comment

Choose a reason for hiding this comment

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

Awesome!

Copy link
Member

@leighmcculloch leighmcculloch left a comment

Choose a reason for hiding this comment

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

Three requests (❗) otherwise LGTM. I definitely don't understand all of it, unfortunately xdrgen is pretty complex, but the areas of the generated code I sampled in stellar/go#4064 look good.

I also left some non-blocking comments: a suggestion (💡), a thought for future improvements that may or may not be beneficial (💭), and a question about something I don't understand (❔).

🔍 I see above we ran a verify range test with 100k ledgers. That's only 5 days worth of ledgers. Given the larger surface area of xdr decoding and its risk for Horizon, and that Horizon supports reingestion of much larger periods of time, should we perform a verify range of a much larger range of time before merging? cc @bartekn

@@ -580,8 +815,16 @@ def render_top_matter(out)
xdrType()
}

type xdrDecodable interface {
Copy link
Member

Choose a reason for hiding this comment

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

❗ Could you add for every type a cast to an unnamed variable? We do that with all the other interfaces in here to make sure all the types satisfy the interfaces. i.e.:

var _ xdrDecodeable = (*#{name})(nil)

Copy link
Member

Choose a reason for hiding this comment

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

❗ Could you name this interface decoderFrom? That's the pattern used for similar named interfaces in the stdlib. e.g. The interface that contains the ReadFrom function is ReaderFrom.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do

Copy link
Member

Choose a reason for hiding this comment

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

+1 As a follow up we can remove the xdrType thing I added too, it's superfluous and wasn't the best approach I took. I'm happy to do the clean up for EncodeTo, but if you're going to do it 👍🏻 sgtm.

Copy link
Contributor Author

@2opremio 2opremio Nov 15, 2021

Choose a reason for hiding this comment

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

If you could cleanup the encodeto part (adding errors, making it consistent with this code etc ...). I would appreciate it.

if err != nil {
return n, err
}
if _, ok := #{private_name type}Map[v]; !ok {
Copy link
Member

Choose a reason for hiding this comment

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

💭 Given that most of our enums start at zero and are sequential, and given that use of the map will now be in the hot path, I wonder if for enums that we detect have that property, if we could simply replace this with a if v >= len(...Map) { // error }? We don't have to do this, and we definitely don't have to do this in this PR, but maybe something to evaluate if there is a saving here over hitting the map.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea, However:

There are many enums with negative values. Some examples are: BucketEntryType, CreateAccountResultCode, PaymentResultCode, PathPaymentStrictReceiveResultCode ....

Not even a range is generally a possibility since the XDR definition can contain scattered values.

We could infer a range where available and use that, but I think it is an overoptimization (without further proof of a bottleneck) and definitely out of the scope of this PR.

Let's later see if further profiling reveals map access as a problem.

render_variable_declaration(out, " ", 'b', "bool", declared_variables: declared_variables)
out.puts " b, nTmp, err = d.DecodeBool()"
out.puts tail
out.puts " #{var} = nil"
Copy link
Member

Choose a reason for hiding this comment

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

🎉 I really like that this sets the value of every field.

lib/xdrgen/generators/go.rb Outdated Show resolved Hide resolved
lib/xdrgen/generators/go.rb Outdated Show resolved Hide resolved
lib/xdrgen/generators/go.rb Show resolved Hide resolved
@2opremio 2opremio force-pushed the generate-decodefrom branch from 968464a to 548f250 Compare November 15, 2021 21:50
@2opremio 2opremio merged commit 7d53fb1 into master Nov 15, 2021
@2opremio 2opremio deleted the generate-decodefrom branch November 15, 2021 21:56
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.

4 participants