-
Notifications
You must be signed in to change notification settings - Fork 511
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
Improvements in command handling #5 #631
base: main
Are you sure you want to change the base?
Conversation
*(long*)(pcurr) = bOffset; pcurr += sizeof(long); | ||
*pcurr = bSetVal; | ||
#endregion | ||
var input = new RawStringInput |
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 seems this pattern is repeated across commands. Does it make sense to do it right after parseState instead of at the time of command execution?
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.
a. When you're setting parseState you don't know the command yet
b. parseStateStartIdx is 1 only when it's a single-key command, so it really depends on the command syntax.
c. What we can do is have a cleaner constructor for RawStringInput (as well as ObjectInput).
if (parseState.Count > 4) | ||
{ | ||
var sbOffsetType = parseState.GetArgSliceByRef(4).ReadOnlySpan; | ||
bitOffsetType = sbOffsetType.EqualsUpperCaseSpanIgnoringCase("BIT"u8) ? (byte)0x1 : (byte)0x0; | ||
if (!sbOffsetType.EqualsUpperCaseSpanIgnoringCase("BIT"u8) && |
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.
How difficult is to propagate the result of this parsing to RMWMethods? It seems we perform this comparison twice which is not efficient.
Maybe we can have an array int args for those commands that require translation of string to categorical arguments in order to pass them in backend functions and avoid another translation/comparison.
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.
True, we are parsing twice, once for validation and once for execution. I think that's part of the price for simplifying the code and making it more readable... @badrishc any ideas 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.
Why can this not be passed as a parameter in input, similar to arg1 etc. and if the backend does not find it (not sure why, maybe because of GarnetAPI code path?), it can reparse it from the parse state.
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.
See comments. In general, pls check for all cases where we create an array of structs, such as ArgSlice[] or SpanByte[] -- these are allocations that need to be avoided if possible, by moving them to session-level fields that are reused across calls into Garnet.
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.
Reviewed to BitmapCommands.cs
/// Get header as Span | ||
/// </summary> | ||
/// <returns>Span</returns> | ||
public unsafe Span<byte> AsSpan() => new(ToPointer(), 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.
Why would we need this as a Span? The only place I see this used is in Length below, which I think could just return Size--and is only used by SpanByte method, so maybe can just be consolidated into that
var serializedLength = header.SpanByte.TotalSize | ||
+ (3 * sizeof(int)) // Length + arg1 + arg2 | ||
+ parseState.GetSerializedLength(parseStateStartIdx); | ||
|
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.
why the local var? Can just be an expression =>
|
||
/// <inheritdoc /> | ||
public unsafe void CopyTo(byte* dest) | ||
{ |
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.
We should pass in the dest len and verify. I know you've verified the length at the callsite, but even so, we are essentially doing C/C++ buffer-copy and should follow the security guidelines for those by doing bounds-checking, just as earlier secure-code initiatives replaced memcpy with memcpy_s
// 1. Header | ||
((RespInputHeader*)pcurr)->SetHeader(RespCommandAccessor.MIGRATE, 0); | ||
var input = new RawStringInput(); | ||
input.header.SetHeader(RespCommandAccessor.MIGRATE, 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.
Can this pass the RespCommand as a ctor arg? The more we guarantee correct initialization, the better
var parseSuccessful = TryReadLongSafe(ref ptr, end, out value, out bytesRead, out var signRead, | ||
out var overflow, allowLeadingZeros); | ||
|
||
if (parseSuccessful) return true; |
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.
better readability to have return on its own line. In fact parseSuccessful is not needed; just return true if TryReadLongSafe. and only the second of the "return false" lines below is needed
if (parseStateCount > 0) | ||
{ | ||
parseState.Initialize(parseStateCount); | ||
|
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 had to go look to figure out what "count" was here. Better to name it (and the ctor arg) "argCount"
var curr = ptr + sizeof(AofHeader); | ||
ref var key = ref Unsafe.AsRef<SpanByte>(curr); | ||
curr += key.TotalSize; | ||
|
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.
This could be cleaned up with an AofRecordDescriptor or something like that, to cover these pointer manipulations
/// </summary> | ||
public unsafe SpanByte SpanByte => new(Length, (nint)ToPointer()); | ||
public int parseStateStartIdx; | ||
|
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.
This is another opportunity for clearer naming: What index? into the byte*? The reader has to look to see it refers to arguments. You use "token" in some other places, which is also good but should be consistent. (I know this field name came from existing ObjectInput; it would be nice to make these all consistent and clear)
long startOffset = *(long*)(input + sizeof(byte)); | ||
long endOffset = *(long*)(input + sizeof(byte) + sizeof(long)); | ||
byte offsetType = *(input + sizeof(byte) + sizeof(long) * 2); | ||
|
||
if (offsetType == 0x0) |
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.
offsetType should be an enum or at least named constants. Magic numbers are uninformative and less maintainable (I know this was there before, but since you're doing such extensive cleanup including this param, we should get this too)
return slice.length != 0 && | ||
RespReadUtils.TryReadIntSafe(ref ptr, slice.ptr + slice.length, out number, out var bytesRead, out _, | ||
out _, false) && | ||
(int)bytesRead == slice.length; |
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.
use "paramName: false" for readability
E = HyperLogLog.DefaultHLL.Count(value.ToPointer()); | ||
*(long*)dst.SpanByte.ToPointer() = E; | ||
return; | ||
|
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.
Was it intentional to remove the
E = HyperLogLog.DefaultHLL.Count(value.ToPointer());
call?
pcurr += sizeof(long); | ||
*pcurr = (byte)(useBitInterval ? 1 : 0); | ||
var startBytes = Encoding.ASCII.GetBytes(start.ToString(CultureInfo.InvariantCulture)); | ||
var endBytes = Encoding.ASCII.GetBytes(end.ToString(CultureInfo.InvariantCulture)); |
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.
These do allocations. stackalloc long[1] instead?
*(long*)pcurr = commandArguments[i].value; pcurr += 8; | ||
*pcurr = commandArguments[i].overflowType; | ||
var op = (RespCommand)commandArguments[i].secondaryOpCode; | ||
var opBytes = Encoding.ASCII.GetBytes(op.ToString()); |
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.
More GetBytes allocations and also some ToString() allocations. Can these be avoided? If not, comment why they are necessary
{ | ||
*(long*)pcurr = (long)HashUtils.MurmurHash2x64A(ptr, bString.Length); |
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 looks like the Murmur has moved into IterateUpdate. Can the string be fixed directly rather than another GetBytes?
Benchmarking results:
main:
current branch: