-
Notifications
You must be signed in to change notification settings - Fork 0
Account for shared strings proportionally in JSON memory usage #22
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -351,14 +351,17 @@ impl IValue { | |
| } | ||
| } | ||
|
|
||
| /// Reports dynamic memory allocated by this value. | ||
| pub fn mem_allocated(&self) -> usize { | ||
| /// Reports proportional dynamic memory in bytes, excluding this value itself. | ||
| /// Shared strings are divided among all live references (including temporary | ||
| /// owners). Container capacity is fully counted; the intern table is excluded. | ||
| /// Retain fractional bytes until rounding the final measurement. | ||
| pub fn mem_allocated(&self) -> f64 { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just verify the callers/client of
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Verified. RedisJSON rounds the final measurement and converts it to an integer. JSON.DEBUG MEMORY still returns an integer (or an array of integers for JSONPath), and MEMORY USAGE also remains integer-valued. Only the accounting changes, not the command response types. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But can it be changed to return float? If not, I wouldn't return float here wither
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess changing it to float is indeed breaking changed we dont want to do , However , There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But anyway you say we will round the result. Need to check if we can really return float(I guess we can, redis command api usually dont difference between float and integer AFAIR)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. assume we have 1000 diffrent strings with each 3 ref count There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I prefer to be exact if we can, so let's first understand if there is any issue at all |
||
| use ValueType::*; | ||
| match self.type_() { | ||
| // inline types consume no extra memory | ||
| Null | Bool => 0, | ||
| Null | Bool => 0.0, | ||
| // Safety: We checked the type | ||
| Number => unsafe { self.as_number_unchecked() }.mem_allocated(), | ||
| Number => unsafe { self.as_number_unchecked() }.mem_allocated() as f64, | ||
| String => unsafe { self.as_string_unchecked() }.mem_allocated(), | ||
| Array => unsafe { self.as_array_unchecked() }.mem_allocated(), | ||
| Object => unsafe { self.as_object_unchecked() }.mem_allocated(), | ||
|
|
@@ -1091,7 +1094,7 @@ mod tests { | |
| assert!(matches!(x.clone().destructure(), Destructured::Null)); | ||
| assert!(matches!(x.clone().destructure_ref(), DestructuredRef::Null)); | ||
| assert!(matches!(x.clone().destructure_mut(), DestructuredMut::Null)); | ||
| assert_eq!(x.mem_allocated(), 0); | ||
| assert_eq!(x.mem_allocated(), 0.0); | ||
| } | ||
|
|
||
| #[test] | ||
|
|
@@ -1112,7 +1115,7 @@ mod tests { | |
| } | ||
|
|
||
| assert_eq!(x.to_bool(), Some(!v)); | ||
| assert_eq!(x.mem_allocated(), 0); | ||
| assert_eq!(x.mem_allocated(), 0.0); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -1147,9 +1150,9 @@ mod tests { | |
| assert_eq!( | ||
| x.mem_allocated(), | ||
| if (INLINE_LOWER..=INLINE_UPPER).contains(&v) { | ||
| 0 | ||
| 0.0 | ||
| } else { | ||
| mem::size_of::<i64>() | ||
| mem::size_of::<i64>() as f64 | ||
| } | ||
| ); | ||
| } | ||
|
|
@@ -1181,9 +1184,9 @@ mod tests { | |
| assert_eq!( | ||
| x.mem_allocated(), | ||
| if v <= i64::MAX as u64 && (INLINE_LOWER..=INLINE_UPPER).contains(&(v as i64)) { | ||
| 0 | ||
| 0.0 | ||
| } else { | ||
| mem::size_of::<u64>() | ||
| mem::size_of::<u64>() as f64 | ||
| } | ||
| ); | ||
| } | ||
|
|
@@ -1207,7 +1210,7 @@ mod tests { | |
| assert!( | ||
| matches!(x.clone().destructure_mut(), DestructuredMut::Number(u) if *u == INumber::try_from(v).unwrap()) | ||
| ); | ||
| assert_eq!(x.mem_allocated(), mem::size_of::<f64>()); | ||
| assert_eq!(x.mem_allocated(), mem::size_of::<f64>() as f64); | ||
| } | ||
| } | ||
| } | ||
|
|
@@ -1224,7 +1227,7 @@ mod tests { | |
| assert!(matches!(x.clone().destructure(), Destructured::String(u) if u == s)); | ||
| assert!(matches!(x.clone().destructure_ref(), DestructuredRef::String(u) if *u == s)); | ||
| assert!(matches!(x.clone().destructure_mut(), DestructuredMut::String(u) if *u == s)); | ||
| assert_eq!(x.mem_allocated(), 0); | ||
| assert_eq!(x.mem_allocated(), 0.0); | ||
| } | ||
|
|
||
| let s = String::from("foofoofoo"); | ||
|
|
@@ -1236,7 +1239,7 @@ mod tests { | |
| assert!(matches!(x.clone().destructure(), Destructured::String(u) if u == s)); | ||
| assert!(matches!(x.clone().destructure_ref(), DestructuredRef::String(u) if *u == s)); | ||
| assert!(matches!(x.clone().destructure_mut(), DestructuredMut::String(u) if *u == s)); | ||
| assert_eq!(x.mem_allocated(), 24); | ||
| assert_eq!(x.mem_allocated(), 24.0); | ||
| } | ||
|
|
||
| #[mockalloc::test] | ||
|
|
@@ -1253,8 +1256,9 @@ mod tests { | |
| assert!(matches!(x.clone().destructure_mut(), DestructuredMut::Array(u) if *u == a)); | ||
| assert_eq!( | ||
| x.mem_allocated(), | ||
| mem::size_of::<usize>() | ||
| + ((a.capacity() as usize * mem::size_of::<i32>() + 7) & !7) | ||
| (mem::size_of::<usize>() | ||
| + ((a.capacity() as usize * mem::size_of::<i32>() + 7) & !7)) | ||
| as f64 | ||
| ); | ||
| } | ||
| } | ||
|
|
@@ -1286,8 +1290,8 @@ mod tests { | |
| x.mem_allocated(), | ||
| o.iter() | ||
| .map(|(k, v)| k.mem_allocated() + v.mem_allocated()) | ||
| .sum::<usize>() | ||
| + ((raw + 7) & !7) | ||
| .sum::<f64>() | ||
| + ((raw + 7) & !7) as f64 | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| use ijson::IValue; | ||
|
|
||
| #[test] | ||
| fn proportional_memory_across_documents() { | ||
| let text = "proportional allocation accounting test string"; | ||
| let first = IValue::from(text); | ||
| let allocation = first.mem_allocated(); | ||
| let mut docs: Vec<IValue> = (0..99).map(|_| IValue::from(text)).collect(); | ||
| docs.push(first); | ||
| assert!(docs[0].mem_allocated() < 1.0); | ||
| assert!((docs.iter().map(IValue::mem_allocated).sum::<f64>() - allocation).abs() < 1e-9); | ||
| let one = docs.pop().unwrap(); | ||
| drop(docs); | ||
| assert_eq!(one.mem_allocated(), allocation); | ||
| drop(one); | ||
|
|
||
| // A field name and nested values share one allocation across documents. | ||
| let json = format!(r#"{{"{text}":["{text}","{text}"]}}"#); | ||
| let doc: IValue = serde_json::from_str(&json).unwrap(); | ||
| let original = doc.mem_allocated(); | ||
| let other = IValue::from(text); | ||
| assert!((doc.mem_allocated() + other.mem_allocated() - original).abs() < 1e-9); | ||
| assert!((other.mem_allocated() - allocation / 4.0).abs() < 1e-9); | ||
| drop(other); | ||
| assert!((doc.mem_allocated() - original).abs() < 1e-9); | ||
|
|
||
| for json in ["null", "true", "[]", "{}", r#""short""#] { | ||
| let value: IValue = serde_json::from_str(json).unwrap(); | ||
| assert_eq!(value.mem_allocated(), 0.0); | ||
| } | ||
| } |
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.
Please write a
SAFETYcomment on why we can deliberately divide by thercvalueThere 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.
done