Skip to content

Commit da9138d

Browse files
Performance Improvement: Replacing get_interned_obj_prop with get_obj_prop (#214)
* Explore removing string interning * Update example_with_targets/src/main.rs Co-authored-by: Jeffrey Charles <jeff.charles@shopify.com> --------- Co-authored-by: Jeffrey Charles <jeff.charles@shopify.com>
1 parent 0c25465 commit da9138d

6 files changed

Lines changed: 131 additions & 17 deletions

File tree

example_with_targets/cart.graphql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
query Input {
2+
cartLines {
3+
id
4+
quantity
5+
title
6+
}
7+
}

example_with_targets/schema.graphql

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@ A void type that can be used to return a null value from a mutation.
6161
"""
6262
scalar Void
6363

64+
"""
65+
A cart line representing an item in the cart.
66+
"""
67+
type CartLine {
68+
id: ID!
69+
quantity: Int!
70+
title: String!
71+
}
72+
6473
"""
6574
The input object for the function.
6675
"""
@@ -77,6 +86,7 @@ type Input {
7786
optionalArray: [String!]
7887
optionalArrayOfArrays: [[String!]!]
7988
optionalArrayOfOptionalArrays: [[String!]]
89+
cartLines: [CartLine!] @restrictTarget(only: ["test.target-cart"])
8090
}
8191

8292
"""
@@ -102,6 +112,16 @@ type MutationRoot {
102112
"""
103113
result: FunctionTargetBResult!
104114
): Void!
115+
116+
"""
117+
The function for API target Cart.
118+
"""
119+
targetCart(
120+
"""
121+
The result of calling the function for API target Cart.
122+
"""
123+
result: FunctionTargetCartResult!
124+
): Void!
105125
}
106126

107127
"""
@@ -119,6 +139,13 @@ input FunctionTargetBResult {
119139
operations: [Operation!]!
120140
}
121141

142+
"""
143+
The result of API target Cart.
144+
"""
145+
input FunctionTargetCartResult {
146+
totalQuantity: Int!
147+
}
148+
122149
input Operation @oneOf {
123150
doThis: This
124151
doThat: That

example_with_targets/src/main.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ mod schema {
1515

1616
#[query("./b.graphql")]
1717
pub mod target_b {}
18+
19+
#[query("./cart.graphql")]
20+
pub mod target_cart {}
1821
}
1922

2023
#[shopify_function]
@@ -45,6 +48,20 @@ fn target_panic(_input: schema::target_a::Input) -> Result<schema::FunctionTarge
4548
panic!("Something went wrong");
4649
}
4750

51+
#[shopify_function]
52+
fn target_cart(input: schema::target_cart::Input) -> Result<schema::FunctionTargetCartResult> {
53+
// Iterate over cart lines and sum quantities - this accesses the `quantity` property
54+
// multiple times, which would demonstrate any benefit from interning strings
55+
let total_quantity: i32 = input
56+
.cart_lines()
57+
.unwrap_or(&[])
58+
.iter()
59+
.map(|line| *line.quantity())
60+
.sum();
61+
62+
Ok(schema::FunctionTargetCartResult { total_quantity })
63+
}
64+
4865
fn main() {
4966
log!("Invoke a named export");
5067
process::abort()

integration_tests/src/lib.rs

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,19 @@ use std::{
66
sync::LazyLock,
77
};
88

9+
/// Result from running a Shopify Function
10+
#[derive(Debug)]
11+
pub struct RunResult {
12+
/// The JSON output from the function
13+
pub output: serde_json::Value,
14+
/// The logs from the function
15+
pub logs: String,
16+
/// The number of instructions executed
17+
pub instructions: u64,
18+
/// The memory usage in bytes
19+
pub memory_usage: u64,
20+
}
21+
922
const FUNCTION_RUNNER_VERSION: &str = "9.1.0";
1023
const TRAMPOLINE_VERSION: &str = "2.0.0";
1124

@@ -148,11 +161,7 @@ pub fn prepare_example(name: &str) -> Result<PathBuf> {
148161
Ok(trampolined_path)
149162
}
150163

151-
pub fn run_example(
152-
path: PathBuf,
153-
export: &str,
154-
input: serde_json::Value,
155-
) -> Result<(serde_json::Value, String)> {
164+
pub fn run_example(path: PathBuf, export: &str, input: serde_json::Value) -> Result<RunResult> {
156165
let function_runner_path = FUNCTION_RUNNER_PATH
157166
.as_ref()
158167
.map_err(|e| anyhow::anyhow!("Failed to download function runner: {}", e))?;
@@ -198,6 +207,16 @@ pub fn run_example(
198207
.ok_or_else(|| anyhow::anyhow!("Logs are not a string"))?
199208
.to_string();
200209

210+
let instructions = output
211+
.get("instructions")
212+
.and_then(|v| v.as_u64())
213+
.ok_or_else(|| anyhow::anyhow!("No instructions count"))?;
214+
215+
let memory_usage = output
216+
.get("memory_usage")
217+
.and_then(|v| v.as_u64())
218+
.ok_or_else(|| anyhow::anyhow!("No memory_usage"))?;
219+
201220
if !status.success() {
202221
anyhow::bail!(
203222
"Function runner returned non-zero exit code: {}, logs: {}",
@@ -210,5 +229,11 @@ pub fn run_example(
210229
.get_mut("output")
211230
.ok_or_else(|| anyhow::anyhow!("No output"))?
212231
.take();
213-
Ok((output_json, logs))
232+
233+
Ok(RunResult {
234+
output: output_json,
235+
logs,
236+
instructions,
237+
memory_usage,
238+
})
214239
}

integration_tests/tests/integration_test.rs

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,18 @@ fn test_example_with_targets_target_a() -> Result<()> {
1616
"name": "test",
1717
"country": "CA"
1818
});
19-
let (output, logs) = run_example(path.clone(), "target_a", input)?;
19+
let result = run_example(path.clone(), "target_a", input)?;
2020
assert_eq!(
21-
output,
21+
result.output,
2222
serde_json::json!({
2323
"status": 200
2424
})
2525
);
26-
assert_eq!(logs, "In target_a\nWith var: 42\nWith var: 42\n");
26+
assert_eq!(result.logs, "In target_a\nWith var: 42\nWith var: 42\n");
27+
eprintln!(
28+
"target_a: instructions={}, memory_usage={}",
29+
result.instructions, result.memory_usage
30+
);
2731
Ok(())
2832
}
2933

@@ -36,9 +40,9 @@ fn test_example_with_targets_target_b() -> Result<()> {
3640
"id": "gid://shopify/Order/1234567890",
3741
"targetAResult": 200
3842
});
39-
let (output, logs) = run_example(path.clone(), "target_b", input)?;
43+
let result = run_example(path.clone(), "target_b", input)?;
4044
assert_eq!(
41-
output,
45+
result.output,
4246
serde_json::json!({
4347
"name": "new name: \"gid://shopify/Order/1234567890\"",
4448
"operations": [
@@ -55,7 +59,11 @@ fn test_example_with_targets_target_b() -> Result<()> {
5559
]
5660
})
5761
);
58-
assert_eq!(logs, "In target_b\n");
62+
assert_eq!(result.logs, "In target_b\n");
63+
eprintln!(
64+
"target_b: instructions={}, memory_usage={}",
65+
result.instructions, result.memory_usage
66+
);
5967
Ok(())
6068
}
6169

@@ -72,10 +80,43 @@ fn test_example_with_panic() -> Result<()> {
7280
.unwrap_err()
7381
.to_string();
7482
let expected_err =
75-
"Function runner returned non-zero exit code: exit status: 1, logs: panicked at example_with_targets/src/main.rs:45:5:\nSomething went wrong\nerror while executing at wasm backtrace:";
83+
"Function runner returned non-zero exit code: exit status: 1, logs: panicked at example_with_targets/src/main.rs:48:5:\nSomething went wrong\nerror while executing at wasm backtrace:";
7684
assert!(
7785
err.contains(expected_err),
7886
"Expected error message to contain:\n`{expected_err}`\nbut was:\n`{err}`"
7987
);
8088
Ok(())
8189
}
90+
91+
#[test]
92+
fn test_example_with_targets_target_cart() -> Result<()> {
93+
let path = EXAMPLE_WITH_TARGETS_RESULT
94+
.as_ref()
95+
.map_err(|e| anyhow::anyhow!("Failed to prepare example: {}", e))?;
96+
// Create input with 100 cart lines to demonstrate interned string benefits
97+
let cart_lines: Vec<_> = (0..100)
98+
.map(|i| {
99+
serde_json::json!({
100+
"id": format!("gid://shopify/CartLine/{i}"),
101+
"quantity": i + 1,
102+
"title": format!("Product {i}")
103+
})
104+
})
105+
.collect();
106+
let input = serde_json::json!({
107+
"cartLines": cart_lines
108+
});
109+
let result = run_example(path.clone(), "target_cart", input)?;
110+
// Sum of 1 + 2 + ... + 100 = 5050
111+
assert_eq!(
112+
result.output,
113+
serde_json::json!({
114+
"totalQuantity": 5050
115+
})
116+
);
117+
eprintln!(
118+
"target_cart (100 lines): instructions={}, memory_usage={}",
119+
result.instructions, result.memory_usage
120+
);
121+
Ok(())
122+
}

shopify_function_macro/src/lib.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,11 +349,8 @@ impl CodeGenerator for ShopifyFunctionCodeGenerator {
349349
parse_quote! {
350350
#description
351351
pub fn #field_name_ident(&self) -> #field_type {
352-
static INTERNED_FIELD_NAME: shopify_function::wasm_api::CachedInternedStringId = shopify_function::wasm_api::CachedInternedStringId::new(#field_name_lit_str, );
353-
let interned_string_id = INTERNED_FIELD_NAME.load();
354-
355352
let value = self.#field_name_ident.get_or_init(|| {
356-
let value = self.__wasm_value.get_interned_obj_prop(interned_string_id);
353+
let value = self.__wasm_value.get_obj_prop(#field_name_lit_str);
357354
shopify_function::wasm_api::Deserialize::deserialize(&value).unwrap()
358355
});
359356
let value_ref = &value;

0 commit comments

Comments
 (0)