当前位置: 首页 > 知识库问答 >
问题:

为什么#地图比#每一个效率更高?

别宏盛
2023-03-14
require 'benchmark'

array = (1..100000).to_a

puts Benchmark.measure {
  100.times do
    array.map { |el| el.even? }
  end
}

puts Benchmark.measure {
  100.times do
    new_array = []
    array.each do |el| 
      new_array << el.even? 
    end
  end
}

# ruby bench.rb
# 0.450598   0.015524   0.466122 (  0.466802)
# 0.496796   0.018525   0.515321 (  0.516196)
puts Benchmark.measure {
  100.times do
    array.select { |el| el.even? }
  end
}

puts Benchmark.measure {
  100.times do
    new_array = []
    array.each do |el| 
      if el.even? 
         new_array << el
      end
    end
  end
}

# ruby bench.rb
# 0.405254   0.007965   0.413219 (  0.413733)
# 0.471416   0.008875   0.480291 (  0.481079)

那么为什么这些更精确的方法会产生明显更好的性能呢?这是Ruby和/或所有语言中的通用公理吗?

共有1个答案

佘辰龙
2023-03-14

在您的两个示例中,第二段代码分配的内存是第一段代码的100倍。它还对数组执行大约log_1.5(100)的大小调整(假设是一个增长因子为1.5的动态数组的标准教科书实现)。调整数组的大小是昂贵的(分配一个新的内存块,然后将所有元素的O(n)副本放入新的内存块)。一般说来,垃圾收集器讨厌变异,他们在收集大量短命的小对象时要比保持几个长命的大对象更有效率。

换句话说,在第一个示例中,您分别测量数组#map数组#select,而在第二个示例中,您不仅测量数组#each,还测量数组#<<以及数组大小调整和内存分配。无法从基准测试的结果中判断出哪一个对基准测试的结果有多大的贡献。就像Zed Shaw曾经说过的那样:“如果你想衡量一件事,那么就不要去衡量其他的狗屎”。

但是,即使您在基准测试中修复了该错误,一般而言,较专门的操作比一般操作具有更多的可用信息,因此较一般的操作通常不会比专门的操作更快。

在您的特定示例中,它可能只是一些非常简单的东西,例如,您使用的Ruby实现不是很好地优化Ruby代码(例如YARV,与例如TruffleRuby不同),同时却有数组#map数组#select的优化本机实现(同样,以YARV为例,它有这两种实现的C实现,通常不能很好地优化Ruby代码)。

最后,编写正确的微基准很难。真的,真的,真的很难。我鼓励阅读和理解机械-同情邮件列表上的整个讨论线程:JMH vs Caliper:reference thread。虽然它是专门关于Java基准测试(实际上是关于JVM基准测试)的,但许多论点适用于任何现代高性能OO执行引擎,如Rubinius、TruffleRuby等,在较小程度上也适用于YARV。注意,大部分讨论都是关于编写微基准测试工具,而不是编写微基准测试本身,也就是说,它是关于编写允许开发人员编写正确的微基准测试的框架,而不需要知道这些东西,但不幸的是,即使使用最好的微基准测试工具(Ruby的benchmark实际上并不是一个很好的工具),您仍然需要对现代编译器、垃圾收集器、执行引擎、CPU、硬件体系结构以及统计数据有非常深入的了解。

这里有一个失败的基准测试的很好的例子,对于未受过训练的基准测试编写者来说可能并不明显:为什么打印“b”比打印“#”显著地慢?。

 类似资料:
  • 问题内容: 什么是i 或 i效率更高? 我只在Java和C / C ++中使用了此语言,但是我实际上是在要求实现此语言的所有语言。 在大学里,我有一位教授向我们展示了++ i效率更高,但这已经有几年了,我想从Stack Overflow社区中获取意见。 问题答案: i ++: 创建我的临时副本 增加我 返回临时副本 ++ i: 增加我 还给我 启用优化后,最终的程序集很可能是相同的,但是++ i效

  • 问题内容: 我有两种方法可以读取字符串并创建Character对象: 和 当我使用18554760字符串运行方法时,我得到的运行时间截然不同。我得到的输出是: 使用较小的输入(4,638,690个字符)时,时间没有变化。 在这种情况下,为什么新的效率更高? 编辑: 我的基准代码很hacky。 问题答案: TL; DR部分 好消息 您的测量确实显示出真实的效果。 坏消息 它之所以这样做是偶然的,因为

  • 我正在编写C++来解决这个问题,从leetcode:https://leetcode.com/problems/remove-element/ 给定数组nums和值val,移除该值的所有实例并返回新的长度。 不要为另一个数组分配额外的空间,您必须用O(1)个额外内存修改输入数组。 元素的顺序可以更改。你在新长度之外留下什么并不重要。 您的函数应该返回长度=2,nums的前两个元素为2。 在返回长度

  • 为什么这是一个好主意呢?您通过使用这种方法获得了什么好处?在某些情况下,这会是一个好主意吗?在每个存储库方法调用实例化时,您是否可以使用这种技术做一些不能做的事情?

  • 为什么首先这是一个好主意?通过使用这种方法,您获得了哪些优势?在某些情况下,这是一个好主意吗?在每个存储库方法调用实例化s时,使用此技术是否可以执行一些不能执行的操作?

  • 下面的代码分别调用两个简单的函数100亿次。 我的假设是这两个调用的性能几乎相同。如果有的话,我会猜到传递两个参数会比传递一个稍微慢一些。鉴于所有参数都是对象引用,我并不期望其中一个参数是列表这一事实会产生任何差异。 我运行了多次测试,一个典型的结果是“12781/30536”。换句话说,使用两个字符串的调用需要13秒,使用列表的调用需要30秒。 更新 这不是一个公平的测试,原因很多。然而,它确实