Best Practices: Structs vs Inline Components
Best Practices: Structs vs Inline Components
Performance guide for choosing between structs and components. Structs are ~16x faster but components offer type safety and validation.
Note: These performance characteristics apply to all component types (CFCs, sub-components, inline components).
Basic Examples
Struct Approach
// Simple data container
personData = {
personId: 123,
firstName: "Susi",
lastName: "Sorglos",
email: "susi.sorglos@lucee.org"
};
// Access data directly
fullName = personData.firstName & " " & personData.lastName;
Inline Component Approach
// Type-safe data container
personBean = new component accessors=true {
property name="personId" type="numeric";
property name="firstName" type="string";
property name="lastName" type="string";
property name="email" type="string";
function getFullName() {
return getFirstName() & " " & getLastName();
}
};
personBean.setPersonId(123);
personBean.setFirstName("Susi");
personBean.setLastName("Sorglos");
Bean Factory Approach
// Custom bean structure with getters/setters
function createBean(boolean readOnly=false) {
var bean = [:];
var args = arguments;
// Remove readOnly from arguments
var readOnly = arguments.readOnly;
structDelete(arguments, "readOnly");
loop struct=arguments index="local.k" item="local.v" {
// Create "private" value
bean["_" & k] = v;
// Getter - uses function name to determine property
bean["get" & ucFirst(k)] = function() {
var key = mid(getPageContext().getActiveUDFCalledName(), 4);
return bean["_" & key];
};
// Setter (if not read-only) - uses function name to determine property
if (!readOnly) {
bean["set" & ucFirst(k)] = function(val) {
var key = mid(getPageContext().getActiveUDFCalledName(), 4);
bean["_" & key] = val;
};
}
}
return bean;
}
// Usage
personBean = createBean(
readOnly: false,
personId: 123,
firstName: "Susi",
lastName: "Sorglos",
email: "susi.sorglos@lucee.org"
);
dump(personBean.getFirstName());
dump(personBean.getLastName());
Common Use Cases
High-Frequency Object Creation
// Recommended: Structs for query loops and bulk processing
loop query=qryPersons {
personRecord = {
id: qryPersons.id,
fullName: qryPersons.firstName & " " & qryPersons.lastName,
email: qryPersons.email,
status: "active"
};
processPerson(personRecord);
}
// Bean factory approach: Better performance than components, no metaspace overhead
loop query=qryPersons {
personBean = createBean(
readOnly: true,
id: qryPersons.id,
firstName: qryPersons.firstName,
lastName: qryPersons.lastName,
email: qryPersons.email
);
processPerson(personBean);
}
// Component approach: Slower and uses more memory
loop query=qryPersons {
personRecord = new component accessors=true {
property name="id" type="numeric";
property name="firstName" type="string";
property name="lastName" type="string";
property name="email" type="string";
};
// Set values and process
}
// Avoid: Components in high-frequency scenarios
loop query=qryPersons {
personRecord = new component accessors=true {
property name="id" type="numeric";
property name="fullName" type="string";
property name="email" type="string";
property name="status" type="string";
};
}
Configuration Objects
// Appropriate: Components for configuration with validation
config = new component accessors=true {
property name="host" type="string" required=true;
property name="port" type="numeric" default=3306;
property name="timeout" type="numeric" default=30000;
function validate() {
if (!len(trim(getHost()))) {
throw("Host is required");
}
return true;
}
};
Simple Data Transfer
// Recommended: Structs for API responses and data transfer
function getPersonData(personId) {
return {
personId: personId,
firstName: getFirstName(personId),
lastName: getLastName(personId),
email: getEmail(personId),
lastLogin: getLastLogin(personId)
};
}
Performance Testing
Basic Benchmark
function performanceTest() {
iterations = 100000;
// Query creation
var qryPersons = queryNew("id,firstName,lastName,email");
loop from=1 to=iterations index="local.idx" {
var row = queryAddRow(qryPersons);
querySetCell(qryPersons, "id", idx, row);
querySetCell(qryPersons, "firstName", "Susi" & idx, row);
querySetCell(qryPersons, "lastName", "Sorglos" & idx, row);
querySetCell(qryPersons, "email", "Susi" & idx & "@lucee.org", row);
}
// Bean factory function
function createBean(boolean readOnly=false) {
var bean = [:];
var args = arguments;
var readOnly = arguments.readOnly;
structDelete(arguments, "readOnly");
loop struct=arguments index="local.k" item="local.v" {
bean["_" & k] = v;
bean["get" & ucFirst(k)] = function() {
var key = mid(getPageContext().getActiveUDFCalledName(), 4);
return bean["_" & key];
};
if (!readOnly) {
bean["set" & ucFirst(k)] = function(val) {
var key = mid(getPageContext().getActiveUDFCalledName(), 4);
bean["_" & key] = val;
};
}
}
return bean;
}
// Test struct creation
start = getTickCount();
loop query=qryPersons {
x = {
id: qryPersons.id,
firstName: qryPersons.firstName,
lastName: qryPersons.lastName,
email: qryPersons.email
};
}
structTime = getTickCount() - start;
// Test bean factory creation
start = getTickCount();
loop query=qryPersons {
x = createBean(
readOnly: false,
id: qryPersons.id,
firstName: qryPersons.firstName,
lastName: qryPersons.lastName,
email: qryPersons.email
);
}
beanTime = getTickCount() - start;
// Test component creation
start = getTickCount();
loop query=qryPersons {
x = new component accessors=true {
property name="id" type="numeric";
property name="firstName" type="string";
property name="lastName" type="string";
property name="email" type="string";
};
}
componentTime = getTickCount() - start;
echo("Struct time: " & structTime & "ms<br>");
echo("Bean factory time: " & beanTime & "ms<br>");
echo("Component time: " & componentTime & "ms<br>");
echo("Bean vs Struct ratio: " & (beanTime/structTime) & "x<br>");
echo("Component vs Struct ratio: " & (componentTime/structTime) & "x<br>");
}
Performance Analysis Results
Based on testing with 100,000 iterations (multiple runs):
Execution Time Comparison
- Struct creation: ~20ms (19-23ms range)
- Bean factory creation: ~320ms (315-347ms range)
- Inline component creation: ~335ms (328-336ms range)
- Performance impact: Bean factories are ~16x slower than structs, components are ~17x slower than structs
Memory Usage Patterns
Struct Approach:
- Minimal memory overhead
- No class loading
- Lightweight objects
- Fast garbage collection
Bean Factory Approach:
- Moderate memory overhead (function closures only)
- No class loading required
- No metaspace allocation
- Slightly faster than components on average
Inline Component Approach:
- Creates physical class definitions
- Metaspace allocation for class metadata
- Component lifecycle overhead
- Highest memory consumption due to class creation
Key Findings
- Performance: Structs are dramatically faster (16-17x) than both bean factories and components
- Bean vs Component: Bean factories are slightly faster than components and have less memory overhead
- Memory Efficiency: Bean factories avoid class loading and metaspace allocation
- Closure vs Class Cost: Function closures are less expensive than class creation overhead
- Universal Impact: These performance characteristics apply to all component types in Lucee
Recommendations
Use Structs When:
- High-frequency object creation
- Performance is critical
- Simple data containers
- Temporary objects
- API responses
- Memory optimization needed
Use Inline Components When:
- Type safety is important
- Complex validation required
- Long-lived objects
- Configuration management
- Code clarity is priority
Note: The same performance considerations apply when choosing between structs and regular components (CFCs) or sub-components.
Use components of any type when the benefits of type safety, validation, and encapsulation outweigh the performance costs.
Testing Guidelines
For accurate performance testing:
- Use fresh JVM instances when possible
- Run multiple iterations
- Consider garbage collection impact
- Test with realistic data sizes
- Monitor both time and memory usage
- Run at least 30k rounds to warmup, before benchmarkings, to allow the JVM optimizations to be applied
Summary
Choose structs for performance-critical scenarios and simple data containers.
Use inline components when type safety and validation are more important than raw performance.
Consider hybrid approaches that combine the benefits of both.