10000 [Routing] improve matching performance by using possesive quantifiers… · dirkaholic/symfony@4eee88f · GitHub
[go: up one dir, main page]

Skip to content

Commit 4eee88f

Browse files
Tobionfabpot
authored andcommitted
[Routing] improve matching performance by using possesive quantifiers when possible (closes symfony#5471)
My benchmarks showed a performance improvement of 20% when matching routes that make use of possesive quantifiers because it prevents backtracking when it's not needed
1 parent a3147e9 commit 4eee88f

File tree

6 files changed

+77
-69
lines changed

6 files changed

+77
-69
lines changed

src/Symfony/Component/Routing/RouteCompiler.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,14 @@ public function compile(Route $route)
7979
// part of {_format} when generating the URL, e.g. _format = 'mobile.html'.
8080
$nextSeparator = $this->findNextSeparator($followingPattern);
8181
$regexp = sprintf('[^/%s]+', '/' !== $nextSeparator && '' !== $nextSeparator ? preg_quote($nextSeparator, self::REGEX_DELIMITER) : '');
82+
if (('' !== $nextSeparator && !preg_match('#^\{\w+\}#', $followingPattern)) || '' === $followingPattern) {
83+
// When we have a separator, which is disallowed for the variable, we can optimize the regex with a possessive
84+
// quantifier. This prevents useless backtracking of PCRE and improves performance by 20% for matching those patterns.
85+
// Given the above example, there is no point in backtracking into {page} (that forbids the dot) when a dot must follow
86+
// after it. This optimization cannot be applied when the next char is no real separator or when the next variable is
87+
// directly adjacent, e.g. '/{x}{y}'.
88+
$regexp .= '+';
89+
}
8290
}
8391

8492
$tokens[] = array('variable', $isSeparator ? $precedingChar : '', $regexp, $varName);

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.apache

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,21 @@ RewriteCond %{REQUEST_URI} ^/foo/(baz|symfony)$
77
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:foo,E=_ROUTING_bar:%1,E=_ROUTING_DEFAULTS_def:test]
88

99
# foobar
10-
RewriteCond %{REQUEST_URI} ^/foo(?:/([^/]+))?$
10+
RewriteCond %{REQUEST_URI} ^/foo(?:/([^/]++))?$
1111
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:foobar,E=_ROUTING_bar:%1,E=_ROUTING_DEFAULTS_bar:toto]
1212

1313
# bar
14-
RewriteCond %{REQUEST_URI} ^/bar/([^/]+)$
14+
RewriteCond %{REQUEST_URI} ^/bar/([^/]++)$
1515
RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC]
1616
RewriteRule .* - [S=1,E=_ROUTING__allow_GET:1,E=_ROUTING__allow_HEAD:1]
17-
RewriteCond %{REQUEST_URI} ^/bar/([^/]+)$
17+
RewriteCond %{REQUEST_URI} ^/bar/([^/]++)$
1818
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:bar,E=_ROUTING_foo:%1]
1919

2020
# baragain
21-
RewriteCond %{REQUEST_URI} ^/baragain/([^/]+)$
21+
RewriteCond %{REQUEST_URI} ^/baragain/([^/]++)$
2222
RewriteCond %{REQUEST_METHOD} !^(GET|POST|HEAD)$ [NC]
2323
RewriteRule .* - [S=1,E=_ROUTING__allow_GET:1,E=_ROUTING__allow_POST:1,E=_ROUTING__allow_HEAD:1]
24-
RewriteCond %{REQUEST_URI} ^/baragain/([^/]+)$
24+
RewriteCond %{REQUEST_URI} ^/baragain/([^/]++)$
2525
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baragain,E=_ROUTING_foo:%1]
2626

2727
# baz
@@ -39,25 +39,25 @@ RewriteCond %{REQUEST_URI} ^/test/baz3/$
3939
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz3]
4040

4141
# baz4
42-
RewriteCond %{REQUEST_URI} ^/test/([^/]+)$
42+
RewriteCond %{REQUEST_URI} ^/test/([^/]++)$
4343
RewriteRule .* $0/ [QSA,L,R=301]
44-
RewriteCond %{REQUEST_URI} ^/test/([^/]+)/$
44+
RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$
4545
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz4,E=_ROUTING_foo:%1]
4646

