Skip to content

Use the extra bits in the flags for len/alloc.#7904

Closed
hbina wants to merge 4 commits intoredis:unstablefrom
hbina:push_sdshdr_to_the_limit
Closed

Use the extra bits in the flags for len/alloc.#7904
hbina wants to merge 4 commits intoredis:unstablefrom
hbina:push_sdshdr_to_the_limit

Conversation

@hbina
Copy link
Contributor

@hbina hbina commented Oct 13, 2020

Implementation notes:

  1. For sds(len/setlen/etc) methods, we technically do not need to mask the for the extra bits whenever we
    we want to shift the given value because the caller of all of these functions should have guaranteed that
    the given size_t does not exceed what the current SDS_TYPE_* can handle.

  2. I think this is all that needs to be done. There might be edge cases where code is accessing the values directly?

Edit: Getting connection refused error for tests. Is that me? :V

Signed-off-by: Hanif Bin Ariffin [email protected]

@hbina hbina force-pushed the push_sdshdr_to_the_limit branch 2 times, most recently from f66e1eb to e7812a2 Compare October 13, 2020 00:45
@hbina hbina marked this pull request as ready for review October 13, 2020 00:50
hbina added 4 commits October 13, 2020 13:14
Implementation notes:

1. For sds(len/setlen/etc) methods, we technically do not need to mask the for the extra bits whenever we
we want to shift the given value because the caller of all of these functions should have guaranteed that
the given `size_t` does not exceed what the current SDS_TYPE_* can handle.

2. I think this is all that needs to be done. There might be edge cases where code is accessing the values directly?

Signed-off-by: Hanif Bin Ariffin <[email protected]>
@hbina hbina force-pushed the push_sdshdr_to_the_limit branch from 95b35e6 to cdcbbd6 Compare October 13, 2020 05:43
@oranagra
Copy link
Member

oranagra commented Oct 13, 2020

@hbina i know certain specific string sizes which once had to use 16 bit header sds, will now fit into 8 bit header sds. but i'm not sure the extra complexity in the code justifies that improvement.
what's more, i also think there's a chance for performance degradation due to two things: one is the extra operations that are done (shift and or), and the other is the restructure of the into separate blocks to handle each field.

let's consider the memory saving in this case (most significant one):
some strings that are over 255 bytes length which was in the past forced to use 16 bit sds header, will not be able to fit into the 8 bit sds header.
in the most extreme case, you're saving 2 bytes of memory out of a string that's anyway longer than 255 bytes.
truth be told, in some cases this 2 bytes reduction can cause the allocation to use a smaller bin size from the allocator (reducing some 30% of internal fragmentation too), but all in all i don't think the complexity and possible performance hit is worth it.

for the bigger sizes it's even less rewarding (saving 4 bytes out of 65k string)

@hbina hbina marked this pull request as draft October 13, 2020 06:08
@hbina
Copy link
Contributor Author

hbina commented Oct 13, 2020

@oranagra thanks for looking at this! I am new here >.<

some strings that are over 255 bytes length which was in the past forced to use 16 bit sds header, will not be able to fit into the 8 bit sds header.
in the most extreme case, you're saving 2 bytes of memory out of a string that's anyway longer than 255 bytes.

I think (from reading above) you are misunderstanding what my change will do?
This basically changes from using 8/16/32-bits for len/alloc to using 10/18/34-bits.
Meaning that 255 bytes length will definitely fit into this.
In fact, "SDS_TYPE_8" can now store up to 1023 worth of characters.

Notice that I shift the additional bits in the flags to make it a power of (9, 10), (17, 18), and (33, 34) approximately.

what's more, i also think there's a chance for performance degradation due to two things: one is the extra operations that are done (shift and or), and the other is the restructure of the into separate blocks to handle each field.

I think the implementation can be made simpler by simply rethinking what bits are used for len/alloc.
Why didn't I think of this earlier...god

