在Ruby中将IPv4地址转换为整数的最快方法是什么?

时间:2015-10-28 16:46:52

标签: ruby

我有大约1,000,000个IPv4地址,我需要将它们转换为整数。

我尝试过的方法:

  1. IPAddr.new(str).to_i
  2. Socket.sockaddr_in(0, str)[4,4].unpack('L>')[0]
  3. str.split('.').map(&:to_i).pack('CCCC').unpack('L>')[0]
  4. str.split('.').map(&:to_i).inject(0) { |sum, v| (sum << 8) + v }
  5. 然而,所有这些都比这个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
    

2 个答案:

答案 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可能是不明智的。如果这是一次性的,很好,但考虑到你的性能问题,我猜这是可以重复使用的。

如果你真的想粉碎基准测试,你应该考虑使用Parallelforkoff之类的并行处理日志。使用所有核心。

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)