mirror of https://github.com/smithy-lang/smithy-rs
Use Vec for route resolution when number of routes <15 (#1429)
* Add `TinyMap` collection, which is a map backed by either `Vec` or `HashMap` depending on the number of entries. * Replace `HashMap` with `TinyMap` for `Routes::AwsJson10` and `Routes::AwsJson11`.
This commit is contained in:
parent
f1a4782614
commit
1875448dbb
|
@ -9,13 +9,13 @@
|
|||
|
||||
use self::future::RouterFuture;
|
||||
use self::request_spec::RequestSpec;
|
||||
use self::tiny_map::TinyMap;
|
||||
use crate::body::{boxed, Body, BoxBody, HttpBody};
|
||||
use crate::error::BoxError;
|
||||
use crate::protocols::Protocol;
|
||||
use crate::response::IntoResponse;
|
||||
use crate::runtime_error::{RuntimeError, RuntimeErrorKind};
|
||||
use http::{Request, Response, StatusCode};
|
||||
use std::collections::HashMap;
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
task::{Context, Poll},
|
||||
|
@ -32,6 +32,7 @@ mod into_make_service;
|
|||
pub mod request_spec;
|
||||
|
||||
mod route;
|
||||
mod tiny_map;
|
||||
|
||||
pub use self::{into_make_service::IntoMakeService, route::Route};
|
||||
|
||||
|
@ -60,6 +61,11 @@ pub struct Router<B = Body> {
|
|||
routes: Routes<B>,
|
||||
}
|
||||
|
||||
// This constant determines when the `TinyMap` implementation switches from being a `Vec` to a
|
||||
// `HashMap`. This is chosen to be 15 as a result of the discussion around
|
||||
// https://github.com/awslabs/smithy-rs/pull/1429#issuecomment-1147516546
|
||||
const ROUTE_CUTOFF: usize = 15;
|
||||
|
||||
/// Protocol-aware routes types.
|
||||
///
|
||||
/// RestJson1 and RestXml routes are stored in a `Vec` because there can be multiple matches on the
|
||||
|
@ -71,8 +77,8 @@ pub struct Router<B = Body> {
|
|||
enum Routes<B = Body> {
|
||||
RestXml(Vec<(Route<B>, RequestSpec)>),
|
||||
RestJson1(Vec<(Route<B>, RequestSpec)>),
|
||||
AwsJson10(HashMap<String, Route<B>>),
|
||||
AwsJson11(HashMap<String, Route<B>>),
|
||||
AwsJson10(TinyMap<String, Route<B>, ROUTE_CUTOFF>),
|
||||
AwsJson11(TinyMap<String, Route<B>, ROUTE_CUTOFF>),
|
||||
}
|
||||
|
||||
impl<B> Clone for Router<B> {
|
||||
|
@ -343,7 +349,7 @@ where
|
|||
// Find the `x-amz-target` header.
|
||||
if let Some(target) = req.headers().get("x-amz-target") {
|
||||
if let Ok(target) = target.to_str() {
|
||||
// Lookup in the `HashMap` for a route for the target.
|
||||
// Lookup in the `TinyMap` for a route for the target.
|
||||
let route = routes.get(target);
|
||||
if let Some(route) = route {
|
||||
return RouterFuture::from_oneshot(route.clone().oneshot(req));
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
use std::{borrow::Borrow, collections::HashMap, hash::Hash};
|
||||
|
||||
/// A map implementation with fast iteration which switches backing storage from [`Vec`] to
|
||||
/// [`HashMap`] when the number of entries exceeds `CUTOFF`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TinyMap<K, V, const CUTOFF: usize> {
|
||||
inner: TinyMapInner<K, V, CUTOFF>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
enum TinyMapInner<K, V, const CUTOFF: usize> {
|
||||
Vec(Vec<(K, V)>),
|
||||
HashMap(HashMap<K, V>),
|
||||
}
|
||||
|
||||
enum OrIterator<Left, Right> {
|
||||
Left(Left),
|
||||
Right(Right),
|
||||
}
|
||||
|
||||
impl<Left, Right> Iterator for OrIterator<Left, Right>
|
||||
where
|
||||
Left: Iterator,
|
||||
Right: Iterator<Item = Left::Item>,
|
||||
{
|
||||
type Item = Left::Item;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
match self {
|
||||
Self::Left(left) => left.next(),
|
||||
Self::Right(right) => right.next(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An owning iterator over the entries of a `TinyMap`.
|
||||
///
|
||||
/// This struct is created by the [`into_iter`](IntoIterator::into_iter) method on [`TinyMap`] (
|
||||
/// provided by the [`IntoIterator`] trait). See its documentation for more.
|
||||
pub struct IntoIter<K, V> {
|
||||
inner: OrIterator<std::vec::IntoIter<(K, V)>, std::collections::hash_map::IntoIter<K, V>>,
|
||||
}
|
||||
|
||||
impl<K, V> Iterator for IntoIter<K, V> {
|
||||
type Item = (K, V);
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, const CUTOFF: usize> IntoIterator for TinyMap<K, V, CUTOFF> {
|
||||
type Item = (K, V);
|
||||
|
||||
type IntoIter = IntoIter<K, V>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
let inner = match self.inner {
|
||||
TinyMapInner::Vec(vec) => OrIterator::Left(vec.into_iter()),
|
||||
TinyMapInner::HashMap(hash_map) => OrIterator::Right(hash_map.into_iter()),
|
||||
};
|
||||
IntoIter { inner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, const CUTOFF: usize> FromIterator<(K, V)> for TinyMap<K, V, CUTOFF>
|
||||
where
|
||||
K: Hash + Eq,
|
||||
{
|
||||
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
|
||||
let mut vec = Vec::with_capacity(CUTOFF);
|
||||
let mut iter = iter.into_iter().enumerate();
|
||||
|
||||
// Populate the `Vec`
|
||||
while let Some((index, pair)) = iter.next() {
|
||||
// If overflow `CUTOFF` then return a `HashMap` instead
|
||||
if index == CUTOFF {
|
||||
let inner = TinyMapInner::HashMap(vec.into_iter().chain(iter.map(|(_, pair)| pair)).collect());
|
||||
return TinyMap { inner };
|
||||
}
|
||||
|
||||
vec.push(pair);
|
||||
}
|
||||
|
||||
TinyMap {
|
||||
inner: TinyMapInner::Vec(vec),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, V, const CUTOFF: usize> TinyMap<K, V, CUTOFF>
|
||||
where
|
||||
K: Eq + Hash,
|
||||
{
|
||||
/// Returns a reference to the value corresponding to the key.
|
||||
///
|
||||
/// The key may be borrowed form of map's key type, but [`Hash`] and [`Eq`] on the borrowed
|
||||
/// form _must_ match those for the key type.
|
||||
pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
|
||||
where
|
||||
K: Borrow<Q>,
|
||||
Q: Hash + Eq,
|
||||
{
|
||||
match &self.inner {
|
||||
TinyMapInner::Vec(vec) => vec
|
||||
.iter()
|
||||
.find(|(key_inner, _)| key_inner.borrow() == key)
|
||||
.map(|(_, value)| value),
|
||||
TinyMapInner::HashMap(hash_map) => hash_map.get(key),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const CUTOFF: usize = 5;
|
||||
|
||||
const SMALL_VALUES: [(&'static str, usize); 3] = [("a", 0), ("b", 1), ("c", 2)];
|
||||
const MEDIUM_VALUES: [(&'static str, usize); 5] = [("a", 0), ("b", 1), ("c", 2), ("d", 3), ("e", 4)];
|
||||
const LARGE_VALUES: [(&'static str, usize); 10] = [
|
||||
("a", 0),
|
||||
("b", 1),
|
||||
("c", 2),
|
||||
("d", 3),
|
||||
("e", 4),
|
||||
("f", 5),
|
||||
("g", 6),
|
||||
("h", 7),
|
||||
("i", 8),
|
||||
("j", 9),
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn collect_small() {
|
||||
let tiny_map: TinyMap<_, _, CUTOFF> = SMALL_VALUES.into_iter().collect();
|
||||
assert!(matches!(tiny_map.inner, TinyMapInner::Vec(_)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_medium() {
|
||||
let tiny_map: TinyMap<_, _, CUTOFF> = MEDIUM_VALUES.into_iter().collect();
|
||||
assert!(matches!(tiny_map.inner, TinyMapInner::Vec(_)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn collect_large() {
|
||||
let tiny_map: TinyMap<_, _, CUTOFF> = LARGE_VALUES.into_iter().collect();
|
||||
assert!(matches!(tiny_map.inner, TinyMapInner::HashMap(_)))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_small_success() {
|
||||
let tiny_map: TinyMap<_, _, CUTOFF> = SMALL_VALUES.into_iter().collect();
|
||||
assert_eq!(tiny_map.get("a"), Some(&0))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_medium_success() {
|
||||
let tiny_map: TinyMap<_, _, CUTOFF> = MEDIUM_VALUES.into_iter().collect();
|
||||
assert_eq!(tiny_map.get("d"), Some(&3))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_large_success() {
|
||||
let tiny_map: TinyMap<_, _, CUTOFF> = LARGE_VALUES.into_iter().collect();
|
||||
assert_eq!(tiny_map.get("h"), Some(&7))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_small_fail() {
|
||||
let tiny_map: TinyMap<_, _, CUTOFF> = SMALL_VALUES.into_iter().collect();
|
||||
assert_eq!(tiny_map.get("x"), None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_medium_fail() {
|
||||
let tiny_map: TinyMap<_, _, CUTOFF> = MEDIUM_VALUES.into_iter().collect();
|
||||
assert_eq!(tiny_map.get("y"), None)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_large_fail() {
|
||||
let tiny_map: TinyMap<_, _, CUTOFF> = LARGE_VALUES.into_iter().collect();
|
||||
assert_eq!(tiny_map.get("z"), None)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue