Skip to content

Commit 7c3ff27

Browse files
authored
Optimize memory alloc and retained (#2441)
* Fix Cache in namespace and path Compile! skip non defined method Pattern, optimize capture_default Use delete_if instead of - * Revert root_prefix and to_regexp * Refactor path * Fix prefix, api string allocation * Drop AttributeTranslator in favor of OrderedOptions Manage Route regexp in Route class * Add cache for capture_index * Drop attribute_translator Remove useless alias * Fix all Rubocop Lint/MissingSuper Add changelog
1 parent 6fe78d1 commit 7c3ff27

22 files changed

+129
-205
lines changed

.rubocop_todo.yml

+1-11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# This configuration was generated by
22
# `rubocop --auto-gen-config`
3-
# on 2024-05-10 16:10:58 UTC using RuboCop version 1.63.2.
3+
# on 2024-05-20 14:55:33 UTC using RuboCop version 1.63.2.
44
# The point is for the user to remove these configuration records
55
# one by one as the offenses are removed from the code base.
66
# Note that changes in the inspected code, or installation of new
@@ -40,16 +40,6 @@ Lint/EmptyClass:
4040
Exclude:
4141
- 'lib/grape/dsl/parameters.rb'
4242

43-
# Offense count: 5
44-
# Configuration parameters: AllowedParentClasses.
45-
Lint/MissingSuper:
46-
Exclude:
47-
- 'lib/grape/api/instance.rb'
48-
- 'lib/grape/exceptions/validation_array_errors.rb'
49-
- 'lib/grape/namespace.rb'
50-
- 'lib/grape/path.rb'
51-
- 'lib/grape/router/pattern.rb'
52-
5343
# Offense count: 1
5444
# Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns.
5545
Metrics/MethodLength:

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
* [#2414](https://github.com/ruby-grape/grape/pull/2414): Fix Rack::Lint missing content-type - [@ericproulx](https://github.com/ericproulx).
4545
* [#2378](https://github.com/ruby-grape/grape/pull/2378): Do not overwrite `route_param` with a regular one if they share same name - [@arg](https://github.com/arg).
4646
* [#2444](https://github.com/ruby-grape/grape/pull/2444): Replace method_missing in endpoint - [@ericproulx](https://github.com/ericproulx).
47+
* [#2441](https://github.com/ruby-grape/grape/pull/2441): Optimize memory alloc and retained - [@ericproulx](https://github.com/ericproulx).
4748
* Your contribution here.
4849

4950
### 2.0.0 (2023/11/11)

lib/grape.rb

+2
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
require 'active_support/core_ext/hash/keys'
1717
require 'active_support/core_ext/hash/reverse_merge'
1818
require 'active_support/core_ext/hash/slice'
19+
require 'active_support/core_ext/module/delegation'
1920
require 'active_support/core_ext/object/blank'
2021
require 'active_support/core_ext/object/deep_dup'
2122
require 'active_support/core_ext/object/duplicable'
2223
require 'active_support/core_ext/string/output_safety'
2324
require 'active_support/core_ext/string/exclude'
2425
require 'active_support/deprecation'
2526
require 'active_support/inflector'
27+
require 'active_support/ordered_options'
2628
require 'active_support/notifications'
2729

2830
require 'English'

lib/grape/api/instance.rb

+2-1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ def evaluate_as_instance_with_configuration(block, lazy: false)
125125
end
126126

127127
def inherited(subclass)
128+
super
128129
subclass.reset!
129130
subclass.logger = logger.clone
130131
end
@@ -220,7 +221,7 @@ def add_head_not_allowed_methods_and_options_methods
220221

221222
def collect_route_config_per_pattern
222223
all_routes = self.class.endpoints.map(&:routes).flatten
223-
routes_by_regexp = all_routes.group_by { |route| route.pattern.to_regexp }
224+
routes_by_regexp = all_routes.group_by(&:pattern_regexp)
224225

225226
# Build the configuration based on the first endpoint and the collection of methods supported.
226227
routes_by_regexp.values.map do |routes|

lib/grape/dsl/routing.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def version(*args, &block)
3030
if args.any?
3131
options = args.extract_options!
3232
options = options.reverse_merge(using: :path)
33-
requested_versions = args.flatten
33+
requested_versions = args.flatten.map(&:to_s)
3434

3535
raise Grape::Exceptions::MissingVendorOption.new if options[:using] == :header && !options.key?(:vendor)
3636

@@ -54,7 +54,7 @@ def version(*args, &block)
5454

5555
# Define a root URL prefix for your entire API.
5656
def prefix(prefix = nil)
57-
namespace_inheritable(:root_prefix, prefix)
57+
namespace_inheritable(:root_prefix, prefix&.to_s)
5858
end
5959

6060
# Create a scope without affecting the URL.

lib/grape/endpoint.rb

+4-3
Original file line numberDiff line numberDiff line change
@@ -190,10 +190,11 @@ def prepare_default_route_attributes
190190
end
191191

192192
def prepare_version
193-
version = namespace_inheritable(:version) || []
193+
version = namespace_inheritable(:version)
194+
return unless version
194195
return if version.empty?
195196

196-
version.length == 1 ? version.first.to_s : version
197+
version.length == 1 ? version.first : version
197198
end
198199

199200
def merge_route_options(**default)
@@ -206,7 +207,7 @@ def map_routes
206207

207208
def prepare_path(path)
208209
path_settings = inheritable_setting.to_hash[:namespace_stackable].merge(inheritable_setting.to_hash[:namespace_inheritable])
209-
Path.prepare(path, namespace, path_settings)
210+
Path.new(path, namespace, path_settings)
210211
end
211212

212213
def namespace

lib/grape/exceptions/validation_array_errors.rb

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ class ValidationArrayErrors < Base
66
attr_reader :errors
77

88
def initialize(errors)
9+
super()
910
@errors = errors
1011
end
1112
end

lib/grape/namespace.rb

+3-2
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ def self.joined_space(settings)
3131
# Join the namespaces from a list of settings to create a path prefix.
3232
# @param settings [Array] list of Grape::Util::InheritableSettings.
3333
def self.joined_space_path(settings)
34-
Grape::Router.normalize_path(JoinedSpaceCache[joined_space(settings)])
34+
JoinedSpaceCache[joined_space(settings)]
3535
end
3636

3737
class JoinedSpaceCache < Grape::Util::Cache
3838
def initialize
39+
super
3940
@cache = Hash.new do |h, joined_space|
40-
h[joined_space] = -joined_space.join('/')
41+
h[joined_space] = Grape::Router.normalize_path(joined_space.join('/'))
4142
end
4243
end
4344
end

lib/grape/path.rb

+24-27
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,6 @@
33
module Grape
44
# Represents a path to an endpoint.
55
class Path
6-
def self.prepare(raw_path, namespace, settings)
7-
Path.new(raw_path, namespace, settings)
8-
end
9-
106
attr_reader :raw_path, :namespace, :settings
117

128
def initialize(raw_path, namespace, settings)
@@ -20,31 +16,27 @@ def mount_path
2016
end
2117

2218
def root_prefix
23-
split_setting(:root_prefix)
19+
settings[:root_prefix]
2420
end
2521

2622
def uses_specific_format?
27-
if settings.key?(:format) && settings.key?(:content_types)
28-
settings[:format] && Array(settings[:content_types]).size == 1
29-
else
30-
false
31-
end
23+
return false unless settings.key?(:format) && settings.key?(:content_types)
24+
25+
settings[:format] && Array(settings[:content_types]).size == 1
3226
end
3327

3428
def uses_path_versioning?
35-
if settings.key?(:version) && settings[:version_options] && settings[:version_options].key?(:using)
36-
settings[:version] && settings[:version_options][:using] == :path
37-
else
38-
false
39-
end
29+
return false unless settings.key?(:version) && settings[:version_options]&.key?(:using)
30+
31+
settings[:version] && settings[:version_options][:using] == :path
4032
end
4133

4234
def namespace?
43-
namespace&.match?(/^\S/) && namespace != '/'
35+
namespace&.match?(/^\S/) && not_slash?(namespace)
4436
end
4537

4638
def path?
47-
raw_path&.match?(/^\S/) && raw_path != '/'
39+
raw_path&.match?(/^\S/) && not_slash?(raw_path)
4840
end
4941

5042
def suffix
@@ -58,7 +50,7 @@ def suffix
5850
end
5951

6052
def path
61-
Grape::Router.normalize_path(PartsCache[parts])
53+
PartsCache[parts]
6254
end
6355

6456
def path_with_suffix
@@ -73,24 +65,29 @@ def to_s
7365

7466
class PartsCache < Grape::Util::Cache
7567
def initialize
68+
super
7669
@cache = Hash.new do |h, parts|
77-
h[parts] = -parts.join('/')
70+
h[parts] = Grape::Router.normalize_path(parts.join('/'))
7871
end
7972
end
8073
end
8174

8275
def parts
83-
parts = [mount_path, root_prefix].compact
84-
parts << ':version' if uses_path_versioning?
85-
parts << namespace.to_s
86-
parts << raw_path.to_s
87-
parts.flatten.reject { |part| part == '/' }
76+
[].tap do |parts|
77+
add_part(parts, mount_path)
78+
add_part(parts, root_prefix)
79+
parts << ':version' if uses_path_versioning?
80+
add_part(parts, namespace)
81+
add_part(parts, raw_path)
82+
end
8883
end
8984

90-
def split_setting(key)
91-
return if settings[key].nil?
85+
def add_part(parts, value)
86+
parts << value if value && not_slash?(value)
87+
end
9288

93-
settings[key].to_s.split('/')
89+
def not_slash?(value)
90+
value != '/'
9491
end
9592
end
9693
end

lib/grape/router.rb

+11-14
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,6 @@ def self.normalize_path(path)
1212
path
1313
end
1414

15-
def self.supported_methods
16-
@supported_methods ||= Grape::Http::Headers::SUPPORTED_METHODS + ['*']
17-
end
18-
1915
def initialize
2016
@neutral_map = []
2117
@neutral_regexes = []
@@ -28,13 +24,12 @@ def compile!
2824

2925
@union = Regexp.union(@neutral_regexes)
3026
@neutral_regexes = nil
31-
self.class.supported_methods.each do |method|
27+
(Grape::Http::Headers::SUPPORTED_METHODS + ['*']).each do |method|
28+
next unless map.key?(method)
29+
3230
routes = map[method]
33-
@optimized_map[method] = routes.map.with_index do |route, index|
34-
route.index = index
35-
Regexp.new("(?<_#{index}>#{route.pattern.to_regexp})")
36-
end
37-
@optimized_map[method] = Regexp.union(@optimized_map[method])
31+
optimized_map = routes.map.with_index { |route, index| route.to_regexp(index) }
32+
@optimized_map[method] = Regexp.union(optimized_map)
3833
end
3934
@compiled = true
4035
end
@@ -44,8 +39,10 @@ def append(route)
4439
end
4540

4641
def associate_routes(pattern, **options)
47-
@neutral_regexes << Regexp.new("(?<_#{@neutral_map.length}>)#{pattern.to_regexp}")
48-
@neutral_map << Grape::Router::GreedyRoute.new(pattern: pattern, index: @neutral_map.length, **options)
42+
Grape::Router::GreedyRoute.new(pattern: pattern, **options).then do |greedy_route|
43+
@neutral_regexes << greedy_route.to_regexp(@neutral_map.length)
44+
@neutral_map << greedy_route
45+
end
4946
end
5047

5148
def call(env)
@@ -145,11 +142,11 @@ def default_response
145142
end
146143

147144
def match?(input, method)
148-
@optimized_map[method].match(input) { |m| @map[method].detect { |route| m["_#{route.index}"] } }
145+
@optimized_map[method].match(input) { |m| @map[method].detect { |route| m[route.regexp_capture_index] } }
149146
end
150147

151148
def greedy_match?(input)
152-
@union.match(input) { |m| @neutral_map.detect { |route| m["_#{route.index}"] } }
149+
@union.match(input) { |m| @neutral_map.detect { |route| m[route.regexp_capture_index] } }
153150
end
154151

155152
def call_with_allow_headers(env, route)

lib/grape/router/attribute_translator.rb

-63
This file was deleted.

lib/grape/router/base_route.rb

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# frozen_string_literal: true
2+
3+
module Grape
4+
class Router
5+
class BaseRoute
6+
delegate_missing_to :@options
7+
8+
attr_reader :index, :pattern, :options
9+
10+
def initialize(**options)
11+
@options = ActiveSupport::OrderedOptions.new.update(options)
12+
end
13+
14+
alias attributes options
15+
16+
def regexp_capture_index
17+
CaptureIndexCache[index]
18+
end
19+
20+
def pattern_regexp
21+
pattern.to_regexp
22+
end
23+
24+
def to_regexp(index)
25+
@index = index
26+
Regexp.new("(?<#{regexp_capture_index}>#{pattern_regexp})")
27+
end
28+
29+
class CaptureIndexCache < Grape::Util::Cache
30+
def initialize
31+
super
32+
@cache = Hash.new do |h, index|
33+
h[index] = "_#{index}"
34+
end
35+
end
36+
end
37+
end
38+
end
39+
end

0 commit comments

Comments
 (0)