-
Notifications
You must be signed in to change notification settings - Fork 653
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
Remove valkey specific changes in jemalloc source code #1266
base: unstable
Are you sure you want to change the base?
Remove valkey specific changes in jemalloc source code #1266
Conversation
This PR is replacing https://github.com/valkey-io/valkey/pull/692. It consists of 2 commits:
|
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## unstable #1266 +/- ##
============================================
+ Coverage 70.69% 70.73% +0.04%
============================================
Files 114 116 +2
Lines 63161 63239 +78
============================================
+ Hits 44650 44732 +82
+ Misses 18511 18507 -4
|
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.
Mostly minor comments, and all the tests are still passing which is good. I might take another pass after this to just cleanup some comments for clarity.
3443501
to
639fa6b
Compare
@madolson, I fixed the comments |
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.
It LGTM, only sticky point left is just some follows ups on the info fields. Would appreciate if one of @JimB123 or @zuiderkwast have time to take a look as well to review the jemalloc logic.
Signed-off-by: Zvi Schneider <[email protected]>
Signed-off-by: Zvi Schneider <[email protected]>
Signed-off-by: Zvi Schneider <[email protected]>
Co-authored-by: Madelyn Olson <[email protected]> Signed-off-by: zvi-code <[email protected]>
Co-authored-by: Madelyn Olson <[email protected]> Signed-off-by: zvi-code <[email protected]>
Co-authored-by: Madelyn Olson <[email protected]> Signed-off-by: zvi-code <[email protected]>
Co-authored-by: Madelyn Olson <[email protected]> Signed-off-by: zvi-code <[email protected]>
Signed-off-by: Zvi Schneider <[email protected]>
Signed-off-by: Zvi Schneider <[email protected]>
Signed-off-by: Zvi Schneider <[email protected]>
Signed-off-by: Zvi Schneider <[email protected]>
Signed-off-by: Zvi Schneider <[email protected]>
11d69e6
to
39a1f6d
Compare
Co-authored-by: Madelyn Olson <[email protected]> Signed-off-by: zvi-code <[email protected]>
Co-authored-by: Madelyn Olson <[email protected]> Signed-off-by: zvi-code <[email protected]>
@valkey-io/core-team this PR replaces previous PR due to git history issues i could not fix. Can we put the 8.1 target (and if it's important than also mark with |
… required) Signed-off-by: Zvi Schneider <[email protected]>
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.
Yes! This looks very close merging now. Just some nits and some questions.
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 like that we are eliminating a custom version of jemalloc. If we can upgrade jemalloc as a drop-in replacement, that's a win.
src/allocator_defrag.c
Outdated
|
||
return makeDefragDecision(&arena_bin_conf.bin_info[binind], | ||
&curr_usage[binind], | ||
arena_bin_conf.bin_info[binind].nregs - nfree); |
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.
If what I said above is correct, this doesn't make sense to me. Are we computing the total number of regions in the BIN less the number of free regions in the SLAB? I don't understand what that would represent, or why. Is this a bug? or do we need more comments here.
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.
No, please see documentation:
/* Represents detailed information about a jemalloc bin.
*
* This struct provides metadata about a jemalloc bin, including the size of
* its regions, total number of regions, and related MIB keys for efficient
* queries.
*/
typedef struct jeBinInfo {
unsigned long reg_size; /* Size of each region in the bin. */
unsigned long nregs; /* Total number of regions in the bin. */
jeBinInfoKeys info_keys; /* Precomputed MIB keys for querying bin statistics. */
} jeBinInfo;
arena_bin_conf.bin_info
holds metadata for each bin id (binind
), as sucharena_bin_conf.bin_info[binind].nregs
is the number of regions in each slab in this bin, it does not change after initial information is retrieved during init.
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.
The code above says that nregs
is the "total number of regions in the bin". You just said that it's the number of regions per slab. Which is it?
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.
The hole struct holds static once initialized information about the bin. As such obviously it's not the number of regs in the bin at specific moment, but how many regs are there in a slab of this bin. I can change the comment to clarify if it's not clear
Signed-off-by: Zvi Schneider <[email protected]>
@madolson , @JimB123 , @zuiderkwast , updated PR with fixes to the comments, please review |
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.
Official, stamp, will wait to see if others want to take another review pass before merging.
void allocatorDefragFree(void *ptr, size_t size); | ||
__attribute__((malloc)) void *allocatorDefragAlloc(size_t size); |
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 was just going to add that we have a few of the zmalloc functions can now optionally take the size to improve the performance. See
Line 439 in aa2dd3e
void zfree_with_size(void *ptr, size_t size) { |
Signed-off-by: Zvi Schneider <[email protected]>
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.
Looks good in general. I haven't reviewed all the details. I just have a few nits and some questions.
What exactly is the relationship between the units zmalloc
and allocator_defrag
? Both of them are allocator abstractions, but one doesn't depend on the other. They're on the same level, but they still have some dependencies to each other: Both have to be using the same allocator.
This structure seems fine to me. Perhaps we could add some comment in each of them to refer to the other file or something.
Application code
/ \
allocation / \ defrag
/ \
zmalloc allocator_defrag
/ | \ / \
/ | \ / \
/ | \ / \
libc tcmalloc jemalloc other
@zuiderkwast , I like your diagram, it is correct. We could have a non allocator defragmentation logic, for example think about lazy listpack\rax compaction, but i removed this abstraction to reduce the scope of the PR. How about this documentation along with the diagram? [I got some help]
|
Signed-off-by: Zvi Schneider <[email protected]>
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
if (jeQueryKeyInit("experimental.utilization.batch_query", &je_cb.util_batch_query) != 0) return -1; | ||
|
||
je_res = jeQueryKeyInit("epoch", &je_cb.epoch); | ||
assert(je_res == 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.
I don't think you understood my previous comment. In the first call, on 267, you return -1 if it fails. On the 2nd call, you assert. Why are there 2 different patterns for the same function call?
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.
The first case, could fail and does not o dictate bug by itself, because the jemalloc version does not have the query api, from this code perspective it's a valid case and -1 is returned to indicate that init didn't succeed. The following calls are expected to succeed and are asserted to verify. I moved the call out of the assert following your comment I believe.
*note: this PR replaced prior PR
Summary of the change
This is a base PR for refactoring defrag. It moves the defrag logic to rely on jemalloc native api instead of relying on custom code changes made by valkey in the jemalloc (je_defrag_hint) library. This enables valkey to use latest vanila jemalloc without the need to maintain code changes cross jemalloc versions.
This change requires some modifications because the new api is providing only the information, not a yes\no defrag. The logic needs to be implemented at valkey code. Additionally, the api does not provide, within single call, all the information needed to make a decision, this information is available through additional api call. To reduce the calls to jemalloc, in this PR the required information is collected during the
computeDefragCycles
and not for every single ptr, this way we are avoiding the additional api call.Followup work will utilize the new options that are now open and will further improve the defrag decision and process.
Added files:
allocator_defrag.c
/allocator_defrag.h
- This files implement the allocator specific knowledge for making defrag decision. The knowledge about slabs and allocation logic and so on, all goes into this file. This improves the separation between jemalloc specific code and other possible implementation.Moved functions:
zmalloc_no_tcache
,zfree_no_tcache
- these are very jemalloc specific logic assumptions, and are very specific to how we defrag with jemalloc. This is also with the vision that from performance perspective we should consider using tcache, we only need to make sure we don't recycle entries without going through the arena [for example: we can use private tcache, one for free and one for alloc].frag_smallbins_bytes
- the logic and implementation moved to the new fileExisting API:
computeDefragCycles
zmalloc_get_allocator_info
: gets from jemalloc allocated, active, resident, retained, muzzy,frag_smallbins_bytes
frag_smallbins_bytes
: for each bin; gets from jemalloc bin_info,curr_regs
,cur_slabs
je_defrag_hint
is getting a memory pointer and returns {0,1} . Internally it uses this information points:nonfull_slabs
total_slabs
Jemalloc API (via ctl interface)
[BATCH]
experimental_utilization_batch_query_ctl
: gets an array of pointers, returns for each pointer 3 values,[EXTENDED]
experimental_utilization_query_ctl
:experimental_utilization_batch_query_ctl
vs valkeyje_defrag_hint
?[good]
[bad]
nonfull_slabs
and total allocated regs.New functions:
defrag_jemalloc_init
: Reducing the cost of call to je_ctl: use the MIB interface to get a faster calls. See this quote from the jemalloc documentation:The mallctlnametomib() function provides a way to avoid repeated name lookups for
applications that repeatedly query the same portion of the namespace,by translating
a name to a “Management Information Base” (MIB) that can be passed repeatedly to
mallctlbymib().
jemalloc_sz2binind_lgq*
: this api is to support reverse map between bin size and it’s info without lookup. This mapping depends on the number of size classes we have that are derived fromlg_quantum
defrag_jemalloc_get_frag_smallbins
: This function replacesfrag_smallbins_bytes
the logic moved to the new file allocator_defragdefrag_jemalloc_should_defrag_multi
→handle_results
- unpacks the resultsshould_defrag
: implements the same logic as the existing implementation inside je_defrag_hintdefrag_jemalloc_should_defrag_multi
: implements the hint for an array of pointers, utilizing the new batch api. currently only 1 pointer is passed.Logical differences:
In order to get the information about #
nonfull_slabs
and #regs
, we use the query cycle to collect the information per size class. In order to find the index of bin information given bin size, in o(1), we usejemalloc_sz2binind_lgq*
.Testing
This is the first draft. I did some initial testing that basically fragmentation by reducing max memory and than waiting for defrag to reach desired level. The test only serves as sanity that defrag is succeeding eventually, no data provided here regarding efficiency and performance.
Test:
activedefrag
used_memory
reaches 10GBmaxmemory
to 5GB andmaxmemory-policy
toallkeys-lru
mem_fragmentation_ratio
to reach 2activedefrag
- start test timermem_fragmentation_ratio
= 1.1Results*:
(With this PR)Test results:
56 sec
(Without this PR)Test results:
67 sec
*both runs perform same "work" number of buffers moved to reach fragmentation target
Next benchmarking is to compare to:
je_get_defrag_hint
int defrag_hint() {return 1;}