【速度测试】SketchUp图元类型判断的速度对比
上一篇文章[SU-2022-04]中提到,使用 typename 来判断图元类型,由于涉及字符串的对比,执行效率较低,因而推荐使用 grep 或者 is_a? 方法。但是这两种判断方式在迭代图元的过程中的执行效率究竟差多少?为了解答这个问题,本文做一个实验测试一下。
一、图元类型判断的方法
判断一个图元是否属于某种类型有以下几种方法,第一种方法直接判断图元类型,这一种方法可用的语法很多,至少包括以下几种:
#令 ent 为一个图元
puts ent.typename == "Edge"
puts ent.is_a?(Sketchup::Edge)
puts ent.instance_of?(Sketchup::Edge)
puts Sketchup::Edge === ent
以上均能够判断图元 ent 是否是一个边线类图元,区别在于:第一种基于字符串的比较,等号左边可能是 "Edge"、 "Face" 或者 "Group" 等字符串,通过和右边的 "Edge" 对比确认 ent 是否为边线类。余下三种都是基于ruby类的判断,通过比较 ent 是否属于 Sketchup::Edge 类来判断。其中第三种方法中的 instance_of? 需要图元必须是 Sketchup::Edge 也不能为其子类,但是对于 SketchUp 模型的情况而言,不存在上述情况,所以可以认为是等价的。
第二种方法是判断其是否拥有某些成员,例如边线类都拥有长度 length 成员,平面类都拥有面积 area 成员:
#令 ent 为一个图元
puts ent.respond_to?(:length)
puts ent.methods.include?(:length)
这种方法的好处在于其清晰的目标导向性,判断图元类型的很大一部分原因是为了避免类型不符的图元抛出 NoMethodError 错误,从而导致脚本意外终止。并且,对于某些特殊的情况可以很方便的表达,例如需要同时选出组件、群组和图片这三类图元时,由于这三种图元对象都包括 :definition 方法,所以可以直接用以下方式判断:
puts ent.respond_to?(:definition)
第三种方法是直接不进行判断,将可能的抛出的异常用 begin rescue end 的方法来处理,这不是一个负责任的做法,但是某些时候可以用来跳过一些比较麻烦的筛选问题。
begin
# 不需要判断图元类型直接执行
rescue
# 如果报错了不会抛出异常而是执行这里的代码,这里也可以是空的
end
二、测试不同方法的运行速度
这三种方法具体又可以分为不同的实现路径,以下选取几种方法,分别测试以下几段代码,测试其执行速度:
#对比class的方法
GC.start
tz=Time.now().to_f
edge_longer_than_10_meters=0
Sketchup.active_model.entities.to_a.each{|ent|
if ent.class==Sketchup::Edge then
if ent.length>10.m then
edge_longer_than_10_meters+=1
end
end
}
puts "长度大于10米的边线图元数量:#{edge_longer_than_10_meters}"
puts "总耗时:#{Time.now().to_f-tz}秒"
#使用is_a?的方法
GC.start
tz=Time.now().to_f
edge_longer_than_10_meters=0
Sketchup.active_model.entities.to_a.each{|ent|
if ent.is_a?(Sketchup::Edge) then
if ent.length>10.m then
edge_longer_than_10_meters+=1
end
end
}
puts "长度大于10米的边线图元数量:#{edge_longer_than_10_meters}"
puts "总耗时:#{Time.now().to_f-tz}秒"
#对比typename的方法
GC.start
tz=Time.now().to_f
edge_longer_than_10_meters=0
Sketchup.active_model.entities.to_a.each{|ent|
if ent.typename=="Edge" then
if ent.length>10.m then
edge_longer_than_10_meters+=1
end
end
}
puts "长度大于10米的边线图元数量:#{edge_longer_than_10_meters}"
puts "总耗时:#{Time.now().to_f-tz}秒"
#使用grep的方法
GC.start
tz=Time.now().to_f
edge_longer_than_10_meters=0
Sketchup.active_model.entities.grep(Sketchup::Edge).each{|ent|
if ent.length>10.m then
edge_longer_than_10_meters+=1
end
}
puts "长度大于10米的边线图元数量:#{edge_longer_than_10_meters}"
puts "总耗时:#{Time.now().to_f-tz}秒"
#判断成员的方法
GC.start
tz=Time.now().to_f
edge_longer_than_10_meters=0
Sketchup.active_model.entities.to_a.each{|ent|
if ent.respond_to?(:length) then
if ent.length>10.m then
edge_longer_than_10_meters+=1
end
end
}
puts "长度大于10米的边线图元数量:#{edge_longer_than_10_meters}"
puts "总耗时:#{Time.now().to_f-tz}秒"
#不使用to_a的is_a?方法
GC.start
tz=Time.now().to_f
edge_longer_than_10_meters=0
Sketchup.active_model.entities.each{|ent|
if ent.is_a?(Sketchup::Edge) then
if ent.length>10.m then
edge_longer_than_10_meters+=1
end
end
}
puts "长度大于10米的边线图元数量:#{edge_longer_than_10_meters}"
puts "总耗时:#{Time.now().to_f-tz}秒"
#处理异常的方法
GC.start
tz=Time.now().to_f
edge_longer_than_10_meters=0
Sketchup.active_model.entities.to_a.each{|ent|
begin
if ent.length>10.m then
edge_longer_than_10_meters+=1
end
rescue
end
}
puts "长度大于10米的边线图元数量:#{edge_longer_than_10_meters}"
puts "总耗时:#{Time.now().to_f-tz}秒"
#使用while的方法
GC.start
tz=Time.now().to_f
edge_longer_than_10_meters=0
index=0
len=Sketchup.active_model.entities.length
while index<len do
ent=Sketchup.active_model.entities[index]
if ent.is_a?(Sketchup::Edge) then
if ent.length>10.m then
edge_longer_than_10_meters+=1
end
end
index+=1
end
puts "长度大于10米的边线图元数量:#{edge_longer_than_10_meters}"
puts "总耗时:#{Time.now().to_f-tz}秒"
三、测试结果
通过运行上一部分中的代码,测试结果如下图所示:
从测试结果来看,有以下几条结论:
(1)使用ruby对象类型的判断方法要比字符串判断方法节约30%左右的时间。
(2)在不涉及修改图元的情况下使用 while 语句非但没有提速,反而速度较慢,因次没有必要用它替代迭代器。
(3)判断是否拥有成员的 respond_to? 方法速度也与判断类型相近,因此对于上文所说的群组组件的判断,用此方法更为理想。
(4)有隔离影响功能的 to_a 方法会额外增加一小部分执行时间,因此使用 grep 方法既可以获得较高的执行效率,同时还可以做到隔离影响,是相对而言最合理的方法。
(5)处理异常的 rescue 语句,速度低于字符串的比较,这是毫不意外的,实现具体功能时还是应该尽量避免本身可控的不确定性。
(完)
本文编号:SU-2022-05