Skip to content
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

router: optimize computeProcID #4520

Merged
merged 4 commits into from
May 14, 2024
Merged

Conversation

jiceatscion
Copy link
Contributor

@jiceatscion jiceatscion commented May 3, 2024

Fixes #4519

@jiceatscion jiceatscion requested a review from matzf as a code owner May 3, 2024 17:56
@matzf
Copy link
Member

matzf commented May 3, 2024

This change is Reviewable

@jiceatscion jiceatscion marked this pull request as ready for review May 3, 2024 18:20
Copy link
Member

@matzf matzf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 1 of 2 files at r1, 1 of 1 files at r2, all commit messages.
Reviewable status: all files reviewed, 2 unresolved discussions (waiting on @jiceatscion)


router/dataplane.go line 604 at r2 (raw file):

	if _, err := rand.Read(randomValue); err != nil {
		panic("Error while generating random value")
	}

Adding this randomness into the hash is a security mechanism, it makes it harder for an attacker to attack the hash function. E.g. an attacker could compute a flow ID to target processing on the same processing queue as a specific victim flow. Note that the output of the hash function is very difficult to observe for an attacker, and so recovering the random value, even if it's short, would be very hard.

That said, 16 bytes of randomness is possibly excessive; 4 bytes seem to suffice to sufficiently randomize over most of the "state space" (I ran a quick experiment, a bit under 2^31 distinct outputs are reached from the full set of 2^32 four byte inputs). If hashing this same random value repeatedly has a significant cost, it could also make sense to precompute the hash state for the random input.


router/dataplane.go line 693 at r2 (raw file):

	return s % uint32(numProcRoutines), nil
}

Would be nice to still implement the main body of the hash function in a separate function, perhaps also in a separate file (this dataplane.go is growing to contain everything).
Something like func hashFNV1a(state uint32, c byte) uint32, then use it as s = hashFNV1a(s, c) . This should be inlined and so have no overhead. The definition of the prime32 constant can be moved into that function body. The initial offset is needed for the initialization, I'd define it as something like fnv1Offset32.

@matzf matzf changed the title Make computeProcID cheap. router: optimize computeProcID May 7, 2024
Copy link
Contributor Author

@jiceatscion jiceatscion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 0 of 4 files reviewed, 2 unresolved discussions (waiting on @matzf)


router/dataplane.go line 604 at r2 (raw file):

Previously, matzf (Matthias Frei) wrote…

Adding this randomness into the hash is a security mechanism, it makes it harder for an attacker to attack the hash function. E.g. an attacker could compute a flow ID to target processing on the same processing queue as a specific victim flow. Note that the output of the hash function is very difficult to observe for an attacker, and so recovering the random value, even if it's short, would be very hard.

That said, 16 bytes of randomness is possibly excessive; 4 bytes seem to suffice to sufficiently randomize over most of the "state space" (I ran a quick experiment, a bit under 2^31 distinct outputs are reached from the full set of 2^32 four byte inputs). If hashing this same random value repeatedly has a significant cost, it could also make sense to precompute the hash state for the random input.

Done.


router/dataplane.go line 693 at r2 (raw file):

Previously, matzf (Matthias Frei) wrote…

Would be nice to still implement the main body of the hash function in a separate function, perhaps also in a separate file (this dataplane.go is growing to contain everything).
Something like func hashFNV1a(state uint32, c byte) uint32, then use it as s = hashFNV1a(s, c) . This should be inlined and so have no overhead. The definition of the prime32 constant can be moved into that function body. The initial offset is needed for the initialization, I'd define it as something like fnv1Offset32.

Done.

Copy link
Member

@matzf matzf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 4 of 4 files at r3, all commit messages.
Reviewable status: all files reviewed, 3 unresolved discussions (waiting on @jiceatscion)


router/dataplane.go line 675 at r3 (raw file):

	}

	var s uint32 = hashSeed

s := hashSeed


router/dataplane_internal_test.go line 217 at r3 (raw file):

func TestComputeProcId(t *testing.T) {
	randomValue := uint32(1234)
	randomValueBytes := []byte{byte(randomValue & 0xff), byte(randomValue >> 8), 0, 0}

Maybe revert this bit here, IMO it was more readable before: randomValue := []byte{1, 2, 3, 4}


router/fnv1aCheap.go line 19 at r3 (raw file):

// fnv1aOffset32 is an initial offset that can be used as initial state when calling
// hashFNV1a.
const fnv1aOffset32 = 2166136261

Make this uint32, const fnv1aOffset32 uint32 = ..., and remove the cast where the value is referenced.

Copy link
Contributor Author

@jiceatscion jiceatscion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewable status: 1 of 4 files reviewed, 2 unresolved discussions (waiting on @matzf)


router/dataplane.go line 675 at r3 (raw file):

Previously, matzf (Matthias Frei) wrote…

s := hashSeed

Done.


router/fnv1aCheap.go line 19 at r3 (raw file):

Previously, matzf (Matthias Frei) wrote…

Make this uint32, const fnv1aOffset32 uint32 = ..., and remove the cast where the value is referenced.

Done.

Copy link
Member

@matzf matzf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 3 of 3 files at r4, all commit messages.
Reviewable status: :shipit: complete! all files reviewed, all discussions resolved (waiting on @jiceatscion)

@matzf
Copy link
Member

matzf commented May 14, 2024

For whatever it's worth, comparing the router benchmark results with this PR against nightly, I see an improvement of 0.09%. Not a whole lot gained, but not a lot of complexity either.

@matzf matzf merged commit 4c9c342 into scionproto:master May 14, 2024
4 checks passed
Copy link
Contributor Author

@jiceatscion jiceatscion left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. Most likely the time removed from computing was added to the time sleeping :-( Oh well, when we improve the I/O side we'll be able to profit from this.

Reviewable status: :shipit: complete! all files reviewed, all discussions resolved

@jiceatscion jiceatscion deleted the router_procid branch May 14, 2024 08:45
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.

router: computeProcID consumes 2.5% of packet processing time
2 participants