Skip to content

Commit ca40376

Browse files
committed
Optimise pyo3 use
While this has not been profiled, it seems rather unlikely that the python implementation uses the same allocator as Rust (whether system or other). This means *returning* a `String` is an extra allocation as that will need to be copied again to a `PyString` on the ouput. Which is just dumb. Copy the internal `Cow` directly to a `PyString` on output. On input, a `String` is necessary as there's no way to lifetime properly from Python, so the extractors need to be `'static`.
1 parent 2673809 commit ca40376

File tree

1 file changed

+31
-31
lines changed

1 file changed

+31
-31
lines changed

ua-parser-py/src/lib.rs

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@
2525
/// them to Parsers as well but that's still very confusing given the
2626
/// global Parser object, unless *that* gets renamed to Extractor on
2727
/// the python side, or something.
28-
use pyo3::exceptions::PyValueError;
2928
use pyo3::prelude::*;
30-
use std::borrow::Cow::{self, Owned};
29+
use pyo3::{exceptions::PyValueError, types::PyString};
30+
use std::borrow::Cow::Owned;
3131

3232
type UAParser = (
3333
String,
@@ -42,15 +42,15 @@ struct UserAgentExtractor(ua_parser::user_agent::Extractor<'static>);
4242
#[pyclass(frozen)]
4343
struct UserAgent {
4444
#[pyo3(get)]
45-
family: String,
45+
family: Py<PyString>,
4646
#[pyo3(get)]
47-
major: Option<String>,
47+
major: Option<Py<PyString>>,
4848
#[pyo3(get)]
49-
minor: Option<String>,
49+
minor: Option<Py<PyString>>,
5050
#[pyo3(get)]
51-
patch: Option<String>,
51+
patch: Option<Py<PyString>>,
5252
#[pyo3(get)]
53-
patch_minor: Option<String>,
53+
patch_minor: Option<Py<PyString>>,
5454
}
5555
#[pymethods]
5656
impl UserAgentExtractor {
@@ -74,13 +74,13 @@ impl UserAgentExtractor {
7474
.map_err(|e| PyValueError::new_err(e.to_string()))
7575
.map(Self)
7676
}
77-
fn extract(&self, s: &str) -> PyResult<Option<UserAgent>> {
77+
fn extract(&self, py: Python<'_>, s: &str) -> PyResult<Option<UserAgent>> {
7878
Ok(self.0.extract(s).map(|v| UserAgent {
79-
family: v.family.into_owned(),
80-
major: v.major.map(|s| s.to_string()),
81-
minor: v.minor.map(|s| s.to_string()),
82-
patch: v.patch.map(|s| s.to_string()),
83-
patch_minor: v.patch_minor.map(|s| s.to_string()),
79+
family: PyString::new_bound(py, &v.family).unbind(),
80+
major: v.major.map(|s| PyString::new_bound(py, s).unbind()),
81+
minor: v.minor.map(|s| PyString::new_bound(py, s).unbind()),
82+
patch: v.patch.map(|s| PyString::new_bound(py, s).unbind()),
83+
patch_minor: v.patch_minor.map(|s| PyString::new_bound(py, s).unbind()),
8484
}))
8585
}
8686
}
@@ -98,15 +98,15 @@ struct OSExtractor(ua_parser::os::Extractor<'static>);
9898
#[pyclass(frozen)]
9999
struct OS {
100100
#[pyo3(get)]
101-
family: String,
101+
family: Py<PyString>,
102102
#[pyo3(get)]
103-
major: Option<String>,
103+
major: Option<Py<PyString>>,
104104
#[pyo3(get)]
105-
minor: Option<String>,
105+
minor: Option<Py<PyString>>,
106106
#[pyo3(get)]
107-
patch: Option<String>,
107+
patch: Option<Py<PyString>>,
108108
#[pyo3(get)]
109-
patch_minor: Option<String>,
109+
patch_minor: Option<Py<PyString>>,
110110
}
111111
#[pymethods]
112112
impl OSExtractor {
@@ -130,13 +130,13 @@ impl OSExtractor {
130130
.map_err(|e| PyValueError::new_err(e.to_string()))
131131
.map(Self)
132132
}
133-
fn extract(&self, s: &str) -> PyResult<Option<OS>> {
133+
fn extract(&self, py: Python<'_>, s: &str) -> PyResult<Option<OS>> {
134134
Ok(self.0.extract(s).map(|v| OS {
135-
family: v.os.into_owned(),
136-
major: v.major.map(Cow::into_owned),
137-
minor: v.minor.map(Cow::into_owned),
138-
patch: v.patch.map(Cow::into_owned),
139-
patch_minor: v.patch_minor.map(Cow::into_owned),
135+
family: PyString::new_bound(py, &v.os).unbind(),
136+
major: v.major.map(|s| PyString::new_bound(py, &s).unbind()),
137+
minor: v.minor.map(|s| PyString::new_bound(py, &s).unbind()),
138+
patch: v.patch.map(|s| PyString::new_bound(py, &s).unbind()),
139+
patch_minor: v.patch_minor.map(|s| PyString::new_bound(py, &s).unbind()),
140140
}))
141141
}
142142
}
@@ -153,11 +153,11 @@ struct DeviceExtractor(ua_parser::device::Extractor<'static>);
153153
#[pyclass(frozen)]
154154
struct Device {
155155
#[pyo3(get)]
156-
family: String,
156+
family: Py<PyString>,
157157
#[pyo3(get)]
158-
brand: Option<String>,
158+
brand: Option<Py<PyString>>,
159159
#[pyo3(get)]
160-
model: Option<String>,
160+
model: Option<Py<PyString>>,
161161
}
162162
#[pymethods]
163163
impl DeviceExtractor {
@@ -184,11 +184,11 @@ impl DeviceExtractor {
184184
.map_err(|e| PyValueError::new_err(e.to_string()))
185185
.map(Self)
186186
}
187-
fn extract(&self, s: &str) -> PyResult<Option<Device>> {
187+
fn extract(&self, py: Python<'_>, s: &str) -> PyResult<Option<Device>> {
188188
Ok(self.0.extract(s).map(|v| Device {
189-
family: v.device.into_owned(),
190-
brand: v.brand.map(Cow::into_owned),
191-
model: v.model.map(Cow::into_owned),
189+
family: PyString::new_bound(py, &v.device).unbind(),
190+
brand: v.brand.map(|s| PyString::new_bound(py, &s).unbind()),
191+
model: v.model.map(|s| PyString::new_bound(py, &s).unbind()),
192192
}))
193193
}
194194
}

0 commit comments

Comments
 (0)