-
Notifications
You must be signed in to change notification settings - Fork 61
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
Add a method for marshaling directly into a user-provided buffer. #521
Conversation
As part of this, I moved the scratch byte array from a pooled buffer field to the stack as-needed.
I also tried updating the implementation of
I would still rather see |
7505de0
to
176bf87
Compare
There doesn't seem to be any performance advantage to the [16]byte scratch space on each encoderBuffer versus stack-allocated local array variables. If anything, there may be a spatial locality advantage to using the stack. B/op and allocs/op are unchanged, sec/op is somewhere between a wash and very marginally better: │ scratch-withbuffer.txt │ scratch-stack.txt │ │ sec/op │ sec/op vs base │ Marshal/Go_bool_to_CBOR_bool 46.77n ± 1% 47.19n ± 0% +0.90% (p=0.000 n=10) Marshal/Go_uint64_to_CBOR_positive_int 66.03n ± 1% 66.15n ± 0% ~ (p=0.393 n=10) Marshal/Go_int64_to_CBOR_negative_int 52.34n ± 1% 52.44n ± 4% ~ (p=0.306 n=10) Marshal/Go_float64_to_CBOR_float 62.95n ± 1% 63.70n ± 1% +1.19% (p=0.000 n=10) Marshal/Go_[]uint8_to_CBOR_bytes 76.89n ± 1% 76.22n ± 0% -0.86% (p=0.000 n=10) Marshal/Go_string_to_CBOR_text 79.52n ± 1% 78.86n ± 1% -0.82% (p=0.000 n=10) Marshal/Go_[]int_to_CBOR_array 294.6n ± 0% 292.8n ± 0% -0.64% (p=0.000 n=10) Marshal/Go_map[string]string_to_CBOR_map 950.4n ± 1% 923.9n ± 1% -2.79% (p=0.000 n=10) Marshal/Go_map[string]interface{}_to_CBOR_map 2.300µ ± 1% 2.310µ ± 0% ~ (p=0.137 n=10) Marshal/Go_struct_to_CBOR_map 1.422µ ± 1% 1.409µ ± 1% -0.88% (p=0.006 n=10) Marshal/Go_map[int]interface{}_to_CBOR_map 2.207µ ± 0% 2.196µ ± 1% -0.50% (p=0.004 n=10) Marshal/Go_struct_keyasint_to_CBOR_map 1.394µ ± 0% 1.397µ ± 0% ~ (p=0.050 n=10) Marshal/Go_[]interface{}_to_CBOR_map 1.676µ ± 0% 1.632µ ± 1% -2.63% (p=0.000 n=10) Marshal/Go_struct_toarray_to_CBOR_array 1.370µ ± 1% 1.357µ ± 1% -0.99% (p=0.000 n=10) MarshalCanonical/Go_map[string]string_to_CBOR_map 947.9n ± 1% 918.3n ± 1% -3.12% (p=0.000 n=10) MarshalCanonical/Go_map[string]string_to_CBOR_map_canonical 1.533µ ± 1% 1.503µ ± 2% -1.96% (p=0.002 n=10) MarshalCanonical/Go_struct_to_CBOR_map 307.9n ± 0% 309.0n ± 1% +0.34% (p=0.014 n=10) MarshalCanonical/Go_struct_to_CBOR_map_canonical 309.9n ± 0% 309.4n ± 1% ~ (p=1.000 n=10) MarshalCanonical/Go_map[int]int_to_CBOR_map 196.7n ± 0% 198.2n ± 0% +0.76% (p=0.000 n=10) MarshalCanonical/Go_map[int]int_to_CBOR_map_canonical 197.6n ± 1% 198.6n ± 1% +0.51% (p=0.005 n=10) MarshalCOSE/128-Bit_Symmetric_Key 340.3n ± 0% 341.6n ± 0% +0.38% (p=0.001 n=10) MarshalCOSE/256-Bit_Symmetric_Key 344.0n ± 0% 344.1n ± 1% ~ (p=0.540 n=10) MarshalCOSE/ECDSA_P256_256-Bit_Key 433.2n ± 0% 438.1n ± 1% +1.12% (p=0.000 n=10) MarshalCWTClaims 296.5n ± 0% 298.9n ± 1% +0.83% (p=0.003 n=10) MarshalSenML 1.522µ ± 0% 1.524µ ± 1% ~ (p=0.697 n=10) MarshalSenMLShortestFloat16 1.534µ ± 1% 1.557µ ± 1% +1.53% (p=0.001 n=10) MarshalWebAuthn 406.5n ± 0% 409.4n ± 1% +0.71% (p=0.007 n=10) MarshalCOSEMAC 319.1n ± 0% 319.6n ± 1% ~ (p=0.446 n=10) MarshalCOSEMACWithTag 399.8n ± 0% 397.9n ± 0% -0.49% (p=0.007 n=10) geomean 404.3n 403.5n -0.22% Signed-off-by: Ben Luddy <[email protected]>
With its only remaining field being an embedded bytes.Buffer, there's no reason to retain the encoderBuffer type. Signed-off-by: Ben Luddy <[email protected]>
The application calling Marshal has more information about the object it wants to marshal than this library does (e.g. what type is it, what is the 95th-percentile serialized size of similar objects, etc.). It can use this information to increase the utilization of encode buffers beyond what is possible with a shared buffer pool for all calls to Marshal. To avoid the backwards-incompatible change of expanding the method set of the EncMode interface, invoking this method from an external package will require a type assertion, and there is currently no exported "optional interface" declaration to make that more convenient. Signed-off-by: Ben Luddy <[email protected]>
176bf87
to
045f722
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@benluddy Thanks for opening this PR! It addresses a valid concern of inefficient use of cached buffer when there are large size and frequency disparities. The solution in this PR is simple and non-intrusive. 👍
I would still rather see Marshal call MarshalToBuffer so that they both benefit from all of the existing test coverage, unless the benchmark diff is a concern.
The benchmark diff is a concern, and I think you are right that diff is caused by overhead of an extra function call.
I can add tests later to call MarshalToBuffer
explicitly to increase test coverage.
A new interface interface{ MarshalToBuffer(interface{}, *bytes.Buffer) error } could be declared for convenience. The interface might initially be documented as subject to change.
Yes, it would be great to create a new interface for the new function.
I can open a PR to add the new interface, along with tests I mentioned earlier. For the v2.7.0 release, some new functions or interfaces can be marked as "preview" and "subject to change".
Thanks again for your contribution!
Description
This is an implementation of the proposal in #520.
PR Was Proposed and Welcomed in Currently Open Issue
Checklist (for code PR only, ignore for docs PR)
Last line of each commit message should be in this format:
Signed-off-by: Firstname Lastname [email protected]
(see next section).
Certify the Developer's Certificate of Origin 1.1
the Developer Certificate of Origin 1.1.