Skip to content

Strange performance difference? #8516

@jzakiya

Description

@jzakiya

In testing this code against 4 Ruby VMs there is a significant performance anomaly in JRuby (via rvm).

jruby 9.4.9.0 (3.1.4) 2024-11-04 547c6b150e Java HotSpot(TM) 64-Bit Server VM 15.0.2+7-27 on 15.0.2+7-27 +jit [x86_64-linux]

Test Code

module Primes
  module Utils

    # Miller-Rabin prime test in Ruby
    # From: http://en.wikipedia.org/wiki/Miller-Rabin_primality_test
    # Ruby Rosetta Code: http://rosettacode.org/wiki/Miller-Rabin_primality_test
    # I modified the Rosetta Code, as shown below

    def primemr2?(k=5)  # increase k for more reliability
      return self >= 2 if self <= 3 
      return false unless 6.gcd(self) == 1
      neg_one_mod = n = d = self - 1      # these are even as self is always odd
      s = 0
      (d >>= 1; s += 1) while d.even?
      k.times do
        a = 2 + rand(self-4)
        y = a.pow(d,self)                 # y = (a**d) mod self
        next if y == 1 or y == neg_one_mod
        (s-1).times do
          y = y.pow(2, self)              # y = (y**2) mod self
          return false if y == 1
          break if y == neg_one_mod
        end
        return false unless y == neg_one_mod
      end
      true                                # n is prime (with high probability)
    end
    
    def primemr3?(k=5)  # increase k for more reliability
      return self >= 2 if self <= 3
      return false unless 6.gcd(self) == 1
      neg_one_mod = n = d = self - 1      # these are even as self is always odd
      d >>= 2 while (d & 0b11) == 0; d >>= (d & 1)^1  # make d odd number
      k.times do
        a = 2 + rand(self-4)
        y = a.pow(d, self)                # y = (a**d) mod self
        next if y == 1 or y == self-1
        until y == 1 || y == neg_one_mod || d == n
	      y = y.pow(2, self)              # y = (y**2) mod self
          d <<= 1
        end
        return false unless y == neg_one_mod
      end
      true                                # n is prime (with high probability)
    end

    # Returns true if +self+ is a prime number, else returns false.
    def primemr?(k = 5)
      wits = WITNESS_RANGES.find { |range, wits| range > self }
      witnesses = wits ? wits[1] : k.times.map{ rand(self - 4) + 2 }
      witnesses.each { |p| return false unless miller_rabin_test(p) }
      true
    end

    def primemr1?(k = 5)             # k is default number of random bases
      return PRIMES.include? self if self <= PRIMES.last
      return false if MODPN.gcd(self) != 1
      #return true  if self < PRIMES.last**2
      neg_one_mod = n = d = self - 1 # these are even as self is always odd
      d >>= 2 while (d & 0b11) == 0; d >>= (d & 1)^1  # make d odd number
      # wits = [range, [wit_prms]] or nil
      wits = WITNESS_RANGES.find { |range, wits| range > self }
      witnesses = wits ? wits[1] : k.times.map{ rand(self - 4) + 2 }
      witnesses.each do |b|
        next if (b % self).zero?     # **skip base if a multiple of input**
        y = b.pow(d, self)           # y = (b**d) mod self
        s = d
        until y == 1 || y == neg_one_mod || s == n
          y = y.pow(2, self)         # y = (y**2) mod self
          s <<= 1
        end
        return false unless y == neg_one_mod || s.odd?
      end
      true
    end

    private

    WITNESS_RANGES = {
      341_531 => [9345883071009581737],
      1_050_535_501 => [336781006125, 9639812373923155],
      350_269_456_337 => [4230279247111683200, 14694767155120705706, 16641139526367750375],
      55_245_642_489_451 => [2, 141889084524735, 1199124725622454117, 11096072698276303650],
      7_999_252_175_582_851 => [2, 4130806001517, 149795463772692060, 186635894390467037, 3967304179347715805],
      585_226_005_592_931_977 => [2, 123635709730000, 9233062284813009, 43835965440333360, 761179012939631437, 1263739024124850375],
      18_446_744_073_709_551_615 => [2, 325, 9375, 28178, 450775, 9780504, 1795265022],
      #318_665_857_834_031_151_167_461   => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37],
      #3_317_044_064_679_887_385_961_981 => [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41]
    }

    MODPN  = 232862364358497360900063316880507363070 # 101# (101 primorial) is largest for u128
    PRIMES = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103]

    # Returns true if +self+ passes Miller-Rabin Test on +b+
    def miller_rabin_test(b)             # b is a witness to test with
      return self >= 2 if self <= 3 
      return false unless 6.gcd(self) == 1
      n = d = self - 1
      d >>= 2 while (d & 0b11) == 0; d >>= (d & 1)^1  # make d odd number
      return if (b % self).zero?         # **skip base if a multiple of input**
      y = b.pow(d, self)                 # y = (b**d) mod self
      until y == 1 || y == n || d == n
	    y = y.pow(2, self)               # x = (x**2) mod self
        d <<= 1
      end
      y == n || d.odd?
    end
  end
