-
Notifications
You must be signed in to change notification settings - Fork 267
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Batch of performance improvements: * linux: replace default allocator with jemalloc (#2882) * Add a private part to the Context structure (#2802) * lighter path manipulation in response formatting (#2854) * prevent span attributes from being formatted to write logs (#2890) * Parse Accept headers once instead of three times (#2847) * use a parking-lot mutex in Context (#2885) * Filter spans before sending them to the opentelemetry layer (#2894)
- Loading branch information
Showing
31 changed files
with
644 additions
and
322 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
### use a parking-lot mutex in Context ([Issue #2751](https://github.com/apollographql/router/issues/2751)) | ||
|
||
The context requires synchronized access to the busy timer, and precedently we used a futures aware mutex for that, but those are susceptible to contention. This replaces that mutex with a parking-lot synchronous mutex that is much faster. | ||
|
||
By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/2885 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
### Filter spans before sending them to the opentelemetry layer | ||
|
||
the sampling configuration in the opentelemetry layer only applies when the span closes, so in the meantime a lot of data is created just to be dropped. This adds a filter than can sample spans before the opentelemetry layer. The sampling decision is done at the root span, and then derived from the parent span in the rest of the trace. | ||
|
||
By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/2894 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
### prevent span attributes from being formatted to write logs | ||
|
||
we do not show span attributes in our logs, but the log formatter still spends some time formatting them to a string, even when there will be no logs written for the trace. This adds the `NullFieldFormatter` that entirely avoids formatting the attributes | ||
|
||
By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/2890 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
### use jemalloc on linux | ||
|
||
Detailed memory investigations of the router in use have revealed that there is a significant amount of memory fragmentation when using the default allocator, glibc, on linux. Performance testing and flamegraph analysis suggests that jemalloc on linux can yield significant performance improvements. In our tests, this figure shows performance to be about 35% faster than the default allocator. The improvement in performance being due to less time spent managing memory fragmentation. | ||
|
||
Not everyone will see a 35% performance improvement in this release of the router. Depending on your usage pattern, you may see more or less than this. If you see a regression, please file an issue with details. | ||
|
||
We have no reason to believe that there are allocation problems on other platforms, so this change is confined to linux. | ||
|
||
By [@garypen](https://github.com/garypen) in https://github.com/apollographql/router/pull/2882 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
### lighter path manipulation in response formatting | ||
|
||
Response formatting generates a lot of temporary allocations to create response paths that end up unused. By making a reference based type to hold these paths, we can prevent those allocations and improve performance. | ||
|
||
By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/2854 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
### Add a private part to the Context structure ([Issue #2800](https://github.com/apollographql/router/issues/2800)) | ||
|
||
There's a cost in using the `Context` structure throughout a request's lifecycle, due to the JSON serialization and deserialization, so it should be reserved from inter plugin communication between rhai, coprocessor and Rust. But for internal router usage, we can have a more efficient structure that avoids serialization costs, and does not expose data that should not be modified by plugins. | ||
|
||
That structure is based on a map indexed by type id, which means that if some part of the code can see that type, then it can access it in the map. | ||
|
||
By [@Geal](https://github.com/Geal) in https://github.com/apollographql/router/pull/2802 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// NOTE: this module is taken from tokio's tracing span's extensions | ||
// which is taken from https://github.com/hyperium/http/blob/master/src/extensions.rs | ||
|
||
use std::any::Any; | ||
use std::any::TypeId; | ||
use std::collections::HashMap; | ||
use std::fmt; | ||
use std::hash::BuildHasherDefault; | ||
use std::hash::Hasher; | ||
|
||
type AnyMap = HashMap<TypeId, Box<dyn Any + Send + Sync>, BuildHasherDefault<IdHasher>>; | ||
|
||
// With TypeIds as keys, there's no need to hash them. They are already hashes | ||
// themselves, coming from the compiler. The IdHasher just holds the u64 of | ||
// the TypeId, and then returns it, instead of doing any bit fiddling. | ||
#[derive(Default)] | ||
struct IdHasher(u64); | ||
|
||
impl Hasher for IdHasher { | ||
fn write(&mut self, _: &[u8]) { | ||
unreachable!("TypeId calls write_u64"); | ||
} | ||
|
||
#[inline] | ||
fn write_u64(&mut self, id: u64) { | ||
self.0 = id; | ||
} | ||
|
||
#[inline] | ||
fn finish(&self) -> u64 { | ||
self.0 | ||
} | ||
} | ||
|
||
/// A type map of protocol extensions. | ||
/// | ||
/// `Extensions` can be used by `Request` and `Response` to store | ||
/// extra data derived from the underlying protocol. | ||
#[derive(Default)] | ||
pub(crate) struct Extensions { | ||
// If extensions are never used, no need to carry around an empty HashMap. | ||
// That's 3 words. Instead, this is only 1 word. | ||
map: Option<Box<AnyMap>>, | ||
} | ||
|
||
#[allow(unused)] | ||
impl Extensions { | ||
/// Create an empty `Extensions`. | ||
#[inline] | ||
pub(crate) fn new() -> Extensions { | ||
Extensions { map: None } | ||
} | ||
|
||
/// Insert a type into this `Extensions`. | ||
/// | ||
/// If a extension of this type already existed, it will | ||
/// be returned. | ||
pub(crate) fn insert<T: Send + Sync + 'static>(&mut self, val: T) -> Option<T> { | ||
self.map | ||
.get_or_insert_with(Box::default) | ||
.insert(TypeId::of::<T>(), Box::new(val)) | ||
.and_then(|boxed| { | ||
(boxed as Box<dyn Any + 'static>) | ||
.downcast() | ||
.ok() | ||
.map(|boxed| *boxed) | ||
}) | ||
} | ||
|
||
/// Get a reference to a type previously inserted on this `Extensions`. | ||
pub(crate) fn get<T: Send + Sync + 'static>(&self) -> Option<&T> { | ||
self.map | ||
.as_ref() | ||
.and_then(|map| map.get(&TypeId::of::<T>())) | ||
.and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref()) | ||
} | ||
|
||
/// Get a mutable reference to a type previously inserted on this `Extensions`. | ||
pub(crate) fn get_mut<T: Send + Sync + 'static>(&mut self) -> Option<&mut T> { | ||
self.map | ||
.as_mut() | ||
.and_then(|map| map.get_mut(&TypeId::of::<T>())) | ||
.and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut()) | ||
} | ||
|
||
pub(crate) fn contains_key<T: Send + Sync + 'static>(&self) -> bool { | ||
self.map | ||
.as_ref() | ||
.map(|map| map.contains_key(&TypeId::of::<T>())) | ||
.unwrap_or_default() | ||
} | ||
|
||
/// Remove a type from this `Extensions`. | ||
/// | ||
/// If a extension of this type existed, it will be returned. | ||
pub(crate) fn remove<T: Send + Sync + 'static>(&mut self) -> Option<T> { | ||
self.map | ||
.as_mut() | ||
.and_then(|map| map.remove(&TypeId::of::<T>())) | ||
.and_then(|boxed| { | ||
(boxed as Box<dyn Any + 'static>) | ||
.downcast() | ||
.ok() | ||
.map(|boxed| *boxed) | ||
}) | ||
} | ||
|
||
/// Clear the `Extensions` of all inserted extensions. | ||
#[inline] | ||
pub(crate) fn clear(&mut self) { | ||
if let Some(ref mut map) = self.map { | ||
map.clear(); | ||
} | ||
} | ||
|
||
/// Check whether the extension set is empty or not. | ||
#[inline] | ||
pub(crate) fn is_empty(&self) -> bool { | ||
self.map.as_ref().map_or(true, |map| map.is_empty()) | ||
} | ||
|
||
/// Get the numer of extensions available. | ||
#[inline] | ||
pub(crate) fn len(&self) -> usize { | ||
self.map.as_ref().map_or(0, |map| map.len()) | ||
} | ||
} | ||
|
||
impl fmt::Debug for Extensions { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
f.debug_struct("Extensions").finish() | ||
} | ||
} | ||
|
||
#[test] | ||
fn test_extensions() { | ||
#[derive(Debug, PartialEq)] | ||
struct MyType(i32); | ||
|
||
let mut extensions = Extensions::new(); | ||
|
||
extensions.insert(5i32); | ||
extensions.insert(MyType(10)); | ||
|
||
assert_eq!(extensions.get(), Some(&5i32)); | ||
assert_eq!(extensions.get_mut(), Some(&mut 5i32)); | ||
|
||
assert_eq!(extensions.remove::<i32>(), Some(5i32)); | ||
assert!(extensions.get::<i32>().is_none()); | ||
|
||
assert_eq!(extensions.get::<bool>(), None); | ||
assert_eq!(extensions.get(), Some(&MyType(10))); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.