Enable cop Performance/OpenStruct
Description of the proposal
Ban all occurrences of OpenStruct
in non-spec code by enabling Performance/OpenStruct
.
This cop checks for
OpenStruct.new
calls. Instantiation of anOpenStruct
invalidates Ruby global method cache (see📓 2️⃣ ) as it causes dynamic method definition during program runtime. This could have an effect on performance, especially in case of single-threaded applications with multipleOpenStruct
instantiations.
Using Struct.new(..., keyword_init: true)
is much faster than
OpenStruct.new(...)
as it does not define methods during runtime.
Misspelled method names raise NoMethodError
instead of silently
returning nil
.
This cop is not auto-correctable and not every offense can be replaced with Struct
easily.
Current offenses
Example:
ee/lib/gitlab/graphql/aggregations/epics/epic_node.rb:44:26: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
OpenStruct.new({
^^^
gitlab-org/gitlab
Offenses:
Guardfile:12:14: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
OpenStruct.new(to_s: "spec").tap do |rspec|
^^^
Guardfile:22:14: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
OpenStruct.new.tap do |rails|
^^^
app/finders/snippets_finder.rb:50:26: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
@params = OpenStruct.new(params)
^^^
app/helpers/application_settings_helper.rb:40:74: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
Gitlab.config.repositories.storages.keys.each_with_object(OpenStruct.new) do |storage, weights|
^^^
app/models/cycle_analytics/project_level_stage_adapter.rb:16:43: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
serializer.new.represent(OpenStruct.new(
^^^
ee/lib/gitlab/graphql/aggregations/epics/epic_node.rb:33:26: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
OpenStruct.new({
^^^
ee/lib/gitlab/graphql/aggregations/epics/epic_node.rb:44:26: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
OpenStruct.new({
^^^
lib/api/wikis.rb:137:32: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
present OpenStruct.new(result[:result]), with: Entities::WikiAttachment
^^^
lib/gitlab/ci/ansi2html.rb:283:22: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
OpenStruct.new(
^^^
lib/gitlab/ci/reports/test_suite_comparer.rb:93:24: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
OpenStruct.new(
^^^
lib/gitlab/git/diff_collection.rb:34:20: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
OpenStruct.new(limits)
^^^
lib/gitlab/import_export/after_export_strategies/base_after_export_strategy.rb:20:33: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
@options = OpenStruct.new(attributes)
^^^
lib/gitlab/testing/request_inspector_middleware.rb:43:30: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
request = OpenStruct.new(
^^^
lib/mattermost/session.rb:67:31: C: Performance/OpenStruct: Consider using Struct over OpenStruct to optimize the performance.
@request ||= OpenStruct.new(parameters: params)
^^^
22247 files inspected, 14 offenses detected
Benchmarks
Script
require "benchmark/ips"
require "ostruct"
struct = Struct.new(:a, :b, :c, :d, :e, keyword_init: true)
args = { a: 1, b: 2, c: 3, d: 4, e: 5 }
Benchmark.ips do |x|
x.report "openstruct new" do
OpenStruct.new(args)
end
x.report "openstruct access" do
ostruct = OpenStruct.new(args)
ostruct.a
ostruct.b
ostruct.c
ostruct.d
ostruct.e
end
x.report "struct new" do
struct.new(args)
end
x.report "struct acccess" do
s = struct.new(args)
s.a
s.b
s.c
s.d
s.e
end
x.compare!
end
Ruby 2.7.2
ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-linux]
Warming up --------------------------------------
openstruct new 139.065k i/100ms
openstruct access 7.325k i/100ms
struct new 253.568k i/100ms
struct acccess 204.764k i/100ms
Calculating -------------------------------------
openstruct new 1.286M (± 4.1%) i/s - 6.536M in 5.090813s
openstruct access 80.426k (± 2.7%) i/s - 402.875k in 5.013021s
struct new 2.836M (± 2.9%) i/s - 14.200M in 5.011973s
struct acccess 2.219M (± 4.2%) i/s - 11.262M in 5.085001s
Comparison:
struct new: 2835598.8 i/s
struct acccess: 2218940.2 i/s - 1.28x (± 0.00) slower
openstruct new: 1286202.5 i/s - 2.20x (± 0.00) slower
openstruct access: 80426.2 i/s - 35.26x (± 0.00) slower
Ruby 3.0.0
ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-linux]
Warming up --------------------------------------
openstruct new 9.826k i/100ms
openstruct access 8.316k i/100ms
struct new 320.240k i/100ms
struct acccess 256.925k i/100ms
Calculating -------------------------------------
openstruct new 98.372k (± 1.3%) i/s - 501.126k in 5.095116s
openstruct access 83.194k (± 1.1%) i/s - 424.116k in 5.098618s
struct new 3.219M (± 0.6%) i/s - 16.332M in 5.073638s
struct acccess 2.544M (± 1.9%) i/s - 12.846M in 5.051045s
Comparison:
struct new: 3219147.6 i/s
struct acccess: 2544267.1 i/s - 1.27x (± 0.00) slower
openstruct new: 98372.2 i/s - 32.72x (± 0.00) slower
openstruct access: 83193.5 i/s - 38.69x (± 0.00) slower
OpenStruct.new
is much slower on Ruby 3.0.0 than Ruby 2.7.2
Check-list
-
Mention this proposal in the relevant Slack channels (e.g. #development
,#backend
,#frontend
) - [-] If there is a choice to make between two potential styles, set up an emoji vote in the MR:
- CHOICE_A:
🅰 - CHOICE_B:
🅱 - Vote yourself for both choices so that people know these are the choices
- CHOICE_A:
-
The MR doesn't have significant objections, and is getting a majority of 👍 vs👎 (remember that we don't need to reach a consensus) - [-] (If applicable) One style is getting a majority of vote (compared to the other choice)
- [-] (If applicable) Update the MR with the chosen style
-
Follow the review process as usual