--------------------------------------------------------------------------------------------------
| 8 -bits of len | 8 -bits of alloc |  5-bit unused | 3-bits of flag | ....
--------------------------------------------------------------------------------------------------

to

--------------------------------------------------------------------------------------------------
| 10 -bits of len | 10 -bits of alloc | 1-bit unused | 3-bits of flag | ....
--------------------------------------------------------------------------------------------------

As long its contained within this file and all access be done through helper functions...it should be fine.

@oranagra
Copy link
Member

@hbina i may be missing your intent indeed (didn't carefully read the changes).
but looking at your response i actually don't think i missed it.

this PR is about memory saving, right?
what is the smallest string that would be affected by your change? and how many bytes will it save?
if i'm not mistaken, the smallest string that will be affected is 256 bytes, and you'll save 2 bytes.
this 2 bytes saving will hold for strings up to 1023 bytes, and anything above it can largely be ignored (saving 4 bytes for string over 16kb is not a real concern).

if i'm still missing your point please correct me.

@hbina
Copy link
Contributor Author

hbina commented Oct 13, 2020

@oranagra Oh okay, yes, I understand now. What you are claiming is that my changes will have diminishing returns in memory saving as the string gets bigger. Yes, I agree with that. But there's also other benefits from increasing the range of SDS because of this

redis/src/sds.c

Lines 249 to 263 in aaacb8c

if (oldtype==type) {
newsh = s_realloc_usable(sh, hdrlen+newlen+1, &usable);
if (newsh == NULL) return NULL;
s = (char*)newsh+hdrlen;
} else {
/* Since the header size changes, need to move the string forward,
* and can't use realloc */
newsh = s_malloc_usable(hdrlen+newlen+1, &usable);
if (newsh == NULL) return NULL;
memcpy((char*)newsh+hdrlen, s, len+1);
s_free(sh);
s = (char*)newsh+hdrlen;
s[-1] = type;
sdssetlen(s, len);
}

While are are at it, and I don't want to open an issue just for this, what do you think about

redis/src/sds.c

Line 979 in aaacb8c

sds *sdssplitargs(const char *line, int *argc) {

eagerly creating the strings instead of lazily doing so? I think it could be improved to do basically 0 wasted allocations.

@oranagra
Copy link
Member

@hbina regarding this memory optimization, it's not only that it's diminishing as strings get bigger, even on the smallest string that it affects, the benefit is not that big (considering the size of the string). this is what we avoided going that way in the past.

regarding size class conversion in sdsMakeRoomFor and also in sdsRemoveFreeSpace, i think this is an edge case that we don't want to consider.
if we do want to go that way, we may even want to just avoid changing size class in certain cases when not necessary (like keep using sds16 when shrinking a string from 260 to 240, or pre-allocating an sds16 when the string is 240 since it might soon be expanded).

note that the above waste of bytes in order to reduce the need for size class conversion goes against the main topic of this PR (to reduce space).

if we are worried about that conversion, the most important thing to do is completely drop the use of sds16 and sds32, and always use sds64, since a conversion of a big string is a lot more painful, and the waste of 16 bytes is not is not that bad.
however, keeping in mind that even when not changing a size class, and doing a simple realloc within the same size class, can result in a memory copy (in case the allocator decided to change bin), and in other cases, can be a NOP.

@hbina
Copy link
Contributor Author

hbina commented Oct 13, 2020

Sounds reasonable. Thanks for looking into this regardless :)

@hbina hbina closed this Oct 13, 2020
alonre24 added a commit to alonre24/redis that referenced this pull request Jan 26, 2026
**Bug Fixes:**

* [redis#7219](RediSearch/RediSearch#7219) Fix a concurrency issue on Reducer in `FT.AGGREGATE`
* [redis#7255](RediSearch/RediSearch#7255) Fix `BM25STD` underflow wraparound
* [redis#7275](RediSearch/RediSearch#7275) Report used memory as `unsigned long long` to avoid underflows
* [redis#7264](RediSearch/RediSearch#7264) Fix `totalDocsLen` updates
* [redis#6995](RediSearch/RediSearch#6995) Do not fanout `FT.INFO` to replicas
* [redis#7350](RediSearch/RediSearch#7350) Fix `FT.CREATE` failure with LeanVec parameters on non-Intel architectures
* [redis#7384](RediSearch/RediSearch#7384) Fix index load from RDB temporary memory overhead
* [redis#7459](RediSearch/RediSearch#7459) Fix Fork GC potential double-free on error path
* [redis#7458](RediSearch/RediSearch#7458) Fix a GC performence regression
* [redis#7470](RediSearch/RediSearch#7470) Avoid draining worker thread pool from FLUSH callback to avoid deadlocks
* [redis#7554](RediSearch/RediSearch#7554) Handle the case where `SCORE` is sent alone without extra fields (coordinator)
* [redis#7685](RediSearch/RediSearch#7685) Fix cursor logical leak
* [redis#7794](RediSearch/RediSearch#7794) Fix `cmp_strings()` to correctly handle binary data with embedded NULLs in TOLIST reducer in FT.AGGREGATE
* [redis#7873](RediSearch/RediSearch#7873) Handle warnings in empty `FT.AGGREGATE` replies (cluster)
* [redis#7886](RediSearch/RediSearch#7886) Remove non-TEXT fields from the spec keys dictionary
* [redis#7904](RediSearch/RediSearch#7904) Refactor keys dictionary handling
* [redis#7901](RediSearch/RediSearch#7901) Support multiple warnings in reply
* [redis#8083](RediSearch/RediSearch#8083) Fix incorrect FULLTEXT field metric counts
* [redis#8153](RediSearch/RediSearch#8153) Fix configuration registration issues

**Improvements:**

* [redis#7154](RediSearch/RediSearch#7154) `FT.AGGREGATE` can return Background Indexing OOM warnings
* [redis#7083](RediSearch/RediSearch#7083) Add the default text scorer as a configuration option
* [redis#7341](RediSearch/RediSearch#7341) Rename `FT.PROFILE` counter fields
* [redis#7436](RediSearch/RediSearch#7436) Enhance `FT.PROFILE` with vector search execution details
* [redis#7435](RediSearch/RediSearch#7435) Ensure full `FT.PROFILE` output on timeout with RETURN policy
* [redis#7534](RediSearch/RediSearch#7534) Reduce the number of worker threads asynchronously to avoid deadlocks during queries
* [redis#7614](RediSearch/RediSearch#7614) Track timeout warnings and errors in INFO
* [redis#7646](RediSearch/RediSearch#7646) Track `maxprefixexpansions` warnings and errors in INFO
* [redis#7577](RediSearch/RediSearch#7577) Track query syntax/argument errors (basis for query error tracking)
* [redis#7737](RediSearch/RediSearch#7737) Add `Internal cursor reads` metric to cluster `FT.PROFILE` output
* [redis#7759](RediSearch/RediSearch#7759) Extend indexing metrics
* [redis#7710](RediSearch/RediSearch#7710) Support `WITHCOUNT` keyword in `FT.AGGREGATE`
* [redis#7957](RediSearch/RediSearch#7957) Persist query warnings across cursor reads
* [redis#8054](RediSearch/RediSearch#8054) Add logging for index-related commands
* [redis#8151](RediSearch/RediSearch#8151) Fix shard total profile time reporting in `FT.PROFILE`
* [redis#8103](RediSearch/RediSearch#8103) Output current thread IndexSpec information on crash
YaacovHazan pushed a commit that referenced this pull request Jan 26, 2026
**Bug Fixes:**

* [#7219](RediSearch/RediSearch#7219) Fix a
concurrency issue on Reducer in `FT.AGGREGATE`
* [#7255](RediSearch/RediSearch#7255) Fix
`BM25STD` underflow wraparound
* [#7275](RediSearch/RediSearch#7275) Report
used memory as `unsigned long long` to avoid underflows
* [#7264](RediSearch/RediSearch#7264) Fix
`totalDocsLen` updates
* [#6995](RediSearch/RediSearch#6995) Do not
fanout `FT.INFO` to replicas
* [#7350](RediSearch/RediSearch#7350) Fix
`FT.CREATE` failure with LeanVec parameters on non-Intel architectures
* [#7694](RediSearch/RediSearch#7694) Use
asynchronous jobs for GC in SVS to accelerate execution
* [#7384](RediSearch/RediSearch#7384) Fix index
load from RDB temporary memory overhead
* [#7459](RediSearch/RediSearch#7459) Fix Fork
GC potential double-free on error path
* [#7458](RediSearch/RediSearch#7458) Fix a GC
performence regression
* [#7470](RediSearch/RediSearch#7470) Avoid
draining worker thread pool from FLUSH callback to avoid deadlocks
* [#7554](RediSearch/RediSearch#7554) Handle the
case where `SCORE` is sent alone without extra fields (coordinator)
* [#7685](RediSearch/RediSearch#7685) Fix cursor
logical leak
* [#7794](RediSearch/RediSearch#7794) Fix
`cmp_strings()` to correctly handle binary data with embedded NULLs in
TOLIST reducer in FT.AGGREGATE
* [#7873](RediSearch/RediSearch#7873) Handle
warnings in empty `FT.AGGREGATE` replies (cluster)
* [#7886](RediSearch/RediSearch#7886) Remove
non-TEXT fields from the spec keys dictionary
* [#7904](RediSearch/RediSearch#7904) Refactor
keys dictionary handling
* [#7901](RediSearch/RediSearch#7901) Support
multiple warnings in reply
* [#8083](RediSearch/RediSearch#8083) Fix
incorrect FULLTEXT field metric counts
* [#8153](RediSearch/RediSearch#8153) Fix
configuration registration issues

**Improvements:**

* [#7154](RediSearch/RediSearch#7154)
`FT.AGGREGATE` can return Background Indexing OOM warnings
* [#7083](RediSearch/RediSearch#7083) Add the
default text scorer as a configuration option
* [#7341](RediSearch/RediSearch#7341) Rename
`FT.PROFILE` counter fields
* [#7436](RediSearch/RediSearch#7436) Enhance
`FT.PROFILE` with vector search execution details
* [#7435](RediSearch/RediSearch#7435) Ensure
full `FT.PROFILE` output on timeout with RETURN policy
* [#7534](RediSearch/RediSearch#7534) Reduce the
number of worker threads asynchronously to avoid deadlocks during
queries
* [#7614](RediSearch/RediSearch#7614) Track
timeout warnings and errors in INFO
* [#7646](RediSearch/RediSearch#7646) Track
`maxprefixexpansions` warnings and errors in INFO
* [#7577](RediSearch/RediSearch#7577) Track
query syntax/argument errors (basis for query error tracking)
* [#7737](RediSearch/RediSearch#7737) Add
`Internal cursor reads` metric to cluster `FT.PROFILE` output
* [#7759](RediSearch/RediSearch#7759) Extend
indexing metrics
* [#7710](RediSearch/RediSearch#7710) Support
`WITHCOUNT` keyword in `FT.AGGREGATE`
* [#7957](RediSearch/RediSearch#7957) Persist
query warnings across cursor reads
* [#8054](RediSearch/RediSearch#8054) Add
logging for index-related commands
* [#8151](RediSearch/RediSearch#8151) Fix shard
total profile time reporting in `FT.PROFILE`
* [#8103](RediSearch/RediSearch#8103) Output
current thread IndexSpec information on crash
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.

2 participants