Skip to content

Commit ca0724b

Browse files
Merge pull request #174 from QueryaHub/perf-unify-dependencies-148
perf(cache): unify dependency arrays into contiguous Arc<[DependencyEntry]> (#148)
2 parents 6995ba0 + f97dbae commit ca0724b

3 files changed

Lines changed: 56 additions & 83 deletions

File tree

src/dispatch.rs

Lines changed: 21 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -665,12 +665,7 @@ pub async fn run_rsgi(
665665
jwt_validation,
666666
read_json_body,
667667
read_form_body,
668-
dep_names,
669-
dep_factories,
670-
dep_is_async,
671-
dep_wants_request,
672-
dep_factory_params,
673-
dep_factory_varkw,
668+
dependencies,
674669
handler_param_names,
675670
handler_varkw,
676671
body_model,
@@ -688,12 +683,7 @@ pub async fn run_rsgi(
688683
e.extra.jwt_validation.clone(),
689684
e.read_json_body,
690685
e.read_form_body,
691-
Arc::clone(&e.extra.dep_names),
692-
Arc::clone(&e.extra.dep_factories),
693-
Arc::clone(&e.extra.dep_is_async),
694-
Arc::clone(&e.extra.dep_wants_request),
695-
Arc::clone(&e.extra.dep_factory_params),
696-
Arc::clone(&e.extra.dep_factory_varkw),
686+
Arc::clone(&e.extra.dependencies),
697687
Arc::clone(&e.extra.handler_param_names),
698688
e.handler_varkw,
699689
e.body_model.clone(),
@@ -923,7 +913,7 @@ pub async fn run_rsgi(
923913
} else {
924914
(HashMap::new(), vec![])
925915
};
926-
let need_req_ctx = dep_wants_request.iter().any(|&x| x);
916+
let need_req_ctx = dependencies.iter().any(|d| d.wants_request);
927917
let request_ctx: Option<Py<PyAny>> = if need_req_ctx {
928918
match Python::with_gil(|py| -> PyResult<Py<PyAny>> {
929919
let s = scope.bind(py);
@@ -939,26 +929,23 @@ pub async fn run_rsgi(
939929
} else {
940930
None
941931
};
942-
let mut dep_out: Vec<PyObject> = Vec::with_capacity(dep_factories.len());
943-
for (i, fact) in dep_factories.iter().enumerate() {
944-
let wants_request = dep_wants_request.get(i) == Some(&true);
945-
let factory_params = dep_factory_params.get(i);
946-
let factory_varkw = dep_factory_varkw.get(i).copied().unwrap_or(false);
947-
let o = if dep_is_async.get(i) == Some(&true) {
932+
let mut dep_out: Vec<PyObject> = Vec::with_capacity(dependencies.len());
933+
for (i, dep) in dependencies.iter().enumerate() {
934+
let o = if dep.is_async {
948935
let r = match Python::with_gil(|py| -> PyResult<PyObject> {
949936
let kw = PyDict::new(py);
950-
if wants_request {
937+
if dep.wants_request {
951938
if let Some(ref rc) = request_ctx {
952939
kw.set_item("request", rc.bind(py))?;
953940
}
954941
}
955-
for j in 0..i {
956-
let name = &dep_names[j];
957-
if factory_varkw || factory_params.is_none_or(|p| p.contains(name)) {
942+
for (j, prev_dep) in dependencies[..i].iter().enumerate() {
943+
let name = &prev_dep.name;
944+
if dep.factory_varkw || dep.factory_params.contains(name) {
958945
kw.set_item(name.as_str(), dep_out[j].bind(py))?;
959946
}
960947
}
961-
let f = fact.bind(py);
948+
let f = dep.factory.bind(py);
962949
if kw.is_empty() {
963950
Ok(f.call0()?.unbind())
964951
} else {
@@ -1012,18 +999,18 @@ pub async fn run_rsgi(
1012999
} else {
10131000
match Python::with_gil(|py| -> PyResult<PyObject> {
10141001
let kw = PyDict::new(py);
1015-
if wants_request {
1002+
if dep.wants_request {
10161003
if let Some(ref rc) = request_ctx {
10171004
kw.set_item("request", rc.bind(py))?;
10181005
}
10191006
}
1020-
for j in 0..i {
1021-
let name = &dep_names[j];
1022-
if factory_varkw || factory_params.is_none_or(|p| p.contains(name)) {
1007+
for (j, prev_dep) in dependencies[..i].iter().enumerate() {
1008+
let name = &prev_dep.name;
1009+
if dep.factory_varkw || dep.factory_params.contains(name) {
10231010
kw.set_item(name.as_str(), dep_out[j].bind(py))?;
10241011
}
10251012
}
1026-
let f = fact.bind(py);
1013+
let f = dep.factory.bind(py);
10271014
if kw.is_empty() {
10281015
Ok(f.call0()?.unbind())
10291016
} else {
@@ -1098,8 +1085,8 @@ pub async fn run_rsgi(
10981085
read_form_body && (handler_varkw || handler_param_names.contains("files"));
10991086
let should_pass_protocol = handler_varkw || handler_param_names.contains("protocol");
11001087
let should_pass_body = !read_form_body && !body_bytes.is_empty() && body_json.is_none();
1101-
let has_dep_kwargs = dep_names.iter().enumerate().any(|(i, name)| {
1102-
dep_out.get(i).is_some() && (handler_varkw || handler_param_names.contains(name))
1088+
let has_dep_kwargs = dependencies.iter().enumerate().any(|(i, dep)| {
1089+
dep_out.get(i).is_some() && (handler_varkw || handler_param_names.contains(&dep.name))
11031090
});
11041091
let should_use_kwargs = !param_map.is_empty()
11051092
|| !query_map.is_empty()
@@ -1132,10 +1119,10 @@ pub async fn run_rsgi(
11321119
}
11331120
kwargs.set_item("query", qd)?;
11341121
}
1135-
for (i, name) in dep_names.iter().enumerate() {
1122+
for (i, dep) in dependencies.iter().enumerate() {
11361123
if let Some(oo) = dep_out.get(i) {
1137-
if handler_varkw || handler_param_names.contains(name) {
1138-
kwargs.set_item(name, oo.bind(py))?;
1124+
if handler_varkw || handler_param_names.contains(&dep.name) {
1125+
kwargs.set_item(&dep.name, oo.bind(py))?;
11391126
}
11401127
}
11411128
}

src/lib.rs

Lines changed: 23 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -53,15 +53,6 @@ pub mod microbench {
5353
}
5454
}
5555

56-
type ParsedDependencies = (
57-
Vec<String>,
58-
Vec<Py<PyAny>>,
59-
Vec<bool>,
60-
Vec<bool>,
61-
Vec<HashSet<String>>,
62-
Vec<bool>,
63-
);
64-
6556
/// Parameter names the route handler accepts, plus whether it has `**kwargs`.
6657
fn handler_signature_kinds(
6758
py: Python<'_>,
@@ -94,16 +85,15 @@ fn parse_algorithm(s: &str) -> PyResult<jsonwebtoken::Algorithm> {
9485
})
9586
}
9687

97-
fn parse_dependencies(py: Python<'_>, dep_list: &Bound<PyList>) -> PyResult<ParsedDependencies> {
88+
fn parse_dependencies(
89+
py: Python<'_>,
90+
dep_list: &Bound<PyList>,
91+
) -> PyResult<Vec<state::DependencyEntry>> {
9892
let inspect = py.import("inspect")?;
9993
let iscoro = inspect.getattr("iscoroutinefunction")?;
10094
let n = dep_list.len();
101-
let mut names = Vec::with_capacity(n);
102-
let mut facts = Vec::with_capacity(n);
103-
let mut asy = Vec::with_capacity(n);
104-
let mut want_req = Vec::with_capacity(n);
105-
let mut factory_params = Vec::with_capacity(n);
106-
let mut factory_varkw = Vec::with_capacity(n);
95+
let mut names = HashSet::with_capacity(n);
96+
let mut out = Vec::with_capacity(n);
10797
for i in 0..n {
10898
let it = dep_list.get_item(i)?;
10999
let tup = it.downcast::<PyTuple>()?;
@@ -113,24 +103,26 @@ fn parse_dependencies(py: Python<'_>, dep_list: &Bound<PyList>) -> PyResult<Pars
113103
));
114104
}
115105
let name: String = tup.get_item(0)?.extract()?;
116-
if names.contains(&name) {
106+
if !names.insert(name.clone()) {
117107
return Err(pyo3::exceptions::PyValueError::new_err(
118108
"duplicate dependency name",
119109
));
120110
}
121111
let f: Py<PyAny> = tup.get_item(1)?.unbind();
122-
let is_a: bool = iscoro.call1((f.clone_ref(py),))?.extract()?;
112+
let is_async: bool = iscoro.call1((f.clone_ref(py),))?.extract()?;
123113
let f_b = f.bind(py);
124-
let has_req: bool = dependency_wants_request(py, f_b)?;
125-
let (params, varkw) = handler_signature_kinds(py, f_b)?;
126-
names.push(name);
127-
facts.push(f);
128-
asy.push(is_a);
129-
want_req.push(has_req);
130-
factory_params.push(params);
131-
factory_varkw.push(varkw);
114+
let wants_request: bool = dependency_wants_request(py, f_b)?;
115+
let (factory_params, factory_varkw) = handler_signature_kinds(py, f_b)?;
116+
out.push(state::DependencyEntry {
117+
name,
118+
factory: f,
119+
is_async,
120+
wants_request,
121+
factory_params,
122+
factory_varkw,
123+
});
132124
}
133-
Ok((names, facts, asy, want_req, factory_params, factory_varkw))
125+
Ok(out)
134126
}
135127

136128
/// True if the factory declares a `request` parameter (for the request context dict).
@@ -362,17 +354,10 @@ impl App {
362354
} else {
363355
(None, None)
364356
};
365-
let (
366-
dep_names,
367-
dep_factories,
368-
dep_is_async,
369-
dep_wants_request,
370-
dep_factory_params,
371-
dep_factory_varkw,
372-
) = if let Some(d) = dependencies {
357+
let dependencies = if let Some(d) = dependencies {
373358
parse_dependencies(py, &d)?
374359
} else {
375-
(vec![], vec![], vec![], vec![], vec![], vec![])
360+
vec![]
376361
};
377362
let op_id: String = handler
378363
.bind(py)
@@ -383,7 +368,7 @@ impl App {
383368
&& !require_jwt
384369
&& !read_json_body
385370
&& !read_form_body
386-
&& dep_factories.is_empty()
371+
&& dependencies.is_empty()
387372
&& !handler_varkw
388373
&& handler_param_names.is_empty();
389374
let mut st = self.state.write();
@@ -394,12 +379,7 @@ impl App {
394379
jwt_cookie,
395380
jwt_decoding_key,
396381
jwt_validation,
397-
dep_names: Arc::<[String]>::from(dep_names),
398-
dep_factories: Arc::<[Py<PyAny>]>::from(dep_factories),
399-
dep_is_async: Arc::<[bool]>::from(dep_is_async),
400-
dep_wants_request: Arc::<[bool]>::from(dep_wants_request),
401-
dep_factory_params: Arc::<[HashSet<String>]>::from(dep_factory_params),
402-
dep_factory_varkw: Arc::<[bool]>::from(dep_factory_varkw),
382+
dependencies: Arc::<[state::DependencyEntry]>::from(dependencies),
403383
handler_param_names: Arc::new(handler_param_names),
404384
body_param_name: body_param_name.unwrap_or_else(|| "json".to_string()),
405385
});

src/state.rs

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,25 @@ pub struct WebsocketRoute {
3636
pub is_async: bool,
3737
}
3838

39+
/// A single route dependency definition.
40+
#[derive(Clone)]
41+
pub struct DependencyEntry {
42+
pub name: String,
43+
pub factory: Py<PyAny>,
44+
pub is_async: bool,
45+
pub wants_request: bool,
46+
pub factory_params: HashSet<String>,
47+
pub factory_varkw: bool,
48+
}
49+
3950
/// Auxiliary / cold metadata for a route.
4051
#[derive(Clone)]
4152
pub struct RouteExtra {
4253
pub path_template: String,
4354
pub jwt_cookie: Option<String>,
4455
pub jwt_decoding_key: Option<Arc<jsonwebtoken::DecodingKey>>,
4556
pub jwt_validation: Option<Arc<jsonwebtoken::Validation>>,
46-
pub dep_names: Arc<[String]>,
47-
pub dep_factories: Arc<[Py<PyAny>]>,
48-
pub dep_is_async: Arc<[bool]>,
49-
pub dep_wants_request: Arc<[bool]>,
50-
pub dep_factory_params: Arc<[HashSet<String>]>,
51-
pub dep_factory_varkw: Arc<[bool]>,
57+
pub dependencies: Arc<[DependencyEntry]>,
5258
pub handler_param_names: Arc<HashSet<String>>,
5359
pub body_param_name: String,
5460
}

0 commit comments

Comments
 (0)