Ruby if 语句优化重构最佳实践
Ruby if statement optimization refactor best practice
我这里遇到了一个很常见的重构情况,在浏览了一些博客之后,我仍然没有得到任何令人满意的评论;所以在这里问一个问题。
h = {
a: 'a',
b: 'b'
}
new_hash = {}
new_hash[:a] = h[:a].upcase if h[:a].present?
据我的朋友说,这段代码可以通过以下方式重构以提高性能。
a = h[:a]
new_hash[:a] = a.upcase if a.present?
乍一看,它看起来有点优化。但它会产生很大的不同还是过度优化?应该首选哪种样式?
Looking for an expert advice :)
更新 Benchmark n = 1000
user system total real
hash lookup 0.000000 0.000000 0.000000 ( 0.000014)
new var 0.000000 0.000000 0.000000 ( 0.000005)
AND op 0.000000 0.000000 0.000000 ( 0.000018)
try 0.000000 0.000000 0.000000 ( 0.000046)
更新 Memory Benchmark
使用 gem benchmark-memory
Calculating -------------------------------------
hash lookup 40.000 memsize ( 40.000 retained)
1.000 objects ( 1.000 retained)
1.000 strings ( 1.000 retained)
new var 0.000 memsize ( 0.000 retained)
0.000 objects ( 0.000 retained)
0.000 strings ( 0.000 retained)
AND op 40.000 memsize ( 40.000 retained)
1.000 objects ( 1.000 retained)
1.000 strings ( 1.000 retained)
try 200.000 memsize ( 40.000 retained)
5.000 objects ( 1.000 retained)
1.000 strings ( 1.000 retained)
优化穿不同的鞋子,有内存优化、性能优化,还有可读性和代码结构。
性能:对速度和性能几乎没有任何影响,因为在 O(1) 中访问散列。尝试使用 benchmark
看看自己几乎没有区别
You can check this article about hash lookup and why it's so fast
内存:你朋友的代码没有你的优化,因为他初始化了另一个对象a
而你的没有。
可读性和风格: 乍一看,您朋友的代码行数更少,描述性更强。但请记住,您可能需要对散列中的每个 key/value 执行此操作,因此您可能需要 a
、b
,并且它会随着散列的进行而继续(当它发生这种情况时,当然最好迭代散列)。在这里看太多了 tbh
根据您的情况 rails 方法,例如 present?
可能很脏并且肯定会影响性能。如果您只关心 nil
检查而不关心空 Array
或空白 String
之类的东西,那么使用纯 ruby 方法会 "much" 更快(引号是为了强调在这个基本示例中性能完全无关紧要的事实)
因为我们正在对事物进行基准测试。
设置
h = {
a: 'a',
b: 'b'
}
class Object
def present?
!blank?
end
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
end
def hash_lookup(h)
new_hash = {}
new_hash[:a] = h[:a].upcase if h[:a].present?
new_hash
end
def new_var(h)
new_hash = {}
a = h[:a]
new_hash[:a] = a.upcase if a.present?
new_hash
end
def hash_lookup_w_safe_nav(h)
new_hash = {}
new_hash[:a] = h[:a]&.upcase
new_hash
end
def hash_lookup_wo_rails(h)
new_hash = {}
new_hash[:a] = h[:a].upcase if h[:a]
new_hash
end
def new_var_wo_rails(h)
new_hash = {}
a = h[:a]
new_hash[:a] = a.upcase if a
new_hash
end
基准测试
N = [1_000,10_000,100_000]
require 'benchmark'
N.each do |n|
puts "OVER #{n} ITERATIONS"
Benchmark.bm do |x|
x.report(:new_var) { n.times {new_var(h)}}
x.report(:hash_lookup) { n.times {hash_lookup(h)}}
x.report(:hash_lookup_w_safe_nav) { n.times {hash_lookup_w_safe_nav(h)}}
x.report(:hash_lookup_wo_rails) { n.times {hash_lookup_wo_rails(h)}}
x.report(:new_var_wo_rails) { n.times {new_var_wo_rails(h)}}
end
end
输出
OVER 1000 ITERATIONS
user system total real
new_var 0.001075 0.000159 0.001234 ( 0.001231)
hash_lookup 0.002441 0.000000 0.002441 ( 0.002505)
hash_lookup_w_safe_nav 0.001077 0.000000 0.001077 ( 0.001077)
hash_lookup_wo_rails 0.001100 0.000000 0.001100 ( 0.001145)
new_var_wo_rails 0.001015 0.000000 0.001015 ( 0.001016)
OVER 10000 ITERATIONS
user system total real
new_var 0.010321 0.000000 0.010321 ( 0.010329)
hash_lookup 0.010104 0.000015 0.010119 ( 0.010123)
hash_lookup_w_safe_nav 0.007211 0.000000 0.007211 ( 0.007213)
hash_lookup_wo_rails 0.007508 0.000000 0.007508 ( 0.017302)
new_var_wo_rails 0.008186 0.000026 0.008212 ( 0.016679)
OVER 100000 ITERATIONS
user system total real
new_var 0.099400 0.000249 0.099649 ( 0.192481)
hash_lookup 0.101419 0.000009 0.101428 ( 0.199788)
hash_lookup_w_safe_nav 0.078156 0.000010 0.078166 ( 0.140796)
hash_lookup_wo_rails 0.078743 0.000000 0.078743 ( 0.166815)
new_var_wo_rails 0.073271 0.000000 0.073271 ( 0.125869)
我这里遇到了一个很常见的重构情况,在浏览了一些博客之后,我仍然没有得到任何令人满意的评论;所以在这里问一个问题。
h = {
a: 'a',
b: 'b'
}
new_hash = {}
new_hash[:a] = h[:a].upcase if h[:a].present?
据我的朋友说,这段代码可以通过以下方式重构以提高性能。
a = h[:a]
new_hash[:a] = a.upcase if a.present?
乍一看,它看起来有点优化。但它会产生很大的不同还是过度优化?应该首选哪种样式?
Looking for an expert advice :)
更新 Benchmark n = 1000
user system total real
hash lookup 0.000000 0.000000 0.000000 ( 0.000014)
new var 0.000000 0.000000 0.000000 ( 0.000005)
AND op 0.000000 0.000000 0.000000 ( 0.000018)
try 0.000000 0.000000 0.000000 ( 0.000046)
更新 Memory Benchmark
使用 gem benchmark-memory
Calculating -------------------------------------
hash lookup 40.000 memsize ( 40.000 retained)
1.000 objects ( 1.000 retained)
1.000 strings ( 1.000 retained)
new var 0.000 memsize ( 0.000 retained)
0.000 objects ( 0.000 retained)
0.000 strings ( 0.000 retained)
AND op 40.000 memsize ( 40.000 retained)
1.000 objects ( 1.000 retained)
1.000 strings ( 1.000 retained)
try 200.000 memsize ( 40.000 retained)
5.000 objects ( 1.000 retained)
1.000 strings ( 1.000 retained)
优化穿不同的鞋子,有内存优化、性能优化,还有可读性和代码结构。
性能:对速度和性能几乎没有任何影响,因为在 O(1) 中访问散列。尝试使用 benchmark
看看自己几乎没有区别
You can check this article about hash lookup and why it's so fast
内存:你朋友的代码没有你的优化,因为他初始化了另一个对象a
而你的没有。
可读性和风格: 乍一看,您朋友的代码行数更少,描述性更强。但请记住,您可能需要对散列中的每个 key/value 执行此操作,因此您可能需要 a
、b
,并且它会随着散列的进行而继续(当它发生这种情况时,当然最好迭代散列)。在这里看太多了 tbh
根据您的情况 rails 方法,例如 present?
可能很脏并且肯定会影响性能。如果您只关心 nil
检查而不关心空 Array
或空白 String
之类的东西,那么使用纯 ruby 方法会 "much" 更快(引号是为了强调在这个基本示例中性能完全无关紧要的事实)
因为我们正在对事物进行基准测试。
设置
h = {
a: 'a',
b: 'b'
}
class Object
def present?
!blank?
end
def blank?
respond_to?(:empty?) ? !!empty? : !self
end
end
def hash_lookup(h)
new_hash = {}
new_hash[:a] = h[:a].upcase if h[:a].present?
new_hash
end
def new_var(h)
new_hash = {}
a = h[:a]
new_hash[:a] = a.upcase if a.present?
new_hash
end
def hash_lookup_w_safe_nav(h)
new_hash = {}
new_hash[:a] = h[:a]&.upcase
new_hash
end
def hash_lookup_wo_rails(h)
new_hash = {}
new_hash[:a] = h[:a].upcase if h[:a]
new_hash
end
def new_var_wo_rails(h)
new_hash = {}
a = h[:a]
new_hash[:a] = a.upcase if a
new_hash
end
基准测试
N = [1_000,10_000,100_000]
require 'benchmark'
N.each do |n|
puts "OVER #{n} ITERATIONS"
Benchmark.bm do |x|
x.report(:new_var) { n.times {new_var(h)}}
x.report(:hash_lookup) { n.times {hash_lookup(h)}}
x.report(:hash_lookup_w_safe_nav) { n.times {hash_lookup_w_safe_nav(h)}}
x.report(:hash_lookup_wo_rails) { n.times {hash_lookup_wo_rails(h)}}
x.report(:new_var_wo_rails) { n.times {new_var_wo_rails(h)}}
end
end
输出
OVER 1000 ITERATIONS
user system total real
new_var 0.001075 0.000159 0.001234 ( 0.001231)
hash_lookup 0.002441 0.000000 0.002441 ( 0.002505)
hash_lookup_w_safe_nav 0.001077 0.000000 0.001077 ( 0.001077)
hash_lookup_wo_rails 0.001100 0.000000 0.001100 ( 0.001145)
new_var_wo_rails 0.001015 0.000000 0.001015 ( 0.001016)
OVER 10000 ITERATIONS
user system total real
new_var 0.010321 0.000000 0.010321 ( 0.010329)
hash_lookup 0.010104 0.000015 0.010119 ( 0.010123)
hash_lookup_w_safe_nav 0.007211 0.000000 0.007211 ( 0.007213)
hash_lookup_wo_rails 0.007508 0.000000 0.007508 ( 0.017302)
new_var_wo_rails 0.008186 0.000026 0.008212 ( 0.016679)
OVER 100000 ITERATIONS
user system total real
new_var 0.099400 0.000249 0.099649 ( 0.192481)
hash_lookup 0.101419 0.000009 0.101428 ( 0.199788)
hash_lookup_w_safe_nav 0.078156 0.000010 0.078166 ( 0.140796)
hash_lookup_wo_rails 0.078743 0.000000 0.078743 ( 0.166815)
new_var_wo_rails 0.073271 0.000000 0.073271 ( 0.125869)