end

class Integer; include Primes::Utils end
  
def tm; s=Time.now; yield; Time.now-s end

# Perform primality tests with Miler-Rabin methods

def primes_tests(n)
  puts "test value #{n} is prime?: #{n.primemr1?}"
  puts "time for n.primemr?  is #{tm{ (10**4).times{ n.primemr?  } } }"
  puts "time for n.primemr1? is #{tm{ (10**4).times{ n.primemr1? } } }"
  puts "time for n.primemr2? is #{tm{ (10**4).times{ n.primemr2? } } }"
  puts "time for n.primemr3? is #{tm{ (10**4).times{ n.primemr3? } } }"
  puts
end

For these tests:

n = 100000000000000000000000000000000000000000000000000000000000000000000000001309503
primes_tests(n)

n = 303_371_455_241                              # lo prime pair for gap of 500
primes_tests(n)

n = 8446744073709551627
primes_tests(n)

n = 8446744073709551556
primes_tests(n)

n = 18446744073709551556
primes_tests(n)

n = 18446744073709551557                         # largest 64-bit prime
primes_tests(n)

n = 18446744073709551615                         # 2**64 - 1, largest 64-bits
primes_tests(n)

n = 18446744073709551629                         # next prime > 64-bits
primes_tests(n)

n = 340282366920938463463374607431768211296      # 1 < largest 128-bit prime
primes_tests(n)

n = 340282366920938463463374607431768211297      # largest 128-bit prime
primes_tests(n)

n = 340282366920938463463374607431768211455      # 2**128 - 1, largest 128-bits
primes_tests(n)

JRuby results

est value 100000000000000000000000000000000000000000000000000000000000000000000000001309503 is prime?: true
time for n.primemr?  is 0.873514
time for n.primemr1? is 0.754906
time for n.primemr2? is 0.733884
time for n.primemr3? is 0.692394

test value 303371455241 is prime?: true
time for n.primemr?  is 8.858697000000001
time for n.primemr1? is 8.801776
time for n.primemr2? is 14.792952
time for n.primemr3? is 15.211563

test value 8446744073709551627 is prime?: false
time for n.primemr?  is 4.929926
time for n.primemr1? is 4.843951000000001
time for n.primemr2? is 5.052339999999999
time for n.primemr3? is 5.02239

test value 8446744073709551556 is prime?: false
➜  miller-rabin ruby miller-rabintest1.rb
test value 100000000000000000000000000000000000000000000000000000000000000000000000001309503 is prime?: true
time for n.primemr?  is 0.836465
time for n.primemr1? is 0.749006
time for n.primemr2? is 0.715312
time for n.primemr3? is 0.703027

test value 303371455241 is prime?: true
time for n.primemr?  is 8.743595
time for n.primemr1? is 8.502458
time for n.primemr2? is 14.66437
time for n.primemr3? is 14.517415000000002

test value 8446744073709551627 is prime?: false
time for n.primemr?  is 4.941791
time for n.primemr1? is 4.80778
time for n.primemr2? is 5.038014
time for n.primemr3? is 5.132918

test value 8446744073709551556 is prime?: false
time for n.primemr?  is 0.016883000000000002
time for n.primemr1? is 0.009792
time for n.primemr2? is 0.005158
time for n.primemr3? is 0.002415

test value 18446744073709551556 is prime?: false
time for n.primemr?  is 0.021440999999999998
time for n.primemr1? is 0.00723
time for n.primemr2? is 0.010395999999999999
time for n.primemr3? is 0.006878

test value 18446744073709551557 is prime?: true
time for n.primemr?  is 0.164283
time for n.primemr1? is 0.106833
time for n.primemr2? is 0.091131
time for n.primemr3? is 0.076461

test value 18446744073709551615 is prime?: false
time for n.primemr?  is 0.019133999999999998
time for n.primemr1? is 0.004553
time for n.primemr2? is 0.0025
time for n.primemr3? is 0.002422

test value 18446744073709551629 is prime?: true
time for n.primemr?  is 0.117601
time for n.primemr1? is 0.11476800000000001
time for n.primemr2? is 0.098049
time for n.primemr3? is 0.09081299999999999

test value 340282366920938463463374607431768211296 is prime?: false
time for n.primemr?  is 0.022298
time for n.primemr1? is 0.009453999999999999
time for n.primemr2? is 0.000998
time for n.primemr3? is 0.000956

test value 340282366920938463463374607431768211297 is prime?: true
time for n.primemr?  is 0.260003
time for n.primemr1? is 0.258279
time for n.primemr2? is 0.24645899999999998
time for n.primemr3? is 0.205179

test value 340282366920938463463374607431768211455 is prime?: false
time for n.primemr?  is 0.021006
time for n.primemr1? is 0.009675999999999999
time for n.primemr2? is 0.001349
time for n.primemr3? is 0.001433

The 2nd||3rd values (303371455241||8446744073709551627) are way slower, though smaller than others!?!

Compare the results on other Ruby VMs

TruffleRuby 24.1.1

test value 100000000000000000000000000000000000000000000000000000000000000000000000001309503 is prime?: true
time for n.primemr?  is 2.037394
time for n.primemr1? is 2.044762
time for n.primemr2? is 2.023177
time for n.primemr3? is 2.030676

test value 303371455241 is prime?: true
time for n.primemr?  is 0.130453
time for n.primemr1? is 0.083802
time for n.primemr2? is 0.13637000000000002
time for n.primemr3? is 0.10047499999999998

test value 8446744073709551627 is prime?: false
time for n.primemr?  is 0.074172
time for n.primemr1? is 0.09076000000000001
time for n.primemr2? is 0.05860000000000001
time for n.primemr3? is 0.05164800000000001

test value 8446744073709551556 is prime?: false
time for n.primemr?  is 0.03371
time for n.primemr1? is 0.008634
time for n.primemr2? is 0.002139
time for n.primemr3? is 0.002048

test value 18446744073709551556 is prime?: false
time for n.primemr?  is 0.027187
time for n.primemr1? is 0.013865
time for n.primemr2? is 0.00269
time for n.primemr3? is 0.001435

test value 18446744073709551557 is prime?: true
time for n.primemr?  is 0.268511
time for n.primemr1? is 0.27220300000000003
time for n.primemr2? is 0.207252
time for n.primemr3? is 0.204208

test value 18446744073709551615 is prime?: false
time for n.primemr?  is 0.046696
time for n.primemr1? is 0.003036
time for n.primemr2? is 0.001324
time for n.primemr3? is 0.001558

test value 18446744073709551629 is prime?: true
time for n.primemr?  is 0.34620700000000004
time for n.primemr1? is 0.2535529999999999
time for n.primemr2? is 0.190887
time for n.primemr3? is 0.256318

test value 340282366920938463463374607431768211296 is prime?: false
time for n.primemr?  is 0.050591
time for n.primemr1? is 0.028317
time for n.primemr2? is 0.000678
time for n.primemr3? is 0.000937

test value 340282366920938463463374607431768211297 is prime?: true
time for n.primemr?  is 0.678474
time for n.primemr1? is 0.63026
time for n.primemr2? is 0.5384709999999999
time for n.primemr3? is 0.534702

test value 340282366920938463463374607431768211455 is prime?: false
time for n.primemr?  is 0.03755
time for n.primemr1? is 0.028116
time for n.primemr2? is 0.000501
time for n.primemr3? is 0.001292

Ruby 3.3.6

est value 100000000000000000000000000000000000000000000000000000000000000000000000001309503 is prime?: true
time for n.primemr?  is 0.61845201
time for n.primemr1? is 0.600007099
time for n.primemr2? is 0.579011867
time for n.primemr3? is 0.579763807

test value 303371455241 is prime?: true
time for n.primemr?  is 0.019405872
time for n.primemr1? is 0.018532674
time for n.primemr2? is 0.022467344
time for n.primemr3? is 0.017704992

test value 8446744073709551627 is prime?: false
time for n.primemr?  is 0.023057941
time for n.primemr1? is 0.02498252
time for n.primemr2? is 0.013945913
time for n.primemr3? is 0.026167141

test value 8446744073709551556 is prime?: false
➜  miller-rabin ruby miller-rabintest1.rb
test value 100000000000000000000000000000000000000000000000000000000000000000000000001309503 is prime?: true
time for n.primemr?  is 0.632514411
time for n.primemr1? is 0.617031376
time for n.primemr2? is 0.584806694
time for n.primemr3? is 0.591961562

test value 303371455241 is prime?: true
time for n.primemr?  is 0.020221291
time for n.primemr1? is 0.018887651
time for n.primemr2? is 0.022730917
time for n.primemr3? is 0.017800883

test value 8446744073709551627 is prime?: false
time for n.primemr?  is 0.023692471
time for n.primemr1? is 0.024016789
time for n.primemr2? is 0.013324277
time for n.primemr3? is 0.019741071

test value 8446744073709551556 is prime?: false
time for n.primemr?  is 0.00981716
time for n.primemr1? is 0.002083217
time for n.primemr2? is 0.001528687
time for n.primemr3? is 0.001532383

test value 18446744073709551556 is prime?: false
time for n.primemr?  is 0.009846595
time for n.primemr1? is 0.002255851
time for n.primemr2? is 0.001545298
time for n.primemr3? is 0.001540399

test value 18446744073709551557 is prime?: true
time for n.primemr?  is 0.098092364
time for n.primemr1? is 0.074849837
time for n.primemr2? is 0.057987474
time for n.primemr3? is 0.050729434

test value 18446744073709551615 is prime?: false
time for n.primemr?  is 0.032238006
time for n.primemr1? is 0.002349256
time for n.primemr2? is 0.001806658
time for n.primemr3? is 0.001847625

test value 18446744073709551629 is prime?: true
time for n.primemr?  is 0.103820035
time for n.primemr1? is 0.085631054
time for n.primemr2? is 0.065799875
time for n.primemr3? is 0.062917339

test value 340282366920938463463374607431768211296 is prime?: false
time for n.primemr?  is 0.034392627
time for n.primemr1? is 0.003315758
time for n.primemr2? is 0.001747036
time for n.primemr3? is 0.001703224

test value 340282366920938463463374607431768211297 is prime?: true
time for n.primemr?  is 0.21347393
time for n.primemr1? is 0.191538654
time for n.primemr2? is 0.165191455
time for n.primemr3? is 0.130311444

test value 340282366920938463463374607431768211455 is prime?: false
time for n.primemr?  is 0.034583705
time for n.primemr1? is 0.003264902
time for n.primemr2? is 0.001701821
time for n.primemr3? is 0.001677576

Ruby 3.4-preview1

est value 100000000000000000000000000000000000000000000000000000000000000000000000001309503 is prime?: true
time for n.primemr?  is 0.635770087
time for n.primemr1? is 0.638503523
time for n.primemr2? is 0.595368691
time for n.primemr3? is 0.598772634