4747
# baz5
48-
RewriteCond %{REQUEST_URI} ^/test/([^/]+)/$
48+
RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$
4949
RewriteCond %{REQUEST_METHOD} !^(GET|HEAD)$ [NC]
5050
RewriteRule .* - [S=2,E=_ROUTING__allow_GET:1,E=_ROUTING__allow_HEAD:1]
51-
RewriteCond %{REQUEST_URI} ^/test/([^/]+)$
51+
RewriteCond %{REQUEST_URI} ^/test/([^/]++)$
5252
RewriteRule .* $0/ [QSA,L,R=301]
53-
RewriteCond %{REQUEST_URI} ^/test/([^/]+)/$
53+
RewriteCond %{REQUEST_URI} ^/test/([^/]++)/$
5454
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz5,E=_ROUTING_foo:%1]
5555

5656
# baz5unsafe
57-
RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]+)/$
57+
RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]++)/$
5858
RewriteCond %{REQUEST_METHOD} !^(POST)$ [NC]
5959
RewriteRule .* - [S=1,E=_ROUTING__allow_POST:1]
60-
RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]+)/$
60+
RewriteCond %{REQUEST_URI} ^/testunsafe/([^/]++)/$
6161
RewriteRule .* app.php [QSA,L,E=_ROUTING__route:baz5unsafe,E=_ROUTING_foo:%1]
6262

6363
# baz6

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher1.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function match($pathinfo)
3131
}
3232

3333
// bar
34-
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
34+
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
3535
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
3636
$allow = array_merge($allow, array('GET', 'HEAD'));
3737
goto not_bar;
@@ -44,7 +44,7 @@ public function match($pathinfo)
4444
not_bar:
4545
< 10000 br>
4646
// barhead
47-
if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
47+
if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
4848
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
4949
$allow = array_merge($allow, array('GET', 'HEAD'));
5050
goto not_barhead;
@@ -72,14 +72,14 @@ public function match($pathinfo)
7272
}
7373

7474
// baz4
75-
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) {
75+
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
7676
$matches['_route'] = 'baz4';
7777

7878
return $matches;
7979
}
8080

8181
// baz5
82-
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) {
82+
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
8383
if ($this->context->getMethod() != 'POST') {
8484
$allow[] = 'POST';
8585
goto not_baz5;
@@ -92,7 +92,7 @@ public function match($pathinfo)
9292
not_baz5:
9393

9494
// baz.baz6
95-
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) {
95+
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
9696
if ($this->context->getMethod() != 'PUT') {
9797
$allow[] = 'PUT';
9898
goto not_bazbaz6;
@@ -124,14 +124,14 @@ public function match($pathinfo)
124124
if (0 === strpos($pathinfo, '/a')) {
125125
if (0 === strpos($pathinfo, '/a/b\'b')) {
126126
// foo1
127-
if (preg_match('#^/a/b\'b/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
127+
if (preg_match('#^/a/b\'b/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
128128
$matches['_route'] = 'foo1';
129129

130130
return $matches;
131131
}
132132

133133
// bar1
134-
if (preg_match('#^/a/b\'b/(?<bar>[^/]+)$#s', $pathinfo, $matches)) {
134+
if (preg_match('#^/a/b\'b/(?<bar>[^/]++)$#s', $pathinfo, $matches)) {
135135
$matches['_route'] = 'bar1';
136136

137137
return $matches;
@@ -148,14 +148,14 @@ public function match($pathinfo)
148148

149149
if (0 === strpos($pathinfo, '/a/b\'b')) {
150150
// foo2
151-
if (preg_match('#^/a/b\'b/(?<foo1>[^/]+)$#s', $pathinfo, $matches)) {
151+
if (preg_match('#^/a/b\'b/(?<foo1>[^/]++)$#s', $pathinfo, $matches)) {
152152
$matches['_route'] = 'foo2';
153153

154154
return $matches;
155155
}
156156

157157
// bar2
158-
if (preg_match('#^/a/b\'b/(?<bar1>[^/]+)$#s', $pathinfo, $matches)) {
158+
if (preg_match('#^/a/b\'b/(?<bar1>[^/]++)$#s', $pathinfo, $matches)) {
159159
$matches['_route'] = 'bar2';
160160

161161
return $matches;
@@ -167,7 +167,7 @@ public function match($pathinfo)
167167

168168
if (0 === strpos($pathinfo, '/multi')) {
169169
// helloWorld
170-
if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?<who>[^/]+))?$#s', $pathinfo, $matches)) {
170+
if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?<who>[^/]++))?$#s', $pathinfo, $matches)) {
171171
return array_merge($this->mergeDefaults($matches, array ( 'who' => 'World!',)), array('_route' => 'helloWorld'));
172172
}
173173

@@ -184,14 +184,14 @@ public function match($pathinfo)
184184
}
185185

186186
// foo3
187-
if (preg_match('#^/(?<_locale>[^/]+)/b/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
187+
if (preg_match('#^/(?<_locale>[^/]++)/b/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
188188
$matches['_route'] = 'foo3';
189189

190190
return $matches;
191191
}
192192

193193
// bar3
194-
if (preg_match('#^/(?<_locale>[^/]+)/b/(?<bar>[^/]+)$#s', $pathinfo, $matches)) {
194+
if (preg_match('#^/(?<_locale>[^/]++)/b/(?<bar>[^/]++)$#s', $pathinfo, $matches)) {
195195
$matches['_route'] = 'bar3';
196196

197197
return $matches;
@@ -203,7 +203,7 @@ public function match($pathinfo)
203203
}
204204

205205
// foo4
206-
if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
206+
if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
207207
$matches['_route'] = 'foo4';
208208

209209
return $matches;
@@ -217,14 +217,14 @@ public function match($pathinfo)
217217

218218
if (0 === strpos($pathinfo, '/a/b')) {
219219
// b
220-
if (preg_match('#^/a/b/(?<var>[^/]+)$#s', $pathinfo, $matches)) {
220+
if (preg_match('#^/a/b/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
221221
$matches['_route'] = 'b';
222222

223223
return $matches;
224224
}
225225

226226
// c
227-
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?<var>[^/]+)$#s', $pathinfo, $matches)) {
227+
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
228228
$matches['_route'] = 'c';
229229

230230
return $matches;

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher2.php

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function match($pathinfo)
3131
}
3232

3333
// bar
34-
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
34+
if (0 === strpos($pathinfo, '/bar') && preg_match('#^/bar/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
3535
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
3636
$allow = array_merge($allow, array('GET', 'HEAD'));
3737
goto not_bar;
@@ -44,7 +44,7 @@ public function match($pathinfo)
4444
not_bar:
4545

4646
// barhead
47-
if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
47+
if (0 === strpos($pathinfo, '/barhead') && preg_match('#^/barhead/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
4848
if (!in_array($this->context->getMethod(), array('GET', 'HEAD'))) {
4949
$allow = array_merge($allow, array('GET', 'HEAD'));
5050
goto not_barhead;
@@ -76,7 +76,7 @@ public function match($pathinfo)
7676
}
7777

7878
// baz4
79-
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/?$#s', $pathinfo, $matches)) {
79+
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/?$#s', $pathinfo, $matches)) {
8080
if (substr($pathinfo, -1) !== '/') {
8181
return $this->redirect($pathinfo.'/', 'baz4');
8282
}
@@ -87,7 +87,7 @@ public function match($pathinfo)
8787
}
8888

8989
// baz5
90-
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) {
90+
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
9191
if ($this->context->getMethod() != 'POST') {
9292
$allow[] = 'POST';
9393
goto not_baz5;
@@ -100,7 +100,7 @@ public function match($pathinfo)
100100
not_baz5:
101101

102102
// baz.baz6
103-
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]+)/$#s', $pathinfo, $matches)) {
103+
if (0 === strpos($pathinfo, '/test') && preg_match('#^/test/(?<foo>[^/]++)/$#s', $pathinfo, $matches)) {
104104
if ($this->context->getMethod() != 'PUT') {
105105
$allow[] = 'PUT';
106106
goto not_bazbaz6;
@@ -132,14 +132,14 @@ public function match($pathinfo)
132132
if (0 === strpos($pathinfo, '/a')) {
133133
if (0 === strpos($pathinfo, '/a/b\'b')) {
134134
// foo1
135-
if (preg_match('#^/a/b\'b/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
135+
if (preg_match('#^/a/b\'b/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
136136
$matches['_route'] = 'foo1';
137137

138138
return $matches;
139139
}
140140

141141
// bar1
142-
if (preg_match('#^/a/b\'b/(?<bar>[^/]+)$#s', $pathinfo, $matches)) {
142+
if (preg_match('#^/a/b\'b/(?<bar>[^/]++)$#s', $pathinfo, $matches)) {
143143
$matches['_route'] = 'bar1';
144144

145145
return $matches;
@@ -156,14 +156,14 @@ public function match($pathinfo)
156156

157157
if (0 === strpos($pathinfo, '/a/b\'b')) {
158158
// foo2
159-
if (preg_match('#^/a/b\'b/(?<foo1>[^/]+)$#s', $pathinfo, $matches)) {
159+
if (preg_match('#^/a/b\'b/(?<foo1>[^/]++)$#s', $pathinfo, $matches)) {
160160
$matches['_route'] = 'foo2';
161161

162162
return $matches;
163163
}
164164

165165
// bar2
166-
if (preg_match('#^/a/b\'b/(?<bar1>[^/]+)$#s', $pathinfo, $matches)) {
166+
if (preg_match('#^/a/b\'b/(?<bar1>[^/]++)$#s', $pathinfo, $matches)) {
167167
$matches['_route'] = 'bar2';
168168

169169
return $matches;
@@ -175,7 +175,7 @@ public function match($pathinfo)
175175

176176
if (0 === strpos($pathinfo, '/multi')) {
177177
// helloWorld
178-
if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?<who>[^/]+))?$#s', $pathinfo, $matches)) {
178+
if (0 === strpos($pathinfo, '/multi/hello') && preg_match('#^/multi/hello(?:/(?<who>[^/]++))?$#s', $pathinfo, $matches)) {
179179
return array_merge($this->mergeDefaults($matches, array ( 'who' => 'World!',)), array('_route' => 'helloWorld'));
180180
}
181181

@@ -196,14 +196,14 @@ public function match($pathinfo)
196196
}
197197

198198
// foo3
199-
if (preg_match('#^/(?<_locale>[^/]+)/b/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
199+
if (preg_match('#^/(?<_locale>[^/]++)/b/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
200200
$matches['_route'] = 'foo3';
201201

202202
return $matches;
203203
}
204204

205205
// bar3
206-
if (preg_match('#^/(?<_locale>[^/]+)/b/(?<bar>[^/]+)$#s', $pathinfo, $matches)) {
206+
if (preg_match('#^/(?<_locale>[^/]++)/b/(?<bar>[^/]++)$#s', $pathinfo, $matches)) {
207207
$matches['_route'] = 'bar3';
208208

209209
return $matches;
@@ -215,7 +215,7 @@ public function match($pathinfo)
215215
}
216216

217217
// foo4
218-
if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?<foo>[^/]+)$#s', $pathinfo, $matches)) {
218+
if (0 === strpos($pathinfo, '/aba') && preg_match('#^/aba/(?<foo>[^/]++)$#s', $pathinfo, $matches)) {
219219< B41A div class="diff-text-inner"> $matches['_route'] = 'foo4';
220220

221221
return $matches;
@@ -229,14 +229,14 @@ public function match($pathinfo)
229229

230230
if (0 === strpos($pathinfo, '/a/b')) {
231231
// b
232-
if (preg_match('#^/a/b/(?<var>[^/]+)$#s', $pathinfo, $matches)) {
232+
if (preg_match('#^/a/b/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
233233
$matches['_route'] = 'b';
234234

235235
return $matches;
236236
}
237237

238238
// c
239-
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?<var>[^/]+)$#s', $pathinfo, $matches)) {
239+
if (0 === strpos($pathinfo, '/a/b/c') && preg_match('#^/a/b/c/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
240240
$matches['_route'] = 'c';
241241

242242
return $matches;

src/Symfony/Component/Routing/Tests/Fixtures/dumper/url_matcher3.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public function match($pathinfo)
3232
}
3333

3434
// dynamic
35-
if (preg_match('#^/rootprefix/(?<var>[^/]+)$#s', $pathinfo, $matches)) {
35+
if (preg_match('#^/rootprefix/(?<var>[^/]++)$#s', $pathinfo, $matches)) {
3636
$matches['_route'] = 'dynamic';
3737

3838
return $matches;

0 commit comments

Comments
 (0)
0