-
Notifications
You must be signed in to change notification settings - Fork 916
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 deep equal check failure in CreateOrUpdateWork(), by replace the marshaler #5939
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
❗ Your organization needs to install the Codecov GitHub app to enable full functionality. Additional details and impacted files@@ Coverage Diff @@
## master #5939 +/- ##
=======================================
Coverage 48.37% 48.37%
=======================================
Files 666 666
Lines 54831 54831
=======================================
Hits 26524 26524
Misses 26590 26590
Partials 1717 1717
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Hi @zach593, I am trying to use this issue to design a performance monitoring case, my case is as follows:
Do you think there are better optimization opportunities for my case? |
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.
LGTM, the current pr resolves the deepcopy failure issue and dose not introduce new performance risks.
About the kind/bug
label. do you expect to sync the current pr to the previous release versions? If we don't need to, maybe we can modify the label.
@chaosi-zju As we discussed in zoom, for the performance optimization, this PR is not enough, I may need another 2 PRs to complete this goal. Since I need more than one PR, I created #6017 to track it, and we can have further discussion in that issue. |
Sure, can you help with that? I'm not familiar with commands with |
BTW, I think we could consider use |
/remove-kind bug |
ok, thanks, I think this PR can move forward first /lgtm |
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.
/assign
I will take a look this week.
pkg/util/helper/work.go
Outdated
@@ -54,7 +55,7 @@ func CreateOrUpdateWork(ctx context.Context, client client.Client, workMeta meta | |||
} | |||
} | |||
|
|||
workloadJSON, err := resource.MarshalJSON() | |||
workloadJSON, err := json.Marshal(resource) |
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.
Is there any difference between encoding/json.Marshal
and unstructured.MarshalJSON
other than the /n
?
I am mostly curious about functionality and performance.
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.
In addition, I think we need to check all of the places where using the unstructured.MarshalJSON
.
PS: I don't mean to check and fix them all in this PR. Just remind me.
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.
Tracked this on #6031, please correct me if anyone feels this is unnecessary.
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.
Is there any difference between
encoding/json.Marshal
andunstructured.MarshalJSON
other than the/n
?
I am mostly curious about functionality and performance.
I did a benchmark test to compare different json marshal function, my test code is as:
benchmark test code
func BenchmarkMarshalJSON(b *testing.B) {
resource := &unstructured.Unstructured{
Object: map[string]interface{}{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": map[string]interface{}{
"name": "test-deployment",
"namespace": "default",
},
"spec": map[string]interface{}{
"replicas": 3,
"selector": map[string]interface{}{
"matchLabels": map[string]interface{}{
"app": "test",
},
},
"template": map[string]interface{}{
"metadata": map[string]interface{}{
"labels": map[string]interface{}{
"app": "test",
},
},
"spec": map[string]interface{}{
"containers": []interface{}{
map[string]interface{}{
"name": "test-container",
"image": "nginx:latest",
},
},
},
},
},
},
}
b.Run("resource.MarshalJSON", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_, _ = resource.MarshalJSON()
}
})
b.Run("json.Marshal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
// "encoding/json"
_, _ = json.Marshal(resource)
}
})
b.Run("jsoniter.Marshal", func(b *testing.B) {
for i := 0; i < b.N; i++ {
// jsoniter "github.com/json-iterator/go"
_, _ = jsoniter.Marshal(resource)
}
})
}
the result is:
goos: windows
goarch: amd64
pkg: github.com/karmada-io/karmada/pkg/controllers/ctrlutil
cpu: Intel(R) Core(TM) i5-14600KF
BenchmarkMarshalJSON
BenchmarkMarshalJSON/resource.MarshalJSON
BenchmarkMarshalJSON/resource.MarshalJSON-20 398614 2835 ns/op
BenchmarkMarshalJSON/json.Marshal
BenchmarkMarshalJSON/json.Marshal-20 307854 4004 ns/op
BenchmarkMarshalJSON/jsoniter.Marshal
BenchmarkMarshalJSON/jsoniter.Marshal-20 393528 3019 ns/op
PASS
and
goos: windows
goarch: amd64
pkg: github.com/karmada-io/karmada/pkg/util/helper
cpu: Intel(R) Xeon(R) Gold 6278C CPU @ 2.60GHz
BenchmarkMarshalJSON
BenchmarkMarshalJSON/resource.MarshalJSON
BenchmarkMarshalJSON/resource.MarshalJSON-4 95715 13065 ns/op
BenchmarkMarshalJSON/json.Marshal
BenchmarkMarshalJSON/json.Marshal-4 68590 16391 ns/op
BenchmarkMarshalJSON/jsoniter.Marshal
BenchmarkMarshalJSON/jsoniter.Marshal-4 79052 13281 ns/op
PASS
seems previous unstructured.MarshalJSON
perform better, while encoding/json.Marshal
worst
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.
resource.MarshalJSON()
is specifically optimized for the unstructured.Unstructured
type, potentially offering significant performance improvements when handling large numbers of Kubernetes resources.
besides, Kubernetes resources may contain fields that require special treatment during serialization, which can be handled by the custom method, for example:
- Timestamp fields like
creationTimestamp
may need to be formatted in a particular way. - Some fields might need to be omitted entirely when they're empty, rather than being serialized as null.
- Certain fields may require custom serialization rules to maintain backward compatibility with older API versions.
- Resource-specific fields (like status in various resources) might need special treatment during serialization.
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.
Is there any other option we can resolve this issue other than replace this function?
what about using json.Compact to remove redundant newlines.
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.
Doubt it. Can you help test it?
I'm afraid the json.Compact
also removes the space characters from the body, which probably makes the deep copy always return false.
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.
I'm afraid the
json.Compact
also removes the space characters from the body, which probably makes the deep copy always return false.
I did the test as follows:
workloadJSON, err := resource.MarshalJSON()
workloadJSON2, err := json.Marshal(resource)
buffer := new(bytes.Buffer)
if err = json.Compact(buffer, workloadJSON); err != nil {}
workloadJSON3 := buffer.Bytes()
klog.Infof("diff workloadJSON VS workloadJSON2: %+v", cmp.Diff(workloadJSON, workloadJSON2))
klog.Infof("diff workloadJSON VS workloadJSON3: %+v", cmp.Diff(workloadJSON, workloadJSON3))
klog.Infof("diff workloadJSON2 VS workloadJSON3: %+v", cmp.Diff(workloadJSON2, workloadJSON3))
the result is: workloadJSON2
and workloadJSON3
is equal, and workloadJSON
is only one more \n
that the other two.
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.
https://github.com/golang/go/blob/go1.22.9/src/encoding/json/encode.go#L430
func marshalerEncoder(e *encodeState, v reflect.Value, opts encOpts) {
if v.Kind() == reflect.Pointer && v.IsNil() {
e.WriteString("null")
return
}
m, ok := v.Interface().(Marshaler)
if !ok {
e.WriteString("null")
return
}
b, err := m.MarshalJSON()
if err == nil {
e.Grow(len(b))
out := e.AvailableBuffer()
out, err = appendCompact(out, b, opts.escapeHTML)
e.Buffer.Write(out)
}
if err != nil {
e.error(&MarshalerError{v.Type(), err, "MarshalJSON"})
}
}
https://github.com/golang/go/blob/go1.22.9/src/encoding/json/indent.go#L39
// Compact appends to dst the JSON-encoded src with
// insignificant space characters elided.
func Compact(dst *bytes.Buffer, src []byte) error {
dst.Grow(len(src))
b := dst.AvailableBuffer()
b, err := appendCompact(b, src, false)
dst.Write(b)
return err
}
json.Marshal()
also does the same thing as json.Compact()
. As I understand it, this is the only real difference between json.Marshal()
and unstructured.MarshalJSON()
, aside from some behaviors that might affect performance.
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.
json.Marshal()
also does the same thing asjson.Compact()
. As I understand it, this is the only real difference betweenjson.Marshal()
andunstructured.MarshalJSON()
, aside from some behaviors that might affect performance.
You're great 👍
So, simply understand, m.MarshalJSON()
is already overwrited, and json.Marshal()
= m.MarshalJSON()
+ json.Compact
…marshaler Signed-off-by: zach593 <[email protected]>
afc1a3c
to
ce1ca99
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.
/lgtm
/approve
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: RainbowMango The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
What type of PR is this?
/kind bug
What this PR does / why we need it:
see #5938
Which issue(s) this PR fixes:
Fixes #5938
Special notes for your reviewer:
Does this PR introduce a user-facing change?: