我有大约1,000,000个IPv4地址,我需要将它们转换为整数。
我尝试过的方法:
IPAddr.new(str).to_i
Socket.sockaddr_in(0, str)[4,4].unpack('L>')[0]
str.split('.').map(&:to_i).pack('CCCC').unpack('L>')[0]
str.split('.').map(&:to_i).inject(0) { |sum, v| (sum << 8) + v }
然而,所有这些都比这个Python方法慢至少10倍:
struct.unpack('!L', socket.inet_aton(str))[0]
除了编写与Python一样快的C-Extension之外,还有其他方法吗?
这是一个简单的基准测试,在这个基准测试中,Python比Ruby快2倍,我会看看在处理随机IP时结果是否会变大。
红宝石:
require 'socket'
t1 = Time.now
10000000.times do
Socket.sockaddr_in(0, '192.168.1.1')[4,4].unpack('L>')[0]
end
t2 = Time.now
puts t2 - t1
的Python:
import time, struct, socket
t1 = time.time()
for i in xrange(10000000):
struct.unpack('!L', socket.inet_aton('192.168.1.1'))[0]
t2 = time.time()
print t2 - t1
答案 0 :(得分:2)
很难帮助你,因为我们不一定能够访问Python或你的Ruby与Python代码基准测试,而且我们写两者都是无效的,因为你必须把我们做的任何东西都塞进你的代码中,可能会减慢或打破它。但是,这可能对开始磨练代码以提高速度有用:
require 'fruity'
require 'ipaddr'
STR = '192.168.0.0'
compare do
ipaddr_new { IPAddr.new(STR).to_i }
sockaddr_in { Socket.sockaddr_in(0, STR)[4,4].unpack('L>')[0] }
pack1 { STR.split('.').map(&:to_i).pack('CCCC').unpack('L>')[0] }
pack2 { STR.split('.').map(&:to_i).inject(0) { |sum, v| (sum << 8) + v } }
end
运行结果:
# >> Running each test 512 times. Test will take about 1 second.
# >> sockaddr_in is faster than pack2 by 30.000000000000004% ± 1.0%
# >> pack2 is faster than pack1 by 19.999999999999996% ± 1.0%
# >> pack1 is faster than ipaddr_new by 2.9x ± 0.1
将您的N
更改与L>
或L!
比较显示:
Socket.sockaddr_in(0, STR)[4,4].unpack('L>')[0] # => 3232235520
Socket.sockaddr_in(0, STR)[4,4].unpack('L!')[0] # => nil
Socket.sockaddr_in(0, STR)[4,4].unpack('N')[0] # => 3232235520
所以L!
无效。
compare do
sockaddr_in1 { Socket.sockaddr_in(0, STR)[4,4].unpack('L>')[0] }
sockaddr_in2 { Socket.sockaddr_in(0, STR)[4,4].unpack('L!')[0] }
sockaddr_in3 { Socket.sockaddr_in(0, STR)[4,4].unpack('N')[0] }
end
# >> Running each test 1024 times. Test will take about 1 second.
# >> sockaddr_in2 is faster than sockaddr_in1 by 10.000000000000009% ± 10.0% (results differ: vs 3232235520)
# >> sockaddr_in1 is similar to sockaddr_in3
答案 1 :(得分:1)
以下是使用大量随机生成的IPv4地址的基准测试结果:
user system total real
IPAddr: 3.240000 0.000000 3.240000 ( 3.242000)
Socket: 0.760000 0.000000 0.760000 ( 0.759157)
pack: 1.790000 0.010000 1.800000 ( 1.797654)
reduce: 1.570000 0.010000 1.580000 ( 1.579099)
ipgem: 4.060000 0.000000 4.060000 ( 4.061129)
正如我在上面的评论中所提到的,Socket.sockaddr_in
技术似乎是最快的。我将在下面附上基准测试代码。
我正在研究的一件事是,大多数这些技术都是特定于IPv4的。考虑到impending切换到IPv6,将代码限制为IPv4可能是不明智的。如果这是一次性的,很好,但考虑到你的性能问题,我猜这是可以重复使用的。
如果你真的想粉碎基准测试,你应该考虑使用Parallel或forkoff之类的并行处理日志。使用所有核心。
require 'benchmark'
require 'ipaddr'
require 'ipaddress'
n = 500_000
family = Socket::AF_INET # IPv4
ipaddrs = n.times.map { IPAddr.new(rand(2**32), family).to_s }
Benchmark.bm do |x|
x.report('IPAddr:') { ipaddrs.map { |str| IPAddr.new(str).to_i } }
x.report('Socket:') { ipaddrs.map { |str| Socket.sockaddr_in(0, str).byteslice(4, 4).unpack('N').first } }
x.report('pack: ') { ipaddrs.map { |str| str.split('.').map(&:to_i).pack('CCCC').unpack('L>').first } }
x.report('reduce:') { ipaddrs.map { |str| str.split('.').map(&:to_i).reduce(0) { |sum, v| (sum << 8) + v } } }
x.report('ipgem: ') { ipaddrs.map { |str| IPAddress.parse(str).to_u32 } }
end
在Python3中运行类似的基准测试(使用您提供的代码作为起点)在同一台机器上产生大约0.242秒的时间,因此比最快的Ruby版本快三倍。
import time, struct, socket, random
n = 500000
ipaddrs = [socket.inet_ntoa(struct.pack('>I', random.randint(1, 0xffffffff))) for i in range(n)]
t1 = time.time()
for ipaddr in ipaddrs:
struct.unpack('!L', socket.inet_aton(ipaddr))[0]
t2 = time.time()
print(t2 - t1)