我正在尝试为Project Euler的问题14编写一个多线程解决方案,但我并没有真正看到加速。没有任何共享资源,也没有使用Mutex锁...由于上下文切换,我的代码是否很慢?我没有正确理解线程的好处吗?
http://projecteuler.net/problem=14
先谢谢!
require 'benchmark'
benchmark_results = Benchmark.measure do
threads = []
num_threads = 10
num_threads.times do |thread_num|
threads << Thread.new(thread_num + 1) do |thread_num|
Thread.current["max_length"] = 0
(thread_num..1000000).step(num_threads).each do |i|
next if i.even?
current = i
length = 0
until current == 1
if current.even?
current = current / 2
else
current = current * 3 + 1
end
length += 1
end
if length > Thread.current["max_length"]
Thread.current["max_length"] = length
Thread.current["max_i"] = i
end
end
end
end
threads.each { |thread| thread.join; print "#{thread['max_i']} -> #{thread['max_length']}\n" }
end
puts benchmark_results
答案 0 :(得分:2)
据我所知,大多数ruby实现不使用实际线程(操作系统级别)或仅使用某种锁定,因此这些实现将无法从多个内核/处理器线程中受益。 (见http://en.wikibooks.org/wiki/Ruby_Programming/Reference/Objects/Thread)
如果您的应用程序受CPU限制,那么应该有效地阻止您从线程中获益。另一方面,如果它是IO绑定,则线程可能会有所帮助。 (然后,当一个绿色线程等待IO时,其他绿色线程可以使用您分配的CPU时间。但是,在物理上,仍然只使用一个CPU内核。)
答案 1 :(得分:1)
Ruby有许多不同的实现。最常提到的是MRI(见other question。
MRI有线程,但遗憾的是一次只使用一个CPU核心。这意味着:当时只有一个线程实际运行。
如果你的线程必须等待IO发生,那么可能会加速。因为如果一个线程必须等待,另一个线程可以赶上。但是你的问题一直需要CPU。
我建议调查另一个像JRuby这样的Ruby实现来解决这类问题。 JRuby有真正的线程。
如果您更改实施,也许您的速度会更快。在你重复计算每一max_length
次的那一刻。例如:n = 4
的序列长度为3
。如果您为length
计算n = 8
,则执行一步(n / 2
)而不是current
4
,您就会知道n = 4
1}}有length = 3
:因此length(8) = 1 + length(4) = 1 + 4 = 5
。例如:
class CollatzSequence
def initialize
@lengths = Hash.new { |h, n| cache_length(h, n) }
end
def length(n)
@lengths[n]
end
private
def cache_length(h, n)
if n <= 1
h[n] = 1
else
next_in_seqence = n.even? ? (n / 2) : (n * 3 + 1)
h[n] = 1 + h[next_in_seqence]
end
end
end
require 'benchmark'
sequencer = CollatzSequence.new
Benchmark.bm(10) do |bm|
bm.report('not cached') { sequencer.length(837799) }
bm.report('cache hit 1') { sequencer.length(837799) }
bm.report('cache hit 2') { sequencer.length(837799 * 2) }
end
# user system total real
# not cached 0.000000 0.000000 0.000000 ( 0.001489)
# cache hit 1 0.000000 0.000000 0.000000 ( 0.000007)
# cache hit 2 0.000000 0.000000 0.000000 ( 0.000011)
答案 2 :(得分:1)
您需要的只是一个过程,从奇数小于1000000开始查找最长的Collatz链,另一个查找从偶数小于1000000开始的最长的一个。在单独的核心中运行多个脚本实例如果你手动启动它们并不太难。它既便宜又脏,但它的工作原理:-)但它们不能只是一个进程中的线程,它们必须是独立的进程。 (我认为我称之为“进程”的是ThorX89所谓的“OS线程”。)
答案 3 :(得分:0)
除了线程之外,还有其他方法可以加快速度。
我似乎记得Ruby是一种特别慢的语言,由一个不关心性能的人设计。也许你可以使用不同的语言。
更重要的是:你这是天真的做法。它有效,但很多计算都重复了很多次。例如,您有以下Collatz序列:
7, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1
11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1
29, 88, 44, 22, 11, 34, 17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1
序列中的大多数步骤(例如52-> 26)被计算不止一次。这里有一些优化空间。您已经通过忽略以偶数开头的序列进行了一些优化(顺便说一下,在整理结果时忘记纠正这个问题)。我发现了一种更快的方法,并将其与天真的方法进行了比较。对于前10,000个而不是前1,000,000个,天真的方法需要63秒;忽略偶数的天真方法耗时35秒;我的方法花了5秒钟。对于完整的1,000,000,我的算法花了9分钟;我没有尝试运行其他人。 (所有这些都是用Perl编写的。)如果你不想要了解我如何做的更多细节,请立即离开!
我不是仅计算每个结果然后忘记它,而是在我进行的时候制作了一系列结果。现在假设您计算了所有Collatz序列长度,最多为12.要计算从13开始的序列长度,从13开始并继续,直到达到小于13的数字。序列为13,40,20 ,10。你看一下数组中的元素10,发现从10到1是6步。你知道从13到10是3步,因为你刚刚完成了这些步骤。因此从13到1是9步,因此您将9设置为数组中的元素13.
用线程做这个没有明显/好的方法。如果你真的想要的话我觉得有可能提出一些东西,但这不值得努力。也许如果他们说了十亿......