Skip to content

Commit 500c8d6

Browse files
committed
perf: pre-calculate buffer size in stalk walking
1 parent f09ac25 commit 500c8d6

File tree

5 files changed

+112
-13
lines changed

5 files changed

+112
-13
lines changed

profiling/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ perfcnt = "0.8.0"
4747
name = "stack_walking"
4848
harness = false
4949

50+
[[bench]]
51+
name = "concatenation"
52+
harness = false
53+
5054
[features]
5155
default = ["allocation_profiling", "timeline", "exception_profiling"]
5256
allocation_profiling = []

profiling/benches/concatenation.rs

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
3+
#[allow(unused)]
4+
fn extract_function_name_v1(module_name: &[u8], class_name: &[u8], method_name: &[u8]) -> Vec<u8> {
5+
let mut buffer = Vec::<u8>::new();
6+
7+
if !module_name.is_empty() {
8+
buffer.extend_from_slice(module_name);
9+
buffer.push(b'|');
10+
}
11+
12+
if !class_name.is_empty() {
13+
buffer.extend_from_slice(class_name);
14+
buffer.extend_from_slice(b"::");
15+
}
16+
buffer.extend_from_slice(method_name);
17+
buffer
18+
}
19+
20+
#[allow(unused)]
21+
fn extract_function_name_v2(module_name: &[u8], class_name: &[u8], method_name: &[u8]) -> Vec<u8> {
22+
let opt_module_separator: &[u8] = if module_name.is_empty() { b"" } else { b"|" };
23+
let opt_class_separator: &[u8] = if class_name.is_empty() { b"" } else { b"::" };
24+
let cap = module_name.len()
25+
+ opt_module_separator.len()
26+
+ class_name.len()
27+
+ opt_class_separator.len()
28+
+ method_name.len();
29+
let mut buffer = Vec::<u8>::with_capacity(cap);
30+
31+
buffer.extend_from_slice(module_name);
32+
buffer.extend_from_slice(opt_module_separator);
33+
buffer.extend_from_slice(class_name);
34+
buffer.extend_from_slice(opt_class_separator);
35+
buffer.extend_from_slice(method_name);
36+
buffer
37+
}
38+
39+
fn bench_concatenation_userland(c: &mut Criterion) {
40+
c.bench_function("bench_concatenation_userland", |b| {
41+
b.iter(|| {
42+
for _ in 1..=100 {
43+
_ = std::hint::black_box(extract_function_name_v2(
44+
b"",
45+
b"Twig\\Template",
46+
b"displayWithErrorHandling",
47+
))
48+
}
49+
});
50+
});
51+
}
52+
53+
fn bench_concatenation_internal1(c: &mut Criterion) {
54+
c.bench_function("bench_concatenation_internal1", |b| {
55+
b.iter(|| {
56+
for _ in 1..=100 {
57+
_ = std::hint::black_box(extract_function_name_v2(
58+
b"dom",
59+
b"DOMDocumentFragment",
60+
b"__construct",
61+
))
62+
}
63+
});
64+
});
65+
}
66+
67+
fn bench_concatenation_internal2(c: &mut Criterion) {
68+
c.bench_function("bench_concatenation_internal2", |b| {
69+
b.iter(|| {
70+
for _ in 1..=100 {
71+
_ = std::hint::black_box(extract_function_name_v2(
72+
b"standard",
73+
b"",
74+
b"file",
75+
))
76+
}
77+
});
78+
});
79+
}
80+
81+
criterion_group!(
82+
benches,
83+
bench_concatenation_userland,
84+
bench_concatenation_internal1,
85+
bench_concatenation_internal2,
86+
);
87+
criterion_main!(benches);

profiling/src/bindings/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,4 +694,7 @@ mod tests {
694694
mem::align_of::<*mut usize>()
695695
);
696696
}
697+
698+
#[test]
699+
fn test_hashmap_size_for_zend_extension() {}
697700
}

profiling/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ extern "C" fn minit(_type: c_int, module_number: c_int) -> ZendResult {
313313
};
314314

315315
/* Currently, the engine is always copying this struct. Every time a new
316-
* PHP version is released, we should double check zend_register_extension
316+
* PHP version is released, we should double-check zend_register_extension
317317
* to ensure the address is not mutated nor stored. Well, hopefully we
318318
* catch it _before_ a release.
319319
*/

profiling/src/profiling/stack_walking.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,27 @@ pub fn extract_function_name_into_buffer(func: &zend_function) -> Option<Vec<u8>
4949
return None;
5050
}
5151

52-
let mut buffer = Vec::<u8>::new();
53-
5452
// User functions do not have a "module". Maybe one day use composer info?
5553
let module_name = func.module_name().unwrap_or(b"");
56-
if !module_name.is_empty() {
57-
buffer.extend_from_slice(module_name);
58-
buffer.push(b'|');
59-
}
60-
6154
let class_name = func.scope_name().unwrap_or(b"");
62-
if !class_name.is_empty() {
63-
buffer.extend_from_slice(class_name);
64-
buffer.extend_from_slice(b"::");
65-
}
66-
55+
let opt_module_separator: &[u8] = if module_name.is_empty() { b"" } else { b"|" };
56+
let opt_class_separator: &[u8] = if class_name.is_empty() { b"" } else { b"::" };
57+
58+
// Precalculating the allocation size and removing the conditionals was
59+
// shown to be faster in criterion benchmarks.
60+
let cap = module_name.len()
61+
+ opt_module_separator.len()
62+
+ class_name.len()
63+
+ opt_class_separator.len()
64+
+ method_name.len();
65+
let mut buffer = Vec::<u8>::with_capacity(cap);
66+
67+
buffer.extend_from_slice(module_name);
68+
buffer.extend_from_slice(opt_module_separator);
69+
buffer.extend_from_slice(class_name);
70+
buffer.extend_from_slice(opt_class_separator);
6771
buffer.extend_from_slice(method_name);
72+
6873
Some(buffer)
6974
}
7075

0 commit comments

Comments
 (0)