LoginSignup
4
0

More than 5 years have passed since last update.

夏休みFortran祭り 素数を10^11まで数える

Last updated at Posted at 2017-08-12

エラトステネスのふるいで素数をしつこく数えます+並列化

Fortran2003/08 では ビット操作関数が増えたので、これを用います。表は奇数のみ記録するとして、ある数 n に対して、配列の何番目の要素の何番目のビットかということを計算して、目的のビットを操作します。また、ある数 n が素数かどうかを返す論理型関数も定義します。

おおよそ 2*10^9 を超えると 32 ビットでは足りなくなってくるので、64ビット長でやっていますが、面倒なので行儀悪く手打ちで _8 を付けています。

実行結果

10^n 10^n 以下の素数の個数 計算時間(秒) 計算時間(並列)(秒)
10^1 4
10^2 26
10^3 168
10^4 1,229
10^5 9,592
10^6 78,498
10^7 664,579
10^8 5,761,455 0.3 0.1
10^9 50,847,534 6 4.5
10^10 455,052,511 77 36
10^11 4,118,054,813 1643 396

記憶容量的には 10^12 まで行ける感じですが、10 時間以上かかりそうなのでやりませんでした。 無理っす、記憶容量足りません。計算違いしてましたw

実行時間は素数のテーブルの書き込みをする二重ループがほとんどを占めていて、そこは素数の数×テーブルの大きさ~lnN*N なので、この割合で実行時間が増えると思います。

ソース・プログラム

布団の中で考えたので適当です。

ビット操作関数としては ibitclr, ibtest, popcnt の三つを使いました。それぞれ、指定のビットを 0 にした整数値を返す、ビットが立っている時真を返す、立っているビットの数を返す関数です。

立っているビット数を数える sum(popcnt(itab)) が、10^1)~ の場合正しく働かず、32bit 演算をする感じなので、その時は DO LOOP を回して数えました。

    program eratos_bit
      implicit none
      integer(8), parameter :: nmax = 10_8**1
      integer(8) :: i, k, n, m, nb 
      integer(8), allocatable :: itab(:)
!      
      integer(8) :: ic0, icr0, ic1, icr1, ic2, icr2
      call system_clock(ic0, icr0)
!      
      nb = 2_8 * bit_size(itab)
      n = nmax / nb + 1
      print *, 'array size=', n
      allocate(itab(n))
      itab = z'FFFFFFFFFFFFFFFF'!z'FFFFFFFF'
      do i = nmax + 1, nb * n - 1, 2
        call clr_bit(i)  
      end do    
      !
      do i = 3, int(sqrt(real(nmax))), 2
        if (is_prime(i)) then  
          do k = i**2, nmax, 2_8 * i
            call clr_bit(k)  
          end do
        end if  
      end do
      !
      call system_clock(ic1, icr1)
      print *, 'eratos time', (ic1 - ic0) / real(icr1)
      print *, sum(popcnt(itab))
      ! for 10^10~
      !k = 0
      !do i = 1, n
      !  k = k + popcnt(itab(i))
      !end do    
      print *, 'no. of primes', k
      call system_clock(ic2, icr2)
      print *, 'bitcnt time', (ic2 - ic1) / real(icr2)
    contains
      subroutine clr_bit(n)
        integer(8), intent(in) :: n
        integer(8) :: ia, ib
        ia = n / nb + 1_8
        ib = mod(n, nb) / 2_8
        itab(ia) = ibclr(itab(ia), ib)
      end subroutine clr_bit

      logical function is_prime(n)
        integer(8), intent(in) :: n
        integer(8) :: ia, ib
        ia = n / nb + 1_8
        ib = mod(n, nb) / 2_8
        is_prime = btest(itab(ia), ib)
      end function is_prime
    end program eratos_bit

並列化

サブルーチンの部分を、ループ中に展開して、do concurrent と自動並列化で並列にしてみました。約4倍スピードアップ。

    program eratos_bit
      implicit none
      integer(8), parameter :: nmax = 10_8**10
      integer(8) :: i, k, n, m, nb 
      integer(8), allocatable :: itab(:)
!      
      nb = 2_8 * bit_size(itab)
      n = nmax / nb + 1
      print *, 'array size=', n
      allocate(itab(n))
      itab = -1_8 ! z'FFFFFFFFFFFFFFFF'!z'FFFFFFFF'
      do i = nmax + 1, nb * n - 1, 2
        block
          integer(8) :: ia, ib  
          ia = i / nb + 1_8
          ib = mod(i, nb) / 2_8
          itab(ia) = ibclr(itab(ia), ib)
        end block
      end do    
      !
      call stamp('begin of the sieve of Eratosthenes')
      do i = 3, int(sqrt(real(nmax))), 2
        if (is_prime(i)) then  
          do concurrent (k = i**2:nmax:2_8 * i)
            block
              integer(8) :: ia, ib   
              ia = k / nb + 1_8
              ib = mod(k, nb) / 2_8
              itab(ia) = ibclr(itab(ia), ib)
            end block
          end do
        end if  
      end do
      !
      call stamp('end   of the sieve of Eratosthenes')
      !print *, sum(popcnt(itab))
      ! for 10^10 ~
      k = 0
      do i = 1, n
        k = k + popcnt(itab(i))
      end do    
      print *, 'no. of primes', k
      call stamp('end   of bit counting')
    contains
      logical function is_prime(n)
        integer(8), intent(in) :: n
        integer(8) :: ia, ib
        ia = n / nb + 1_8
        ib = mod(n, nb) / 2_8
        is_prime = btest(itab(ia), ib)
      end function is_prime

      subroutine stamp(text)
        character(*), intent(in) :: text
        logical, save :: first = .true.
        integer(8), save :: ic0, icr0
        integer(8) :: ic1, icr1
        if (first) then
          call system_clock(ic0, icr0)
          first = .false.
        end if
        call system_clock(ic1, icr1)
        print '(f10.2, 2a)', (ic1 - ic0) / real(icr1), '(sec) : ', text   
      end subroutine stamp  
    end program eratos_bit
4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0