Ruby 多线程时序与 cpu 核心数

Ruby Multithreading timing versus cpu core count

我编写了以下代码来测试 Ruby 中的单线程与多线程。我循环一个包含两个元素的数组并按顺序休眠 1 秒(~ 2 秒执行时间)。然后我做同样的事情,但每次都会分叉该过程(执行时间约为 1 秒,因为两个睡眠命令是 运行ning 并行执行的)。这对我来说很有意义。

随着我增加数组的大小,即更多的迭代,我注意到多线程总是在大约 1 秒内返回。我在 VirtualBox 的 Ubuntu 实例上 运行ning 并且有 1 个处理单元可供我使用:

root@vbox-docker:~/docker-projects/hello-world/lib# nproc
1

我的假设是,随着迭代次数的增加,多线程将花费更长的时间,因为所有处理单元都已被消耗。我唯一能想到的另一件事是时钟速度正在同时推动这些线程中的许多线程,我需要更多地增加迭代才能看到多线程端的速度变慢。

我的问题是:我的想法是否正确?我的逻辑有意义吗?这是查看单线程应用程序与多线程应用程序结果的准确测试吗?

查看下面的 Ruby 脚本,运行 如果您愿意,请提供反馈。

提前致谢!

编辑:将迭代设置为 500 并将睡眠设置为 5 秒导致多线程部分需要 22 秒并发生变化。有意思。

#!/usr/bin/ruby

#Do a single threaded run, timing each iteration of the array [0,1], doing two iterations because of two elements
before = Time.now
[0,1].each do |i|
  sleep 1
end
after = Time.now
delta = (after - before) * 1000.0
puts "Single-threaded: #{delta} milliseconds" #Should be 1000 miliseconds X number of elements in the array (2)

#Do a multi threaded run, timing each iterationg of the array [0,1], doing two iterations because of the two elements
before = Time.now
[0,1].each do |i|
  fork do #Instead of running each operation sequentially, run each process in a different thread
    sleep 1
  end
end
Process.waitall
after = Time.now
delta = (after - before) * 1000.0
puts "Multi-threaded: #{delta} milliseconds" #Should be 1000 miliseconds X 1 while array length < CPU Core count

要衡量并发性,您需要做一些工作。这是一个计算 Fibbinaci 数列的实现(以一种故意缓慢的方式)。

require 'thread'
require 'benchmark'

def fibbinaci(n=33)
  return n if n <= 1
  fibbinaci(n-1) + fibbinaci(n-2)
end

LOOPS = 5

Benchmark.bm do |x|
  x.report("Single threaded") do
    LOOPS.times { fibbinaci }
  end

  x.report("Multithreaded") do
    LOOPS.times.map do
      Thread.new { fibbinaci }
    end.each(&:join)
  end

  x.report("Forked") do
    LOOPS.times do
      fork do
        fibbinaci
      end
    end
    Process.waitall
  end unless RUBY_PLATFORM == "java"
end

这给出了类似的东西:

$ ruby fib.rb
                 user       system     total    real
Single threaded  4.050000   0.000000   4.050000 (  4.054188)
Multithreaded    4.100000   0.000000   4.100000 (  4.114595)
Forked           0.000000   0.000000   4.000000 (  2.054361)

这是预期的 - Ruby 使用绿色线程,这意味着单个 Ruby 进程一次不能消耗超过 1 个 CPU 核心。我的机器有 2 个内核,所以它 运行 分叉时速度大约是原来的两倍(允许分叉开销)。

如果我 运行 在 JRuby 下 确实 有本机线程(即实际进程内并发),我会得到如下信息:

$ ruby fib.rb
                user        system    total     real
Single threaded 27.850000   0.100000  27.950000 ( 27.812978)
Multithreaded   27.870000   0.060000  27.930000 ( 14.355506)

进程内线程确实将任务的 运行时间减半(不过,哎呀,这似乎是 JRuby 特别不擅长的)。

您可能想知道为什么 Ruby 不能在每个进程使用多个核心时提供线程 - 这是因为在等待 IO 时您仍然可以跨线程工作!如果您的应用程序花费大量时间与网络套接字通信(例如,进行数据库查询),那么您将在阻塞套接字时让其他线程工作,从而从多线程中看到并发收益。