test value 303371455241 is prime?: true
time for n.primemr?  is 0.020588841
time for n.primemr1? is 0.019819027
time for n.primemr2? is 0.022277577
time for n.primemr3? is 0.017595767

test value 8446744073709551627 is prime?: false
time for n.primemr?  is 0.024912559
time for n.primemr1? is 0.027428728
time for n.primemr2? is 0.014424921
time for n.primemr3? is 0.025096815

test value 8446744073709551556 is prime?: false
time for n.primemr?  is 0.011165799
time for n.primemr1? is 0.002304622
time for n.primemr2? is 0.001619867
time for n.primemr3? is 0.001628684

test value 18446744073709551556 is prime?: false
time for n.primemr?  is 0.012746023
time for n.primemr1? is 0.003037716
time for n.primemr2? is 0.002829116
time for n.primemr3? is 0.001748089

test value 18446744073709551557 is prime?: true
time for n.primemr?  is 0.099865469
time for n.primemr1? is 0.077873137
time for n.primemr2? is 0.059939405
time for n.primemr3? is 0.055619184

test value 18446744073709551615 is prime?: false
time for n.primemr?  is 0.035954957
time for n.primemr1? is 0.002472667
time for n.primemr2? is 0.001887079
time for n.primemr3? is 0.001818871

test value 18446744073709551629 is prime?: true
time for n.primemr?  is 0.104930787
time for n.primemr1? is 0.08522826
time for n.primemr2? is 0.068326143
time for n.primemr3? is 0.064138119

test value 340282366920938463463374607431768211296 is prime?: false
time for n.primemr?  is 0.035416507
time for n.primemr1? is 0.003739953
time for n.primemr2? is 0.001777073
time for n.primemr3? is 0.00171192

test value 340282366920938463463374607431768211297 is prime?: true
time for n.primemr?  is 0.21398621
time for n.primemr1? is 0.190769001
time for n.primemr2? is 0.171413123
time for n.primemr3? is 0.131613246

test value 340282366920938463463374607431768211455 is prime?: false
time for n.primemr?  is 0.034755878
time for n.primemr1? is 0.003716379
time for n.primemr2? is 0.001799555
time for n.primemr3? is 0.001675553

My system

➜  ~ neofetch

             ooooooooooooo               jzakiya@jz1 
         ooooooooooooooooooooo           ----------- 
      oooooooooooooooooooooooooo         OS: PCLinuxOS x86_64 
    oooooooo              oooooooo       Host: 83DH Legion Slim 5 16AHP9 
   oooooo                    oooooo      Kernel: 6.6.58-pclos1 
  oooooo                       ooooo     Uptime: 5 hours, 5 mins 
ooooo    pppppppp    cccccccc   ooooo    Packages: 2660 (rpm) 
 oooo    ppp  pppp  ccccccccc    oooo    Shell: zsh 5.8 
ooooo    ppp   ppp ccccc         ooooo   Resolution: 2560x1600 
ooooo    ppp  ppp  cccc          ooooo   DE: Plasma 5.27.11 
ooooo    ppppppp   ccccc         ooooo   WM: KWin 
 ooooo   ppp         cccccccc   ooooo    WM Theme: Breeze 
  ooooo  ppp          ccccccc   ooooo    Theme: [Plasma], Breeze [GTK3] 
   ooooo                      oooooo     Icons: [Plasma], breeze [GTK2/3] 
    ooooo                   ooooooo      Terminal: konsole 
     ooooooo              ooooooo        CPU: AMD Ryzen 7 8845HS w/ Radeon 780M Graphics (16) @ 3.800GHz 
       oooooooooooooooooooooooo          GPU: AMD ATI 05:00.0 Device 1900 
          oooooooooooooooooo             GPU: NVIDIA GeForce RTX 4070 Max-Q / Mobile 
               ooooooooo                 Memory: 2901MiB / 15306MiB 
 

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions