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

Makes writeValueRecursively iterative #368

Merged
merged 4 commits into from
Jun 16, 2021
Merged

Makes writeValueRecursively iterative #368

merged 4 commits into from
Jun 16, 2021

Conversation

zslayton
Copy link
Contributor

@zslayton zslayton commented Jun 16, 2021

The method _Private_IonWriterBase#writeValueRecursively is
recursive in two senses:

  1. It visits all of the children in the provided IonReader's
    current value recursively.
  2. The method itself is implemented using recursion.

This patch modifies the implementation to be iterative,
eliminating a source of StackOverflowExceptions and offering
a modest reduction in CPU cost.

Benchmark

This benchmark creates an IonStruct that is 1,000 levels deep, wraps it in an IonReader, then uses writeValueRecursively to write it to a binary Ion stream 100k times. The source can be viewed here.

This change does not affect allocations or garbage collection, only CPU time.

Before

                                       Score           Error   Units
time                                1804.311 ±         7.401   ms/op
·gc.alloc.rate                      1089.760 ±         3.556  MB/sec
·gc.alloc.rate.norm           2636281467.200 ±        70.340    B/op
·gc.churn.G1_Eden_Space             1092.004 ±        73.732  MB/sec
·gc.churn.G1_Eden_Space.norm  2641782374.400 ± 180920323.756    B/op
·gc.churn.G1_Old_Gen                   0.171 ±         0.040  MB/sec
·gc.churn.G1_Old_Gen.norm         413110.400 ±     96520.541    B/op
·gc.count                            114.000                  counts
·gc.time                              83.000                      ms

After

                                       Score           Error   Units
time                                1735.452 ±        42.039   ms/op
·gc.alloc.rate                      1123.361 ±        21.138  MB/sec
·gc.alloc.rate.norm           2636281504.000 ±         0.001    B/op
·gc.churn.G1_Eden_Space             1131.249 ±        89.896  MB/sec
·gc.churn.G1_Eden_Space.norm  2654679859.200 ± 200798830.041    B/op
·gc.churn.G1_Old_Gen                   0.162 ±         0.046  MB/sec
·gc.churn.G1_Old_Gen.norm         380224.000 ±    112156.067    B/op
·gc.count                            105.000                  counts
·gc.time                              82.000                      ms

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

The method `_Private_IonWriterBase#writeValueRecursively` is
recursive in two senses:

1. It visits all of the children in the provided IonReader's
   current value recursively.
2. The method itself is implemented using recursion.

This patch modifies the implementation (#2) to be iterative,
eliminating a source of StackOverflowExceptions and offering
a modest reduction in CPU cost.
@codecov
Copy link

codecov bot commented Jun 16, 2021

Codecov Report

Merging #368 (b102d59) into master (64a5b8e) will increase coverage by 0.00%.
The diff coverage is 88.88%.

Impacted file tree graph

@@            Coverage Diff            @@
##             master     #368   +/-   ##
=========================================
  Coverage     64.04%   64.05%           
+ Complexity     4838     4835    -3     
=========================================
  Files           136      136           
  Lines         21105    21108    +3     
  Branches       3818     3821    +3     
=========================================
+ Hits          13517    13520    +3     
+ Misses         6254     6251    -3     
- Partials       1334     1337    +3     
Impacted Files Coverage Δ
...rc/com/amazon/ion/impl/_Private_IonWriterBase.java 77.86% <88.63%> (-0.27%) ⬇️
src/com/amazon/ion/impl/IonWriterUserBinary.java 56.09% <100.00%> (ø)
src/com/amazon/ion/impl/BlockedBuffer.java 51.09% <0.00%> (+0.12%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 64a5b8e...b102d59. Read the comment docs.

Comment on lines +432 to +433
// The isNullValue() check above will handle this.
throw new IllegalStateException("isNullValue() was false but IonType was NULL.");
Copy link
Contributor

Choose a reason for hiding this comment

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

I wondered about this, thanks for the clarification

stepIn(IonType.SEXP);
break;
default:
throw new IllegalStateException("Unknown value type: " + type);
Copy link
Contributor

Choose a reason for hiding this comment

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

Your mileage may vary here, but what do you think of this expression?

switch (type) {
    case      NULL: // The isNullValue() check above will handle this.
        throw new IllegalStateException("isNullValue() was false but IonType was NULL.");
    
    case      BOOL: writeBool(reader.booleanValue());        break;
    case       INT: writeInt(reader.bigIntegerValue());      break;
    case     FLOAT: writeFloat(reader.doubleValue());        break;
    case   DECIMAL: writeDecimal(reader.decimalValue());     break;
    case TIMESTAMP: writeTimestamp(reader.timestampValue()); break;
    case    STRING: writeString(reader.stringValue());       break;
    case    SYMBOL: writeSymbolToken(reader.symbolValue());  break;
    case      BLOB: writeBlob(reader.newBytes());            break;
    case      CLOB: writeClob(reader.newBytes());            break;
    case    STRUCT: // Intentional fallthrough
    case      LIST: // Intentional fallthrough
    case      SEXP: reader.stepIn(); stepIn(type);           break;

    default:
        throw new IllegalStateException("Unexpected value type: " + type);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I adopted the fallthrough for the container types, thanks!

*/
final void writeValueRecursively(IonType type, IonReader reader)
throws IOException
final void writeValueRecursively(IonType ionType, IonReader reader)
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm.... 🤨

Copy link
Contributor

Choose a reason for hiding this comment

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

Zack responded offline that while the implementation is no longer recursive the value structure still is. This makes sense, traversing a recursive structure is what this does no matter how it does it.

@jobarr-amzn
Copy link
Contributor

Thank you for the detailed comments!

tgregg
tgregg previously approved these changes Jun 16, 2021
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