Skip to content

Commit 3df0128

Browse files
committed
Added fast closure
Closure interface approach can filter faster than raw Perl. Docs updated
1 parent e01b4f4 commit 3df0128

File tree

8 files changed

+1262
-75
lines changed

8 files changed

+1262
-75
lines changed

Changes

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,39 @@
11
Revision history for Params-Filter
22

3+
v0.011 2026-01-26 (STABLE RELEASE)
4+
- NEW: Added closure interface with make_filter() function for maximum performance
5+
- make_filter() creates optimized, reusable closures that can be 20-25% faster than
6+
hand-written Perl filtering code due to pre-computed exclusion lookups and
7+
specialized closure variants
8+
- Three specialized closure variants:
9+
* Required-only (empty accepted list) - 3.1M ops/sec
10+
* Wildcard (accepted contains '*') - 1.3M ops/sec, 24% faster than raw Perl
11+
* Accepted-specific (normal case) - 1.8M ops/sec, 20% faster than raw Perl
12+
- OPTIMIZED: filter() function now uses same optimizations as closure interface:
13+
* Pre-computed exclusion hash (O(1) lookups instead of O(n) array search)
14+
* Hash slice for required field copying (faster bulk operations)
15+
* Non-destructive operations with hash lookups instead of delete
16+
* Single wildcard check instead of per-iteration checks
17+
- All interfaces (functional, OO, closure) now use identical optimization techniques
18+
- Closure interface is 142-239% faster than functional interface due to:
19+
* No input parsing overhead (hashref-only input)
20+
* No error message construction overhead
21+
* Pre-compiled closures optimized for specific configuration
22+
- Added comprehensive test suite for make_filter() (t/04-make_filter.t with 10 subtests)
23+
- Added complete POD documentation for closure interface
24+
- Added usage examples (examples/closure_interface.pl with 6 working examples)
25+
- Added benchmark scripts:
26+
* benchmark-three-variants.pl - Performance comparison of three closure types
27+
* benchmark-optimized-filter.pl - Optimized filter() vs make_filter() comparison
28+
* benchmark-make_filter.pl - Closure vs raw Perl performance
29+
- Updated README.md with closure interface documentation and performance notes
30+
- Updated performance considerations to clarify that closure interface provides
31+
maximum speed while functional/OO have feature overhead
32+
- Changed "database queries" to "database statements" for accuracy (includes inserts/updates)
33+
- All 73 tests passing across 6 test files
34+
- POD syntax validated
35+
- Full backward compatibility maintained
36+
337
v0.010 2026-01-25 (STABLE RELEASE)
438
- Updated documentation to emphasize security, compliance, and correctness
539
over raw performance

MANIFEST

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ examples/realistic-benchmark.pl
1515
examples/strict_construction.pl
1616
examples/test-fastpath.pl
1717
examples/wildcard.pl
18+
examples/closure_interface.pl
19+
examples/benchmark-all-interfaces.pl
20+
examples/benchmark-make_filter.pl
21+
examples/benchmark-vs-raw.pl
22+
examples/CALL_CHAIN_ANALYSIS.md
23+
examples/CALL_CHAIN_VISUALIZATION.txt
24+
benchmark-make_filter.pl
25+
benchmark-optimized-filter.pl
26+
benchmark-three-variants.pl
27+
test-make_filter.pl
1828
lib/Params/Filter.pm
1929
Makefile.PL
2030
MANIFEST
@@ -25,3 +35,5 @@ t/00-load.t
2535
t/01-functional.t
2636
t/02-oo-interface.t
2737
t/03-modifier-methods.t
38+
t/04-make_filter.t
39+
t/benchmark.t

README.md

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,20 @@ This module separates field filtering from value validation:
1616
The main advantages of using Params::Filter are:
1717

1818
- **Consistency** - Converts varying incoming data formats to consistent key-value pairs
19-
- **Security** - Sensitive fields (passwords, SSNs, credit cards) never reach your validation code or database queries
19+
- **Security** - Sensitive fields (passwords, SSNs, credit cards) never reach your validation code or database statements
2020
- **Compliance** - Automatically excludes fields that shouldn't be processed or stored (e.g., GDPR, PCI-DSS)
2121
- **Correctness** - Ensures only expected fields are processed, preventing accidental data leakage or processing errors
2222
- **Maintainability** - Clear separation between data filtering (what fields to accept) and validation (whether values are correct)
2323

2424
### Performance Considerations
2525

26-
**Important**: In many cases, Params::Filter is slower than manual hash lookups or similar operations, especially when the incoming data is in a known consistent format. The value of Params::Filter is in its capability to assure the security, compliance, and correctness benefits listed above.
26+
**Important**: The functional and OO interfaces include features that add overhead compared to manual hash lookups, especially when the incoming data is in a known consistent format. The value of Params::Filter is in its capability to assure the security, compliance, and correctness benefits listed above.
2727

28-
Nonetheless, Params::Filter CAN improve overall performance when downstream validation is expensive (database queries, API calls, complex regex).
28+
However, the **Closure Interface** (`make_filter`) provides maximum performance and can be faster than hand-written Perl filtering code due to pre-computed exclusion lookups and specialized closure variants. Use `make_filter` for hot code paths or high-frequency filtering.
2929

30-
A simple benchmark comparing the validation cost of typical input to that of input restricted to required fields would reveal any speed gain with expensive downstream validations.
30+
For all interfaces, Params::Filter CAN improve overall performance when downstream validation is expensive (database statements, API calls, complex regex) by failing fast when required fields are missing.
3131

32-
For simple cases and when validation is cheap, raw speed is not the reason to use Params::Filter.
32+
A simple benchmark comparing the validation cost of typical input to that of input restricted to required fields would reveal any speed gain with expensive downstream validations.
3333

3434
### Common Use Cases
3535

@@ -87,7 +87,7 @@ This module provides important security benefits by separating data filtering fr
8787

8888
#### Preventing Sensitive Data Leakage
8989

90-
By excluding sensitive fields early in the request processing pipeline, you ensure they never reach validation code, database queries, or logging systems:
90+
By excluding sensitive fields early in the request processing pipeline, you ensure they never reach validation code, database statements, or logging systems:
9191

9292
```perl
9393
my $user_filter = Params::Filter->new_filter({
@@ -210,10 +210,48 @@ my ($user2, $msg2) = $user_filter->apply($api_request_data);
210210
my ($user3, $msg3) = $user_filter->apply($db_record_data);
211211
```
212212

213+
### Closure Interface (Maximum Speed)
214+
215+
```perl
216+
use Params::Filter qw/make_filter/;
217+
218+
# Create a reusable filter closure
219+
my $fast_filter = make_filter(
220+
[qw(id username)], # required
221+
[qw(email bio)], # accepted
222+
[qw(password token)], # excluded
223+
);
224+
225+
# Apply to high-volume data stream
226+
for my $record (@large_dataset) {
227+
my $filtered = $fast_filter->($record);
228+
next unless $filtered; # Skip if required fields missing
229+
process($filtered);
230+
}
231+
232+
# Wildcard example - accept everything except sensitive fields
233+
my $safe_filter = make_filter(
234+
[qw(id type)],
235+
['*'], # accept all other fields
236+
[qw(password token ssn)], # but exclude these
237+
);
238+
239+
# Safe logging - sensitive fields automatically excluded
240+
my $log_entry = $safe_filter->($incoming_data);
241+
log_to_file($log_entry); # Passwords, tokens, SSNs never logged
242+
```
243+
244+
The closure interface provides maximum performance for hot code paths and high-frequency filtering operations. It only accepts data in the form of a hashref. It creates a specialized, optimized closure based on your configuration:
245+
246+
- **Required-only** - When accepted list is empty, returns only required fields
247+
- **Wildcard** - When accepted contains `'*'`, accepts all input fields except exclusions
248+
- **Accepted-specific** - When accepted has specific fields, returns required plus those accepted fields (minus exclusions)
249+
213250
## Features
214251

215-
- **Dual interface**: Functional or OO usage
252+
- **Three interfaces**: Functional, OO, or Closure (for maximum speed)
216253
- **Security-first**: Excludes sensitive fields before they reach validation code
254+
- **Performance**: Closure interface can be faster than hand-written Perl filtering
217255
- **Fail-closed**: Returns immediately on missing required parameters
218256
- **Early validation**: Returns immediately if all required parameters are provided and no others are provided or will be accepted
219257
- **Non-destructive**: Allows multiple filters and conditional use without affecting data
@@ -796,6 +834,7 @@ NOTE: The original `$data` is not modified during filtering, so the same data ca
796834
See the `examples/` directory for complete working scripts:
797835
- `basic_usage.pl` - Simple form input filtering
798836
- `oo_interface.pl` - Reusable filters
837+
- `closure_interface.pl` - High-performance closure interface
799838
- `wildcard.pl` - Wildcard acceptance patterns
800839
- `error_handling.pl` - Various error handling strategies
801840
- `debug_mode.pl` - Development-time warnings

benchmark-optimized-filter.pl

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
#!/usr/bin/env perl
2+
use v5.40;
3+
use strict;
4+
use warnings;
5+
use Benchmark qw(:all);
6+
7+
use Params::Filter qw/filter make_filter/;
8+
9+
say "=" x 80;
10+
say "Benchmark: Optimized filter() vs make_filter() Closure";
11+
say "=" x 80;
12+
say "";
13+
say "Testing if optimized functional approach matches closure performance";
14+
say "";
15+
16+
# Test data
17+
my $test_data = {
18+
id => 123,
19+
name => 'Alice',
20+
email => '[email protected]',
21+
phone => '555-1234',
22+
city => 'NYC',
23+
password => 'secret',
24+
extra => 'ignored',
25+
};
26+
27+
# ============================================================================
28+
# Scenario 1: Required-only (no accepted fields)
29+
# ============================================================================
30+
say "=" x 80;
31+
say "SCENARIO 1: Required-only (no accepted fields)";
32+
say "=" x 80;
33+
say "Config: required=['id','name','email'], accepted=[], excluded=[]";
34+
say "";
35+
36+
my @req1 = qw/id name email/;
37+
my @ok1 = ();
38+
my @no1 = ();
39+
40+
my $closure1 = make_filter(\@req1, \@ok1, \@no1);
41+
42+
cmpthese(-5, {
43+
'filter() functional' => sub {
44+
my $result = filter($test_data, \@req1, \@ok1, \@no1);
45+
},
46+
'make_filter() closure' => sub {
47+
my $result = $closure1->($test_data);
48+
},
49+
});
50+
51+
say "";
52+
53+
# ============================================================================
54+
# Scenario 2: Wildcard (accept all except exclusions)
55+
# ============================================================================
56+
say "=" x 80;
57+
say "SCENARIO 2: Wildcard (accept all except exclusions)";
58+
say "=" x 80;
59+
say "Config: required=['id','name'], accepted=['*'], excluded=['password']";
60+
say "";
61+
62+
my @req2 = qw/id name/;
63+
my @ok2 = ('*');
64+
my @no2 = qw/password extra/;
65+
66+
my $closure2 = make_filter(\@req2, \@ok2, \@no2);
67+
68+
cmpthese(-5, {
69+
'filter() functional' => sub {
70+
my $result = filter($test_data, \@req2, \@ok2, \@no2);
71+
},
72+
'make_filter() closure' => sub {
73+
my $result = $closure2->($test_data);
74+
},
75+
});
76+
77+
say "";
78+
79+
# ============================================================================
80+
# Scenario 3: Accepted-specific (normal case)
81+
# ============================================================================
82+
say "=" x 80;
83+
say "SCENARIO 3: Accepted-specific (normal case)";
84+
say "=" x 80;
85+
say "Config: required=['id','name','email'], accepted=['phone','city'], excluded=['password']";
86+
say "";
87+
88+
my @req3 = qw/id name email/;
89+
my @ok3 = qw/phone city/;
90+
my @no3 = qw/password/;
91+
92+
my $closure3 = make_filter(\@req3, \@ok3, \@no3);
93+
94+
cmpthese(-5, {
95+
'filter() functional' => sub {
96+
my $result = filter($test_data, \@req3, \@ok3, \@no3);
97+
},
98+
'make_filter() closure' => sub {
99+
my $result = $closure3->($test_data);
100+
},
101+
});
102+
103+
say "";
104+
105+
# ============================================================================
106+
# Scenario 4: Multiple filters (simulating high-volume processing)
107+
# ============================================================================
108+
say "=" x 80;
109+
say "SCENARIO 4: High-volume processing (100K operations)";
110+
say "=" x 80;
111+
say "Applying 3 different filters to same data";
112+
say "";
113+
114+
cmpthese(-5, {
115+
'filter() functional' => sub {
116+
my $r1 = filter($test_data, \@req1, \@ok1, \@no1);
117+
my $r2 = filter($test_data, \@req2, \@ok2, \@no2);
118+
my $r3 = filter($test_data, \@req3, \@ok3, \@no3);
119+
},
120+
'make_filter() closure' => sub {
121+
my $r1 = $closure1->($test_data);
122+
my $r2 = $closure2->($test_data);
123+
my $r3 = $closure3->($test_data);
124+
},
125+
});
126+
127+
say "";
128+
129+
# ============================================================================
130+
# Correctness verification
131+
# ============================================================================
132+
say "=" x 80;
133+
say "CORRECTNESS VERIFICATION";
134+
say "=" x 80;
135+
say "";
136+
137+
my $f1 = filter($test_data, \@req1, \@ok1, \@no1);
138+
my $c1 = $closure1->($test_data);
139+
140+
say "Scenario 1 (required-only):";
141+
say " filter(): ", join(", ", sort keys %$f1);
142+
say " make_filter(): ", join(", ", sort keys %$c1);
143+
say " Match: ", (join(" ", sort keys %$f1) eq join(" ", sort keys %$c1) ? "" : "");
144+
say "";
145+
146+
my $f2 = filter($test_data, \@req2, \@ok2, \@no2);
147+
my $c2 = $closure2->($test_data);
148+
149+
say "Scenario 2 (wildcard):";
150+
say " filter(): ", join(", ", sort keys %$f2);
151+
say " make_filter(): ", join(", ", sort keys %$c2);
152+
say " Match: ", (join(" ", sort keys %$f2) eq join(" ", sort keys %$c2) ? "" : "");
153+
say "";
154+
155+
my $f3 = filter($test_data, \@req3, \@ok3, \@no3);
156+
my $c3 = $closure3->($test_data);
157+
158+
say "Scenario 3 (accepted-specific):";
159+
say " filter(): ", join(", ", sort keys %$f3);
160+
say " make_filter(): ", join(", ", sort keys %$c3);
161+
say " Match: ", (join(" ", sort keys %$f3) eq join(" ", sort keys %$c3) ? "" : "");
162+
say "";
163+
164+
say "=" x 80;
165+
say "ANALYSIS";
166+
say "=" x 80;
167+
say "";
168+
say "Key insights:";
169+
say " • Both interfaces now use identical optimization techniques";
170+
say " • Pre-computed exclusion hash (O(1) lookups)";
171+
say " • Hash slice for required field copying";
172+
say " • Non-destructive operations";
173+
say " • Single wildcard check";
174+
say "";
175+
say "Performance differences expected:";
176+
say " • filter() has overhead for input parsing";
177+
say " • filter() builds error messages each call";
178+
say " • make_filter() closures are pre-compiled";
179+
say "";
180+
say "Use filter() when you need:";
181+
say " - Input format flexibility (hashref, arrayref, scalar)";
182+
say " - Detailed error messages";
183+
say " - One-time filtering operations";
184+
say "";
185+
say "Use make_filter() when you need:";
186+
say " - Maximum performance";
187+
say " - Reusable filters";
188+
say " - High-frequency filtering (hot code paths)";
189+
say "";

0 commit comments

Comments
 (0)