Seberapa lambat sebenarnya Python? (Atau seberapa cepat bahasa Anda?)

149

Saya memiliki kode ini yang saya tulis dengan Python / NumPy

from __future__ import division
import numpy as np
import itertools

n = 6
iters = 1000
firstzero = 0
bothzero = 0
""" The next line iterates over arrays of length n+1 which contain only -1s and 1s """
for S in itertools.product([-1, 1], repeat=n+1):
    """For i from 0 to iters -1 """
    for i in xrange(iters):
        """ Choose a random array of length n.
            Prob 1/4 of being -1, prob 1/4 of being 1 and prob 1/2 of being 0. """
        F = np.random.choice(np.array([-1, 0, 0, 1], dtype=np.int8), size=n)
        """The next loop just makes sure that F is not all zeros."""
        while np.all(F == 0):
            F = np.random.choice(np.array([-1, 0, 0, 1], dtype=np.int8), size=n)
        """np.convolve(F, S, 'valid') computes two inner products between
        F and the two successive windows of S of length n."""
        FS = np.convolve(F, S, 'valid')
        if FS[0] == 0:
            firstzero += 1
        if np.all(FS == 0):
            bothzero += 1

print("firstzero: %i" % firstzero)
print("bothzero: %i" % bothzero)

Ini menghitung berapa kali lilitan dua array acak, yang satu lebih panjang dari yang lain, dengan distribusi probabilitas tertentu, memiliki 0 di posisi pertama atau 0 di kedua posisi.

Saya bertaruh dengan seorang teman yang mengatakan Python adalah bahasa yang buruk untuk menulis kode yang harus cepat. Dibutuhkan 9s di komputer saya. Dia mengatakan itu bisa dibuat 100 kali lebih cepat jika ditulis dalam "bahasa yang tepat".

Tantangannya adalah untuk melihat apakah kode ini memang dapat dibuat 100 kali lebih cepat dalam bahasa apa pun yang Anda pilih. Saya akan menguji kode Anda dan satu minggu tercepat dari sekarang akan menang. Jika ada yang mendapat di bawah 0,09 maka mereka secara otomatis menang dan saya kalah.

Status

  • Python . 30 kali dipercepat oleh Alistair Buxon! Meskipun bukan solusi tercepat itu sebenarnya favorit saya.
  • Oktaf . 100 kali dipercepat oleh @Thethos.
  • Karat . 500 kali dipercepat oleh @dbaupp.
  • C ++ . 570 kali dipercepat oleh Guy Sirton.
  • C . 727 kali dipercepat oleh @ace.
  • C ++ . Sangat cepat oleh @Stefan.

Solusi tercepat sekarang terlalu cepat untuk waktu yang masuk akal. Karena itu saya telah meningkatkan n menjadi 10 dan mengatur iters = 100000 untuk membandingkan yang terbaik. Di bawah ukuran ini yang tercepat adalah.

  • C . 7.5s oleh @ace.
  • C ++ . 1d oleh @Stefan.

Mesin Saya Pengaturan waktu akan dijalankan pada mesin saya. Ini adalah instalasi ubuntu standar pada Prosesor Delapan Core AMD FX-8350. Ini juga berarti saya harus dapat menjalankan kode Anda.

Tindak lanjut diposting Karena kompetisi ini agak terlalu mudah untuk mendapatkan speedup x100, saya telah memposting tindak lanjut bagi mereka yang ingin melatih keahlian guru kecepatan mereka. Lihat Betapa Lambatnya Python (Bagian II)?

Komunitas
sumber

Jawaban:

61

C ++ bit magic

0.84ms dengan RNG sederhana, 1.67ms dengan c ++ 11 std :: knuth

0.16ms dengan sedikit modifikasi algoritmik (lihat edit di bawah)

Implementasi python berjalan dalam 7,97 detik di rig saya. Jadi ini 9488 hingga 4772 kali lebih cepat tergantung pada RNG apa yang Anda pilih.

#include <iostream>
#include <bitset>
#include <random>
#include <chrono>
#include <stdint.h>
#include <cassert>
#include <tuple>

#if 0
// C++11 random
std::random_device rd;
std::knuth_b gen(rd());

uint32_t genRandom()
{
    return gen();
}
#else
// bad, fast, random.

uint32_t genRandom()
{
    static uint32_t seed = std::random_device()();
    auto oldSeed = seed;
    seed = seed*1664525UL + 1013904223UL; // numerical recipes, 32 bit
    return oldSeed;
}
#endif

#ifdef _MSC_VER
uint32_t popcnt( uint32_t x ){ return _mm_popcnt_u32(x); }
#else
uint32_t popcnt( uint32_t x ){ return __builtin_popcount(x); }
#endif



std::pair<unsigned, unsigned> convolve()
{
    const uint32_t n = 6;
    const uint32_t iters = 1000;
    unsigned firstZero = 0;
    unsigned bothZero = 0;

    uint32_t S = (1 << (n+1));
    // generate all possible N+1 bit strings
    // 1 = +1
    // 0 = -1
    while ( S-- )
    {
        uint32_t s1 = S % ( 1 << n );
        uint32_t s2 = (S >> 1) % ( 1 << n );
        uint32_t fmask = (1 << n) -1; fmask |= fmask << 16;
        static_assert( n < 16, "packing of F fails when n > 16.");


        for( unsigned i = 0; i < iters; i++ )
        {
            // generate random bit mess
            uint32_t F;
            do {
                F = genRandom() & fmask;
            } while ( 0 == ((F % (1 << n)) ^ (F >> 16 )) );

            // Assume F is an array with interleaved elements such that F[0] || F[16] is one element
            // here MSB(F) & ~LSB(F) returns 1 for all elements that are positive
            // and  ~MSB(F) & LSB(F) returns 1 for all elements that are negative
            // this results in the distribution ( -1, 0, 0, 1 )
            // to ease calculations we generate r = LSB(F) and l = MSB(F)

            uint32_t r = F % ( 1 << n );
            // modulo is required because the behaviour of the leftmost bit is implementation defined
            uint32_t l = ( F >> 16 ) % ( 1 << n );

            uint32_t posBits = l & ~r;
            uint32_t negBits = ~l & r;
            assert( (posBits & negBits) == 0 );

            // calculate which bits in the expression S * F evaluate to +1
            unsigned firstPosBits = ((s1 & posBits) | (~s1 & negBits));
            // idem for -1
            unsigned firstNegBits = ((~s1 & posBits) | (s1 & negBits));

            if ( popcnt( firstPosBits ) == popcnt( firstNegBits ) )
            {
                firstZero++;

                unsigned secondPosBits = ((s2 & posBits) | (~s2 & negBits));
                unsigned secondNegBits = ((~s2 & posBits) | (s2 & negBits));

                if ( popcnt( secondPosBits ) == popcnt( secondNegBits ) )
                {
                    bothZero++;
                }
            }
        }
    }

    return std::make_pair(firstZero, bothZero);
}

int main()
{
    typedef std::chrono::high_resolution_clock clock;
    int rounds = 1000;
    std::vector< std::pair<unsigned, unsigned> > out(rounds);

    // do 100 rounds to get the cpu up to speed..
    for( int i = 0; i < 10000; i++ )
    {
        convolve();
    }


    auto start = clock::now();

    for( int i = 0; i < rounds; i++ )
    {
        out[i] = convolve();
    }

    auto end = clock::now();
    double seconds = std::chrono::duration_cast< std::chrono::microseconds >( end - start ).count() / 1000000.0;

#if 0
    for( auto pair : out )
        std::cout << pair.first << ", " << pair.second << std::endl;
#endif

    std::cout << seconds/rounds*1000 << " msec/round" << std::endl;

    return 0;
}

Kompilasi dalam 64-bit untuk register tambahan. Saat menggunakan generator acak sederhana, loop di convolve () berjalan tanpa akses memori apa pun, semua variabel disimpan dalam register.

Cara kerjanya: alih-alih menyimpan Sdan Fsebagai array dalam memori, ia disimpan sebagai bit dalam uint32_t.
Untuk S, nbit paling signifikan digunakan di mana bit set menunjukkan +1 dan bit unset menunjukkan -1.
Fmembutuhkan setidaknya 2 bit untuk membuat distribusi [-1, 0, 0, 1]. Ini dilakukan dengan menghasilkan bit acak dan memeriksa 16 bit paling signifikan (disebut r) dan 16 bit paling signifikan (disebut l). Jika l & ~rkita menganggap bahwa F adalah +1, jika ~l & rkita menganggap itu F-1. Kalau Ftidak 0. Ini menghasilkan distribusi yang kita cari.

Sekarang kita miliki S, posBitsdengan set bit pada setiap lokasi di mana F == 1 dan negBitsdengan bit set pada setiap lokasi di mana F == -1.

Kami dapat membuktikan bahwa F * S(di mana * menunjukkan perkalian) mengevaluasi ke +1 dalam kondisi tersebut (S & posBits) | (~S & negBits). Kami juga dapat membuat logika yang sama untuk semua kasus yang F * Sdievaluasi menjadi -1. Dan akhirnya, kita tahu bahwa sum(F * S)mengevaluasi ke 0 jika dan hanya jika ada jumlah yang sama dengan -1 dan +1 di hasilnya. Ini sangat mudah untuk dihitung hanya dengan membandingkan jumlah +1 bit dan -1 bit.

Implementasi ini menggunakan 32 bit int, dan maksimum yang nditerima adalah 16. Dimungkinkan untuk menskalakan implementasi hingga 31 bit dengan memodifikasi kode menghasilkan acak, dan menjadi 63 bit dengan menggunakan uint64_t alih-alih uint32_t.

sunting

Fungsi berbelit-belit berikut:

std::pair<unsigned, unsigned> convolve()
{
    const uint32_t n = 6;
    const uint32_t iters = 1000;
    unsigned firstZero = 0;
    unsigned bothZero = 0;
    uint32_t fmask = (1 << n) -1; fmask |= fmask << 16;
    static_assert( n < 16, "packing of F fails when n > 16.");


    for( unsigned i = 0; i < iters; i++ )
    {
        // generate random bit mess
        uint32_t F;
        do {
            F = genRandom() & fmask;
        } while ( 0 == ((F % (1 << n)) ^ (F >> 16 )) );

        // Assume F is an array with interleaved elements such that F[0] || F[16] is one element
        // here MSB(F) & ~LSB(F) returns 1 for all elements that are positive
        // and  ~MSB(F) & LSB(F) returns 1 for all elements that are negative
        // this results in the distribution ( -1, 0, 0, 1 )
        // to ease calculations we generate r = LSB(F) and l = MSB(F)

        uint32_t r = F % ( 1 << n );
        // modulo is required because the behaviour of the leftmost bit is implementation defined
        uint32_t l = ( F >> 16 ) % ( 1 << n );

        uint32_t posBits = l & ~r;
        uint32_t negBits = ~l & r;
        assert( (posBits & negBits) == 0 );

        uint32_t mask = posBits | negBits;
        uint32_t totalBits = popcnt( mask );
        // if the amount of -1 and +1's is uneven, sum(S*F) cannot possibly evaluate to 0
        if ( totalBits & 1 )
            continue;

        uint32_t adjF = posBits & ~negBits;
        uint32_t desiredBits = totalBits / 2;

        uint32_t S = (1 << (n+1));
        // generate all possible N+1 bit strings
        // 1 = +1
        // 0 = -1
        while ( S-- )
        {
            // calculate which bits in the expression S * F evaluate to +1
            auto firstBits = (S & mask) ^ adjF;
            auto secondBits = (S & ( mask << 1 ) ) ^ ( adjF << 1 );

            bool a = desiredBits == popcnt( firstBits );
            bool b = desiredBits == popcnt( secondBits );
            firstZero += a;
            bothZero += a & b;
        }
    }

    return std::make_pair(firstZero, bothZero);
}

memotong runtime menjadi 0,160-0,161ms. Buka gulungan manual (tidak digambarkan di atas) membuat 0,150. Semakin sedikit sepele n = 10, iter = 100000 case berjalan di bawah 250ms. Saya yakin saya bisa mendapatkannya di bawah 50 ms dengan memanfaatkan core tambahan tapi itu terlalu mudah.

Ini dilakukan dengan membuat cabang loop dalam bebas dan menukar loop F dan S.
Jika bothZerotidak diperlukan saya dapat mengurangi waktu berjalan ke 0,02 ms dengan jarang perulangan semua kemungkinan array S

Stefan
sumber
3
Bisakah Anda memberikan versi ramah gcc dan juga apa baris perintah Anda? Saya tidak yakin bisa mengujinya saat ini.
Saya tidak tahu apa-apa tentang hal ini tetapi google memberi tahu saya bahwa __builtin_popcount mungkin merupakan pengganti _mm_popcnt_u32 ().
3
Kode diperbarui, menggunakan saklar #ifdef untuk memilih perintah popcnt yang benar. Ini mengkompilasi dengan -std=c++0x -mpopcnt -O2dan membutuhkan 1,01ms untuk berjalan dalam mode 32 bit (saya tidak memiliki versi GCC 64-bit di tangan).
Stefan
Bisakah Anda membuatnya mencetak output? Saya tidak yakin apakah itu benar-benar melakukan sesuatu saat ini :)
7
Kamu jelas penyihir. +1
BurntPizza
76

Python2.7 + Numpy 1.8.1: 10.242 s

Fortran 90+: 0,029 s 0,003 s 0,022 s 0,010 s

Sialan Anda kehilangan taruhan! Bukan setetes paralelisasi di sini juga, cukup lurus Fortran 90+.

EDIT Saya telah mengambil algoritma Guy Sirton untuk permutasi array S(good find: D). Saya rupanya juga memiliki -g -tracebackflag compiler aktif yang memperlambat kode ini menjadi sekitar 0,017. Saat ini, saya menyusun ini sebagai

ifort -fast -o convolve convolve_random_arrays.f90

Bagi yang belum punya ifort, bisa Anda gunakan

gfortran -O3 -ffast-math -o convolve convolve_random_arrays.f90

EDIT 2 : Penurunan run-time adalah karena saya melakukan sesuatu yang salah sebelumnya dan mendapat jawaban yang salah. Melakukannya dengan cara yang benar tampaknya lebih lambat. Saya masih tidak percaya bahwa C ++ lebih cepat dari milik saya, jadi saya mungkin akan menghabiskan beberapa waktu minggu ini mencoba untuk mengubah omong kosong dari ini untuk mempercepatnya.

EDIT 3 : Dengan hanya mengubah bagian RNG menggunakan yang didasarkan pada RNG BSD (seperti yang disarankan oleh Sampo Smolander) dan menghilangkan pembagian konstan m1, saya memotong run-time sama dengan jawaban C ++ oleh Guy Sirton . Menggunakan array statis (seperti yang disarankan oleh Sharpie) menjatuhkan run-time di bawah run-time C ++! Yay Fortran! : D

EDIT 4 Rupanya ini tidak mengkompilasi (dengan gfortran) dan berjalan dengan benar (nilai yang salah) karena bilangan bulat melampaui batas-batas mereka. Saya telah membuat koreksi untuk memastikannya berfungsi, tetapi ini mengharuskan seseorang untuk memiliki ifort 11+ atau gfortran 4.7+ (atau kompiler lain yang memungkinkan iso_fortran_envdan int64jenis F2008 ).

Ini kodenya:

program convolve_random_arrays
   use iso_fortran_env
   implicit none
   integer(int64), parameter :: a1 = 1103515245
   integer(int64), parameter :: c1 = 12345
   integer(int64), parameter :: m1 = 2147483648
   real, parameter ::    mi = 4.656612873e-10 ! 1/m1
   integer, parameter :: n = 6
   integer :: p, pmax, iters, i, nil(0:1), seed
   !integer, allocatable ::  F(:), S(:), FS(:)
   integer :: F(n), S(n+1), FS(2)

   !n = 6
   !allocate(F(n), S(n+1), FS(2))
   iters = 1000
   nil = 0

   !call init_random_seed()

   S = -1
   pmax = 2**(n+1)
   do p=1,pmax
      do i=1,iters
         F = rand_int_array(n)
         if(all(F==0)) then
            do while(all(F==0))
               F = rand_int_array(n)
            enddo
         endif

         FS = convolve(F,S)

         if(FS(1) == 0) then
            nil(0) = nil(0) + 1
            if(FS(2) == 0) nil(1) = nil(1) + 1
         endif

      enddo
      call permute(S)
   enddo

   print *,"first zero:",nil(0)
   print *," both zero:",nil(1)

 contains
   pure function convolve(x, h) result(y)
!x is the signal array
!h is the noise/impulse array
      integer, dimension(:), intent(in) :: x, h
      integer, dimension(abs(size(x)-size(h))+1) :: y
      integer:: i, j, r
      y(1) = dot_product(x,h(1:n-1))
      y(2) = dot_product(x,h(2:n  ))
   end function convolve

   pure subroutine permute(x)
      integer, intent(inout) :: x(:)
      integer :: i

      do i=1,size(x)
         if(x(i)==-1) then
            x(i) = 1
            return
         endif
         x(i) = -1
      enddo
   end subroutine permute

   function rand_int_array(i) result(x)
     integer, intent(in) :: i
     integer :: x(i), j
     real :: y
     do j=1,i
        y = bsd_rng()
        if(y <= 0.25) then
           x(j) = -1
        else if (y >= 0.75) then
           x(j) = +1
        else
           x(j) = 0
        endif
     enddo
   end function rand_int_array

   function bsd_rng() result(x)
      real :: x
      integer(int64) :: b=3141592653
      b = mod(a1*b + c1, m1)
      x = real(b)*mi
   end function bsd_rng
end program convolve_random_arrays

Saya kira pertanyaannya sekarang adalah apakah Anda akan berhenti menggunakan Python slow-as-molasses dan menggunakan Fortran;)

Kyle Kanos
sumber
1
Bukankah pernyataan kasus lebih cepat dari fungsi generator? Kecuali jika Anda mengharapkan semacam percepatan cabang-prediksi / cache-line / etc?
OrangeDog
17
Kecepatan harus dibandingkan pada mesin yang sama. Apa runtime yang Anda dapatkan untuk kode OP?
nbubis
3
Jawaban C ++ mengimplementasikan generator nomor acaknya sendiri yang sangat ringan. Jawaban Anda menggunakan default yang datang dengan kompiler, yang bisa lebih lambat?
Sampo Smolander
3
Juga, contoh C ++ tampaknya menggunakan array yang dialokasikan secara statis. Coba gunakan array panjang tetap yang ditetapkan pada waktu kompilasi dan lihat apakah itu mencukur kapan saja.
Sharpie
1
@KyleKanos @Lembik masalahnya adalah bahwa tugas integer di fortran tidak menggunakan spesifikasi int64 secara implisit, maka angkanya adalah int32 sebelum konversi dilakukan. Kode harus: integer(int64) :: b = 3141592653_int64untuk semua int64. Ini adalah bagian dari standar fortran dan diharapkan oleh programmer dalam bahasa pemrograman tipe-dinyatakan. (perhatikan bahwa pengaturan default tentu saja dapat mengesampingkan ini)
zeroth
69

Python 2.7 - 0.882s 0.283s

(Asli OP: 6.404d)

Sunting: Optimalisasi Steven Rumbalski dengan mengkomputasi nilai F. Dengan optimasi ini, cpython akan mengalahkan 0,365-an pypy.

import itertools
import operator
import random

n=6
iters = 1000
firstzero = 0
bothzero = 0

choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))

for S in itertools.product([-1,1], repeat = n+1):
    for i in xrange(iters):
        F = random.choice(choicesF)
        if not sum(map(operator.mul, F, S[:-1])):
            firstzero += 1
            if not sum(map(operator.mul, F, S[1:])):
                bothzero += 1

print "firstzero", firstzero
print "bothzero", bothzero

Kode asli OP menggunakan array kecil seperti itu tidak ada manfaatnya untuk menggunakan Numpy, seperti yang ditunjukkan oleh implementasi python murni ini. Tetapi lihat juga implementasi numpy ini yang tiga kali lebih cepat dari kode saya.

Saya juga mengoptimalkan dengan melewatkan sisa konvolusi jika hasil pertama tidak nol.

Alistair Buxton
sumber
11
Dengan pypy ini beroperasi dalam waktu sekitar 0,5 detik.
Alistair Buxton
2
Anda mendapatkan speedup yang jauh lebih meyakinkan jika Anda menetapkan n = 10. Saya mendapatkan 19s versus 4.6s untuk cpython versus pypy.
3
Optimalisasi lain adalah untuk mencegah kemungkinan Fkarena hanya ada 4032 di antaranya. Tentukan di choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))luar loop. Kemudian di innerloop tentukan F = random.choice(choicesF). Saya mendapatkan speedx 3x dengan pendekatan seperti itu.
Steven Rumbalski
3
Bagaimana dengan kompilasi ini di Cython? Kemudian menambahkan beberapa tipe statis yang bijaksana?
Thane Brimhall
2
Letakkan semuanya dalam suatu fungsi dan sebut itu di akhir. Itu melokalkan nama, yang juga membuat optimisasi yang diusulkan oleh @riffraff berfungsi. Juga, pindahkan pembuatan dari range(iters)loop. Secara keseluruhan, saya mendapatkan speedup sekitar 7% atas jawaban Anda yang sangat bagus.
Wolfram
44

Karat: 0,011s

Python Asli: 8.3

Terjemahan langsung dari Python asli.

extern crate rand;

use rand::Rng;

static N: uint = 6;
static ITERS: uint = 1000;

fn convolve<T: Num>(into: &mut [T], a: &[T], b: &[T]) {
    // we want `a` to be the longest array
    if a.len() < b.len() {
        convolve(into, b, a);
        return
    }

    assert_eq!(into.len(), a.len() - b.len() + 1);

    for (n,place) in into.mut_iter().enumerate() {
        for (x, y) in a.slice_from(n).iter().zip(b.iter()) {
            *place = *place + *x * *y
        }
    }
}

fn main() {
    let mut first_zero = 0;
    let mut both_zero = 0;
    let mut rng = rand::XorShiftRng::new().unwrap();

    for s in PlusMinus::new() {
        for _ in range(0, ITERS) {
            let mut f = [0, .. N];
            while f.iter().all(|x| *x == 0) {
                for p in f.mut_iter() {
                    match rng.gen::<u32>() % 4 {
                        0 => *p = -1,
                        1 | 2 => *p = 0,
                        _ => *p = 1
                    }
                }
            }

            let mut fs = [0, .. 2];
            convolve(fs, s, f);

            if fs[0] == 0 { first_zero += 1 }
            if fs.iter().all(|&x| x == 0) { both_zero += 1 }
        }
    }

    println!("{}\n{}", first_zero, both_zero);
}



/// An iterator over [+-]1 arrays of the appropriate length
struct PlusMinus {
    done: bool,
    current: [i32, .. N + 1]
}
impl PlusMinus {
    fn new() -> PlusMinus {
        PlusMinus { done: false, current: [-1, .. N + 1] }
    }
}

impl Iterator<[i32, .. N + 1]> for PlusMinus {
    fn next(&mut self) -> Option<[i32, .. N+1]> {
        if self.done {
            return None
        }

        let ret = self.current;

        // a binary "adder", that just adds one to a bit vector (where
        // -1 is the zero, and 1 is the one).
        for (i, place) in self.current.mut_iter().enumerate() {
            *place = -*place;
            if *place == 1 {
                break
            } else if i == N {
                // we've wrapped, so we want to stop after this one
                self.done = true
            }
        }

        Some(ret)
    }
}
  • Disusun dengan --opt-level=3
  • Kompiler karat saya adalah malam terakhir : ( rustc 0.11-pre-nightly (eea4909 2014-04-24 23:41:15 -0700)tepatnya)
huon
sumber
Saya mendapatkannya untuk dikompilasi menggunakan versi karat malam. Namun saya pikir kodenya salah. Output harus sesuatu yang dekat dengan firstzero 27215 bothzero 12086. Sebaliknya itu memberikan 27367 6481
@Lembik, wah, membuat saya adan saya bterlibat dalam belitan; diperbaiki (tidak mengubah runtime secara nyata).
huon
4
Ini adalah demonstrasi kecepatan karat yang sangat bagus.
39

C ++ (VS 2012) - 0,026s 0,015s

Python 2.7.6 / Numpy 1.8.1 - 12s

Speedup ~ x800.

Kesenjangan akan jauh lebih kecil jika array yang berbelit-belit sangat besar ...

#include <vector>
#include <iostream>
#include <ctime>

using namespace std;

static unsigned int seed = 35;

int my_random()
{
   seed = seed*1664525UL + 1013904223UL; // numerical recipes, 32 bit

   switch((seed>>30) & 3)
   {
   case 0: return 0;
   case 1: return -1;
   case 2: return 1;
   case 3: return 0;
   }
   return 0;
}

bool allzero(const vector<int>& T)
{
   for(auto x : T)
   {
      if(x!=0)
      {
         return false;
      }
   }
   return true;
}

void convolve(vector<int>& out, const vector<int>& v1, const vector<int>& v2)
{
   for(size_t i = 0; i<out.size(); ++i)
   {
      int result = 0;
      for(size_t j = 0; j<v2.size(); ++j)
      {
         result += v1[i+j]*v2[j];
      }
      out[i] = result;
   }
}

void advance(vector<int>& v)
{
   for(auto &x : v)
   {
      if(x==-1)
      {
         x = 1;
         return;
      }
      x = -1;
   }
}

void convolve_random_arrays(void)
{
   const size_t n = 6;
   const int two_to_n_plus_one = 128;
   const int iters = 1000;
   int bothzero = 0;
   int firstzero = 0;

   vector<int> S(n+1);
   vector<int> F(n);
   vector<int> FS(2);

   time_t current_time;
   time(&current_time);
   seed = current_time;

   for(auto &x : S)
   {
      x = -1;
   }
   for(int i=0; i<two_to_n_plus_one; ++i)
   {
      for(int j=0; j<iters; ++j)
      {
         do
         {
            for(auto &x : F)
            {
               x = my_random();
            }
         } while(allzero(F));
         convolve(FS, S, F);
         if(FS[0] == 0)
         {
            firstzero++;
            if(FS[1] == 0)
            {
               bothzero++;
            }
         }
      }
      advance(S);
   }
   cout << firstzero << endl; // This output can slow things down
   cout << bothzero << endl; // comment out for timing the algorithm
}

Beberapa catatan:

  • Fungsi acak dipanggil dalam loop jadi saya pergi untuk generator congruential linear yang sangat ringan (tapi dengan murah hati melihat MSB).
  • Ini benar-benar hanya titik awal untuk solusi yang dioptimalkan.
  • Tidak butuh waktu lama untuk menulis ...
  • Saya beralih melalui semua nilai S yang diambil S[0]sebagai digit "paling tidak signifikan".

Tambahkan fungsi utama ini untuk contoh yang lengkap:

int main(int argc, char** argv)
{
  for(int i=0; i<1000; ++i) // run 1000 times for stop-watch
  {
      convolve_random_arrays();
  }
}
Guy Sirton
sumber
1
Memang. Ukuran kecil array dalam kode OP berarti menggunakan numpy sebenarnya adalah urutan besarnya lebih lambat daripada python lurus.
Alistair Buxton
2
Sekarang x800 adalah apa yang saya bicarakan!
Sangat bagus! Saya telah meningkatkan kecepatan pada kode saya karena advancefungsi Anda , jadi kode saya sekarang lebih cepat daripada milik Anda: P (tapi persaingan yang sangat bagus!)
Kyle Kanos
1
@lembik ya seperti kata Mat. Anda membutuhkan dukungan C ++ 11 dan fungsi utama. Beri tahu saya jika Anda memerlukan bantuan lebih lanjut untuk menjalankan ini ...
Guy Sirton
2
Saya baru saja menguji ini dan dapat mencukur 20% lainnya dengan menggunakan array biasa, bukan std :: vector ..
PlasmaHH
21

C

Membawa 0,015 detik pada mesin saya, dengan kode asli OP mengambil ~ 7,7 detik. Mencoba mengoptimalkan dengan membuat array acak dan berbelit-belit dalam loop yang sama, tetapi tampaknya tidak membuat banyak perbedaan.

Array pertama dihasilkan dengan mengambil integer, menuliskannya dalam biner, dan mengubah semua 1 menjadi -1 dan semua 0 menjadi 1. Selebihnya harus sangat mudah.

Sunting: alih-alih memiliki nsebagai int, sekarang kita memiliki nkonstanta yang didefinisikan secara makro, jadi kita dapat menggunakannya int arr[n];sebagai ganti malloc.

Sunting2: Alih-alih rand()fungsi bawaan, ini sekarang mengimplementasikan PRNG xorshift. Juga, banyak pernyataan bersyarat dihapus ketika membuat array acak.

Kompilasi instruksi:

gcc -O3 -march=native -fwhole-program -fstrict-aliasing -ftree-vectorize -Wall ./test.c -o ./test

Kode:

#include <stdio.h>
#include <time.h>

#define n (6)
#define iters (1000)
unsigned int x,y=34353,z=57768,w=1564; //PRNG seeds

/* xorshift PRNG
 * Taken from https://en.wikipedia.org/wiki/Xorshift#Example_implementation
 * Used under CC-By-SA */
int myRand() {
    unsigned int t;
    t = x ^ (x << 11);
    x = y; y = z; z = w;
    return w = w ^ (w >> 19) ^ t ^ (t >> 8);
}

int main() {
    int firstzero=0, bothzero=0;
    int arr[n+1];
    unsigned int i, j;
    x=(int)time(NULL);

    for(i=0; i< 1<<(n+1) ; i++) {
        unsigned int tmp=i;
        for(j=0; j<n+1; j++) {
            arr[j]=(tmp&1)*(-2)+1;
            tmp>>=1;
        }
        for(j=0; j<iters; j++) {
            int randArr[n];
            unsigned int k, flag=0;
            int first=0, second=0;
            do {
                for(k=0; k<n; k++) {
                    randArr[k]=(1-(myRand()&3))%2;
                    flag+=(randArr[k]&1);
                    first+=arr[k]*randArr[k];
                    second+=arr[k+1]*randArr[k];
                }
            } while(!flag);
            firstzero+=(!first);
            bothzero+=(!first&&!second);
        }
    }
    printf("firstzero %d\nbothzero %d\n", firstzero, bothzero);
    return 0;
}
ace_HongKongIndependence
sumber
1
Saya menguji ini. Ini sangat cepat (coba n = 10) dan memberikan hasil yang benar. Terima kasih.
Implementasi ini tidak mengikuti aslinya karena jika vektor acak semua nol hanya elemen terakhir yang akan dihasilkan kembali. Dalam aslinya seluruh vektor akan menjadi. Anda harus menyertakan loop itu do{}while(!flag)atau sesuatu dengan efek itu. Saya tidak berharap ini akan banyak mengubah run-time (dapat membuatnya lebih cepat).
Guy Sirton
@Guy Sirton Perhatikan bahwa sebelum continue;pernyataan saya ditugaskan -1untuk k, sehingga kakan loop dari 0 lagi.
ace_HongKongIndependence
1
@ Ah ah! kamu benar. Saya memindai terlalu cepat dan sepertinya itu -=bukan =-:-) Suatu saat loop akan lebih mudah dibaca.
Guy Sirton
17

J

Saya tidak berharap untuk mengalahkan bahasa yang dikompilasi, dan sesuatu mengatakan kepada saya bahwa ini akan membutuhkan mesin ajaib untuk mendapatkan kurang dari 0,09 detik dengan ini, tetapi saya tetap ingin mengirimkan J ini, karena itu cukup apik.

NB. constants
num =: 6
iters =: 1000

NB. convolve
NB. take the multiplication table                */
NB. then sum along the NE-SW diagonals           +//.
NB. and keep the longest ones                    #~ [: (= >./) #/.
NB. operate on rows of higher dimensional lists  " 1
conv =: (+//. #~ [: (= >./) #/.) @: (*/) " 1

NB. main program
S  =: > , { (num+1) # < _1 1                NB. all {-1,1}^(num+1)
F  =: (3&= - 0&=) (iters , num) ?@$ 4       NB. iters random arrays of length num
FS =: ,/ S conv/ F                          NB. make a convolution table
FB =: +/ ({. , *./)"1 ] 0 = FS              NB. first and both zero
('first zero ',:'both zero ') ,. ":"0 FB    NB. output results

Ini membutuhkan waktu sekitar 0,5 detik pada laptop dari dekade sebelumnya, hanya sekitar 20 kali lebih cepat dari Python dalam jawabannya. Sebagian besar waktu dihabiskan convkarena kami menulisnya dengan malas (kami menghitung seluruh lilitan) dan secara umum sepenuhnya.

Karena kita mengetahui banyak hal Sdan F, kita dapat mempercepat dengan membuat optimasi khusus untuk program ini. Yang terbaik yang bisa saya dapatkan adalah — conv =: ((num, num+1) { +//.)@:(*/)"1pilih secara khusus dua angka yang sesuai dari jumlah diagonal hingga elemen terpanjang dari konvolusi — yang kira-kira mengurangi separuh waktu.

algoritme hiu
sumber
6
J selalu layak
disampaikan
17

Perl - 9.3X lebih cepat ... 830% peningkatan

Di netbook kuno saya, kode OP membutuhkan waktu 53 detik untuk dijalankan; Versi Alistair Buxton membutuhkan waktu sekitar 6,5 detik, dan versi Perl berikut membutuhkan waktu sekitar 5,7 detik.

use v5.10;
use strict;
use warnings;

use Algorithm::Combinatorics qw( variations_with_repetition );
use List::Util qw( any sum );
use List::MoreUtils qw( pairwise );

my $n         = 6;
my $iters     = 1000;
my $firstzero = 0;
my $bothzero  = 0;

my $variations = variations_with_repetition([-1, 1], $n+1);
while (my $S = $variations->next)
{
  for my $i (1 .. $iters)
  {
    my @F;
    until (@F and any { $_ } @F)
    {
      @F = map +((-1,0,0,1)[rand 4]), 1..$n;
    }

    # The pairwise function doesn't accept array slices,
    # so need to copy into a temp array @S0
    my @S0 = @$S[0..$n-1];

    unless (sum pairwise { $a * $b } @F, @S0)
    {
      $firstzero++;
      my @S1 = @$S[1..$n];  # copy again :-(
      $bothzero++ unless sum pairwise { $a * $b } @F, @S1;
    }
  }
}

say "firstzero ", $firstzero;
say "bothzero ", $bothzero;
tobyink
sumber
12

Python 2.7 - numpy 1.8.1 dengan binding mkl - 0.086s

(Asli OP: 6.404s) (python murni Buxton: 0.270s)

import numpy as np
import itertools

n=6
iters = 1000

#Pack all of the Ses into a single array
S = np.array( list(itertools.product([-1,1], repeat=n+1)) )

# Create a whole array of test arrays, oversample a bit to ensure we 
# have at least (iters) of them
F = np.random.rand(int(iters*1.1),n)
F = ( F < 0.25 )*-1 + ( F > 0.75 )*1
goodrows = (np.abs(F).sum(1)!=0)
assert goodrows.sum() > iters, "Got very unlucky"
# get 1000 cases that aren't all zero
F = F[goodrows][:iters]

# Do the convolution explicitly for the two 
# slots, but on all of the Ses and Fes at the 
# same time
firstzeros = (F[:,None,:]*S[None,:,:-1]).sum(-1)==0
secondzeros = (F[:,None,:]*S[None,:,1:]).sum(-1)==0

firstzero_count = firstzeros.sum()
bothzero_count = (firstzeros * secondzeros).sum()
print "firstzero", firstzero_count
print "bothzero", bothzero_count

Seperti yang ditunjukkan Buxton, kode asli OP menggunakan array sekecil itu, tidak ada manfaatnya menggunakan Numpy. Implementasi ini memanfaatkan numpy dengan melakukan semua kasus F dan S sekaligus dengan cara yang berorientasi array. Ini dikombinasikan dengan binding mkl untuk python mengarah ke implementasi yang sangat cepat.

Perhatikan juga bahwa hanya memuat pustaka dan memulai interpreter memerlukan waktu 0,076s sehingga perhitungan sebenarnya memakan waktu ~ 0,01 detik, mirip dengan solusi C ++.

alemi
sumber
Apa itu mkl bindings dan bagaimana cara mendapatkannya di ubuntu?
Running python -c "import numpy; numpy.show_config()"akan menunjukkan kepada Anda jika versi numpy Anda dikompilasi dengan blas / atlas / mkl, dll. ATLAS adalah paket matematika akselerasi gratis yang numpy dapat dihubungkan , Intel MKL yang biasanya harus Anda bayar (kecuali Anda seorang akademisi) dan dapat dihubungkan dengan numpy / scipy .
alemi
Untuk cara yang mudah, gunakan distribusi python anaconda dan gunakan paket akselerasi . Atau gunakan distribusi pemikiran .
alemi
Jika Anda menggunakan windows, unduh numpy dari sini . Installer numpy yang telah dikompilasi sebelumnya yang terhubung dengan MKL.
Nama Palsu
9

MATLAB 0,024s

Komputer 1

  • Kode Asli: ~ 3.3 s
  • Kode Alistar Buxton: ~ 0,51 dtk
  • Kode baru Alistar Buxton: ~ 0,25 dtk
  • Kode Matlab: ~ 0,024 s (Matlab sudah berjalan)

Komputer 2

  • Kode Asli: ~ 6.66 dtk
  • Kode Alistar Buxton: ~ 0,64 dtk
  • Kode baru Alistar Buxton:?
  • Matlab: ~ 0,07 s (Matlab sudah berjalan)
  • Oktaf: ~ 0,07 dtk

Saya memutuskan untuk mencoba Matlab yang sangat lambat. Jika Anda tahu caranya, Anda dapat menghilangkan sebagian besar loop (di Matlab), yang membuatnya cukup cepat. Namun, persyaratan memori lebih tinggi daripada untuk solusi looped tetapi ini tidak akan menjadi masalah jika Anda tidak memiliki array yang sangat besar ...

function call_convolve_random_arrays
tic
convolve_random_arrays
toc
end

function convolve_random_arrays

n = 6;
iters = 1000;
firstzero = 0;
bothzero = 0;

rnd = [-1, 0, 0, 1];

S = -1 *ones(1, n + 1);

IDX1 = 1:n;
IDX2 = IDX1 + 1;

for i = 1:2^(n + 1)
    F = rnd(randi(4, [iters, n]));
    sel = ~any(F,2);
    while any(sel)
        F(sel, :) = rnd(randi(4, [sum(sel), n]));
        sel = ~any(F,2);
    end

    sum1 = F * S(IDX1)';
    sel = sum1 == 0;
    firstzero = firstzero + sum(sel);

    sum2 = F(sel, :) * S(IDX2)';
    sel = sum2 == 0;
    bothzero = bothzero + sum(sel);

    S = permute(S); 
end

fprintf('firstzero %i \nbothzero %i \n', firstzero, bothzero);

end

function x = permute(x)

for i=1:length(x)
    if(x(i)==-1)
        x(i) = 1;
            return
    end
        x(i) = -1;
end

end

Inilah yang saya lakukan:

  • gunakan fungsi Kyle Kanos untuk mengubah arah melalui S
  • hitung semua n * iter angka acak sekaligus
  • peta 1 hingga 4 hingga [-1 0 0 1]
  • gunakan perkalian Matriks (jumlah elemen (F * S (1: 5)) sama dengan perkalian matriks F * S (1: 5) '
  • untuk bothzero: hanya menghitung anggota yang memenuhi persyaratan pertama

Saya berasumsi Anda tidak memiliki matlab, yang terlalu buruk karena saya benar-benar ingin melihat bagaimana membandingkannya ...

(Fungsi ini bisa lebih lambat saat pertama kali Anda menjalankannya.)

matematika
sumber
Yah saya punya satu oktaf jika Anda bisa membuatnya bekerja untuk itu ...?
Saya bisa mencobanya - saya tidak pernah bekerja dengan oktaf.
mathause
Ok, saya bisa menjalankannya seperti pada oktaf jika saya memasukkan kode dalam file bernama call_convolve_random_arrays.m dan kemudian memanggilnya dari oktaf.
mathause
Apakah perlu kode lagi untuk benar-benar membuatnya melakukan sesuatu? Ketika saya melakukan "oktaf call_convolve_random_arrays.m" itu tidak menghasilkan apa-apa. Lihat bpaste.net/show/JPtLOCeI3aP3wc3F3aGf
maaf, coba buka oktaf dan jalankan kemudian. Seharusnya menampilkan firstzero, bothzero dan waktu eksekusi.
mathause
7

Julia: 0,30 dtk

Op's Python: 21.36 s (Core2 duo)

Kecepatan 71x

function countconv()                                                                                                                                                           
    n = 6                                                                                                                                                                      
    iters = 1000                                                                                                                                                               
    firstzero = 0                                                                                                                                                              
    bothzero = 0                                                                                                                                                               
    cprod= Iterators.product(fill([-1,1], n+1)...)                                                                                                                             
    F=Array(Float64,n);                                                                                                                                                        
    P=[-1. 0. 0. 1.]                                                                                                                                                                                                                                                                                                             

    for S in cprod                                                                                                                                                             
        Sm=[S...]                                                                                                                                                              
        for i = 1:iters                                                                                                                                                        
            F=P[rand(1:4,n)]                                                                                                                                                  
            while all(F==0)                                                                                                                                                   
                F=P[rand(1:4,n)]                                                                                                                                              
            end                                                                                                                                                               
            if  dot(reverse!(F),Sm[1:end-1]) == 0                                                                                                                           
                firstzero += 1                                                                                                                                                 
                if dot(F,Sm[2:end]) == 0                                                                                                                              
                    bothzero += 1                                                                                                                                              
                end                                                                                                                                                            
            end                                                                                                                                                                
        end                                                                                                                                                                    
    end
    return firstzero,bothzero
end

Saya melakukan beberapa modifikasi dari jawaban Arman Julia: Pertama-tama, saya membungkusnya dalam suatu fungsi, karena variabel global menyulitkan inferensi tipe Julia dan JIT: Sebuah variabel global dapat mengubah tipenya kapan saja, dan harus diperiksa setiap operasi . Kemudian, saya menyingkirkan fungsi anonim dan pemahaman array. Mereka tidak benar-benar diperlukan, dan masih sangat lambat. Julia lebih cepat dengan abstraksi tingkat rendah sekarang.

Ada banyak cara untuk membuatnya lebih cepat, tetapi ini melakukan pekerjaan yang layak.

pengguna20768
sumber
Apakah Anda mengukur waktu dalam REPL atau menjalankan seluruh file dari baris perintah?
Aditya
baik dari REPL.
user20768
6

Ok saya memposting ini hanya karena saya merasa Jawa perlu diwakili di sini. Saya buruk dengan bahasa lain dan saya mengaku tidak mengerti masalah sebenarnya, jadi saya perlu bantuan untuk memperbaiki kode ini. Saya mencuri sebagian besar contoh kode ace's C, dan kemudian meminjam beberapa cuplikan dari yang lain. Saya harap itu bukan ...

Satu hal yang ingin saya tunjukkan adalah bahwa bahasa yang dioptimalkan pada waktu berjalan perlu dijalankan beberapa kali untuk mencapai kecepatan penuh. Saya pikir dibenarkan untuk mengambil kecepatan yang sepenuhnya dioptimalkan (atau setidaknya kecepatan rata-rata) karena kebanyakan hal yang Anda khawatirkan dengan berlari cepat akan berjalan beberapa kali.

Kode masih perlu diperbaiki, tetapi saya menjalankannya untuk melihat berapa kali saya akan mendapatkan.

Berikut adalah hasil dari CPU Intel (R) Xeon (R) E3-1270 V2 @ 3.50GHz di Ubuntu yang menjalankannya 1000 kali:

server: / tmp # time java8 -cp. Penguji

firstzero 40000

bothzero 20000

run time pertama: 41 ms run time terakhir: 4 ms

0m5.014s nyata pengguna 0m4.664s sys 0m0.268s

Ini kode jelek saya:

public class Tester 
{
    public static void main( String[] args )
    {
        long firstRunTime = 0;
        long lastRunTime = 0;
        String testResults = null;
        for( int i=0 ; i<1000 ; i++ )
        {
            long timer = System.currentTimeMillis();
            testResults = new Tester().runtest();
            lastRunTime = System.currentTimeMillis() - timer;
            if( i ==0 )
            {
                firstRunTime = lastRunTime;
            }
        }
        System.err.println( testResults );
        System.err.println( "first run time: " + firstRunTime + " ms" );
        System.err.println( "last run time: " + lastRunTime + " ms" );
    }

    private int x,y=34353,z=57768,w=1564; 

    public String runtest()
    {
        int n = 6;
        int iters = 1000;
        //#define iters (1000)
        //PRNG seeds

        /* xorshift PRNG
         * Taken from https://en.wikipedia.org/wiki/Xorshift#Example_implementation
         * Used under CC-By-SA */

            int firstzero=0, bothzero=0;
            int[] arr = new int[n+1];
            int i=0, j=0;
            x=(int)(System.currentTimeMillis()/1000l);

            for(i=0; i< 1<<(n+1) ; i++) {
                int tmp=i;
                for(j=0; j<n+1; j++) {
                    arr[j]=(tmp&1)*(-2)+1;
                    tmp>>=1;
                }
                for(j=0; j<iters; j++) {
                    int[] randArr = new int[n];
                    int k=0;
                    long flag = 0;
                    int first=0, second=0;
                    do {
                        for(k=0; k<n; k++) {
                            randArr[k]=(1-(myRand()&3))%2;
                            flag+=(randArr[k]&1);
                            first+=arr[k]*randArr[k];
                            second+=arr[k+1]*randArr[k];
                        }
                    } while(allzero(randArr));
                    if( first == 0 )
                    {
                        firstzero+=1;
                        if( second == 0 )
                        {
                            bothzero++;
                        }
                    }
                }
            }
         return ( "firstzero " + firstzero + "\nbothzero " + bothzero + "\n" );
    }

    private boolean allzero(int[] arr)
    {
       for(int x : arr)
       {
          if(x!=0)
          {
             return false;
          }
       }
       return true;
    }

    public int myRand() 
    {
        long t;
        t = x ^ (x << 11);
        x = y; y = z; z = w;
        return (int)( w ^ (w >> 19) ^ t ^ (t >> 8));
    }
}

Dan saya mencoba menjalankan kode python setelah memutakhirkan python dan menginstal python-numpy tetapi saya mendapatkan ini:

server:/tmp# python tester.py
Traceback (most recent call last):
  File "peepee.py", line 15, in <module>
    F = np.random.choice(np.array([-1,0,0,1], dtype=np.int8), size = n)
AttributeError: 'module' object has no attribute 'choice'
Chris Seline
sumber
Komentar: Jangan pernah gunakan currentTimeMillisuntuk pembandingan (gunakan versi nano dalam Sistem) dan 1k menjalankan mungkin tidak cukup untuk melibatkan JIT (1.5k untuk klien dan 10k untuk server akan menjadi default, meskipun Anda cukup sering memanggil myRand sehingga akan menjadi JITed yang seharusnya menyebabkan beberapa fungsi di callstack untuk dikompilasi yang dapat bekerja di sini). Terakhir namun tidak sedikit PNRG yang lemah curang, tetapi begitu juga solusi C ++ dan lainnya, jadi saya kira itu tidak terlalu tidak adil.
Voo
Pada windows Anda harus menghindari currentTimeMillis, tetapi untuk linux untuk semua pengukuran granularitas yang sangat halus, Anda tidak memerlukan waktu nano, dan panggilan untuk mendapatkan waktu nano jauh lebih mahal daripada milid. Jadi saya sangat tidak setuju bahwa Anda TIDAK PERNAH menggunakannya.
Chris Seline
Jadi Anda menulis kode Java untuk satu implementasi OS dan JVM tertentu? Sebenarnya saya tidak yakin OS mana yang Anda gunakan, karena saya baru saja memeriksa HotSpot dev tree dan Linux saya gunakan gettimeofday(&time, NULL)untuk miliSeconds yang tidak monoton dan tidak memberikan jaminan akurasi (jadi pada beberapa platform / kernel persis sama. masalah sebagai implementasi Windows currentTimeMillis - sehingga yang baik juga atau tidak adalah). nanoTime di sisi lain menggunakan clock_gettime(CLOCK_MONOTONIC, &tp)yang jelas juga merupakan hal yang tepat untuk digunakan ketika melakukan benchmarking di Linux.
Voo
Itu tidak pernah menyebabkan masalah bagi saya karena saya telah mengkode java pada setiap distro atau kernel Linux.
Chris Seline
6

Versi 45X python Golang pada mesin saya di bawah ini kode Golang:

package main

import (
"fmt"
"time"
)

const (
n     = 6
iters = 1000
)

var (
x, y, z, w = 34353, 34353, 57768, 1564 //PRNG seeds
)

/* xorshift PRNG
 * Taken from https://en.wikipedia.org/wiki/Xorshift#Example_implementation
 * Used under CC-By-SA */
func myRand() int {
var t uint
t = uint(x ^ (x << 11))
x, y, z = y, z, w
w = int(uint(w^w>>19) ^ t ^ (t >> 8))
return w
}

func main() {
var firstzero, bothzero int
var arr [n + 1]int
var i, j int
x = int(time.Now().Unix())

for i = 0; i < 1<<(n+1); i = i + 1 {
    tmp := i
    for j = 0; j < n+1; j = j + 1 {
        arr[j] = (tmp&1)*(-2) + 1
        tmp >>= 1
    }
    for j = 0; j < iters; j = j + 1 {
        var randArr [n]int
        var flag uint
        var k, first, second int
        for {
            for k = 0; k < n; k = k + 1 {
                randArr[k] = (1 - (myRand() & 3)) % 2
                flag += uint(randArr[k] & 1)
                first += arr[k] * randArr[k]
                second += arr[k+1] * randArr[k]
            }
            if flag != 0 {
                break
            }
        }
        if first == 0 {
            firstzero += 1
            if second == 0 {
                bothzero += 1
            }
        }
    }
}
println("firstzero", firstzero, "bothzero", bothzero)
}

dan kode python di bawah ini disalin dari atas:

import itertools
import operator
import random

n=6
iters = 1000
firstzero = 0
bothzero = 0

choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))

for S in itertools.product([-1,1], repeat = n+1):
    for i in xrange(iters):
        F = random.choice(choicesF)
        if not sum(map(operator.mul, F, S[:-1])):
            firstzero += 1
            if not sum(map(operator.mul, F, S[1:])):
                bothzero += 1

print "firstzero", firstzero
print "bothzero", bothzero

dan waktu di bawah ini:

$time python test.py
firstzero 27349
bothzero 12125

real    0m0.477s
user    0m0.461s
sys 0m0.014s

$time ./hf
firstzero 27253 bothzero 12142

real    0m0.011s
user    0m0.008s
sys 0m0.002s
lunny
sumber
1
Pernahkah Anda berpikir untuk menggunakan "github.com/yanatan16/itertools"? Anda juga akan mengatakan ini akan bekerja dengan baik di beberapa goroutine?
ymg
5

C # 0.135s

C # berdasarkan pada python polos Alistair Buxton : 0.278s
Parallelised C #: 0.135s
Python dari pertanyaan: 5.907s
python polos Alistair: 0.853s

Saya tidak benar-benar yakin implementasi ini benar - outputnya berbeda, jika Anda melihat hasilnya di bagian bawah.

Tentu saja ada algoritma yang lebih optimal. Saya baru saja memutuskan untuk menggunakan algoritma yang sangat mirip dengan yang Python.

Utas tunggal C

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConvolvingArrays
{
    static class Program
    {
        static void Main(string[] args)
        {
            int n=6;
            int iters = 1000;
            int firstzero = 0;
            int bothzero = 0;

            int[] arraySeed = new int[] {-1, 1};
            int[] randomSource = new int[] {-1, 0, 0, 1};
            Random rand = new Random();

            foreach (var S in Enumerable.Repeat(arraySeed, n+1).CartesianProduct())
            {
                for (int i = 0; i < iters; i++)
                {
                    var F = Enumerable.Range(0, n).Select(_ => randomSource[rand.Next(randomSource.Length)]);
                    while (!F.Any(f => f != 0))
                    {
                        F = Enumerable.Range(0, n).Select(_ => randomSource[rand.Next(randomSource.Length)]);
                    }
                    if (Enumerable.Zip(F, S.Take(n), (f, s) => f * s).Sum() == 0)
                    {
                        firstzero++;
                        if (Enumerable.Zip(F, S.Skip(1), (f, s) => f * s).Sum() == 0)
                        {
                            bothzero++;
                        }
                    }
                }
            }

            Console.WriteLine("firstzero {0}", firstzero);
            Console.WriteLine("bothzero {0}", bothzero);
        }

        // itertools.product?
        // http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
        static IEnumerable<IEnumerable<T>> CartesianProduct<T>
            (this IEnumerable<IEnumerable<T>> sequences)
        {
            IEnumerable<IEnumerable<T>> emptyProduct =
              new[] { Enumerable.Empty<T>() };
            return sequences.Aggregate(
              emptyProduct,
              (accumulator, sequence) =>
                from accseq in accumulator
                from item in sequence
                select accseq.Concat(new[] { item }));
        }
    }
}

Paralel C #:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConvolvingArrays
{
    static class Program
    {
        static void Main(string[] args)
        {
            int n=6;
            int iters = 1000;
            int firstzero = 0;
            int bothzero = 0;

            int[] arraySeed = new int[] {-1, 1};
            int[] randomSource = new int[] {-1, 0, 0, 1};

            ConcurrentBag<int[]> results = new ConcurrentBag<int[]>();

            // The next line iterates over arrays of length n+1 which contain only -1s and 1s
            Parallel.ForEach(Enumerable.Repeat(arraySeed, n + 1).CartesianProduct(), (S) =>
            {
                int fz = 0;
                int bz = 0;
                ThreadSafeRandom rand = new ThreadSafeRandom();
                for (int i = 0; i < iters; i++)
                {
                    var F = Enumerable.Range(0, n).Select(_ => randomSource[rand.Next(randomSource.Length)]);
                    while (!F.Any(f => f != 0))
                    {
                        F = Enumerable.Range(0, n).Select(_ => randomSource[rand.Next(randomSource.Length)]);
                    }
                    if (Enumerable.Zip(F, S.Take(n), (f, s) => f * s).Sum() == 0)
                    {
                        fz++;
                        if (Enumerable.Zip(F, S.Skip(1), (f, s) => f * s).Sum() == 0)
                        {
                            bz++;
                        }
                    }
                }

                results.Add(new int[] { fz, bz });
            });

            foreach (int[] res in results)
            {
                firstzero += res[0];
                bothzero += res[1];
            }

            Console.WriteLine("firstzero {0}", firstzero);
            Console.WriteLine("bothzero {0}", bothzero);
        }

        // itertools.product?
        // http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
        static IEnumerable<IEnumerable<T>> CartesianProduct<T>
            (this IEnumerable<IEnumerable<T>> sequences)
        {
            IEnumerable<IEnumerable<T>> emptyProduct =
              new[] { Enumerable.Empty<T>() };
            return sequences.Aggregate(
              emptyProduct,
              (accumulator, sequence) =>
                from accseq in accumulator
                from item in sequence
                select accseq.Concat(new[] { item }));
        }
    }

    // http://stackoverflow.com/a/11109361/1030702
    public class ThreadSafeRandom
    {
        private static readonly Random _global = new Random();
        [ThreadStatic]
        private static Random _local;

        public ThreadSafeRandom()
        {
            if (_local == null)
            {
                int seed;
                lock (_global)
                {
                    seed = _global.Next();
                }
                _local = new Random(seed);
            }
        }
        public int Next()
        {
            return _local.Next();
        }
        public int Next(int maxValue)
        {
            return _local.Next(maxValue);
        }
    }
}

Output tes:

Windows (.NET)

C # jauh lebih cepat di Windows. Mungkin karena .NET lebih cepat daripada mono.

Waktu pengguna dan sistem tampaknya tidak berfungsi (digunakan git bashuntuk menghitung waktu).

$ time /c/Python27/python.exe numpypython.py
firstzero 27413
bothzero 12073

real    0m5.907s
user    0m0.000s
sys     0m0.000s
$ time /c/Python27/python.exe plainpython.py
firstzero 26983
bothzero 12033

real    0m0.853s
user    0m0.000s
sys     0m0.000s
$ time ConvolvingArrays.exe
firstzero 28526
bothzero 6453

real    0m0.278s
user    0m0.000s
sys     0m0.000s
$ time ConvolvingArraysParallel.exe
firstzero 28857
bothzero 6485

real    0m0.135s
user    0m0.000s
sys     0m0.000s

Linux (mono)

bob@phoebe:~/convolvingarrays$ time python program.py
firstzero 27059
bothzero 12131

real    0m11.932s
user    0m11.912s
sys     0m0.012s
bob@phoebe:~/convolvingarrays$ mcs -optimize+ -debug- program.cs
bob@phoebe:~/convolvingarrays$ time mono program.exe
firstzero 28982
bothzero 6512

real    0m1.360s
user    0m1.532s
sys     0m0.872s
bob@phoebe:~/convolvingarrays$ mcs -optimize+ -debug- parallelprogram.cs
bob@phoebe:~/convolvingarrays$ time mono parallelprogram.exe
firstzero 28857
bothzero 6496

real    0m0.851s
user    0m2.708s
sys     0m3.028s
Bob
sumber
1
Saya tidak berpikir kode itu benar seperti yang Anda katakan. Outputnya tidak benar.
@Lembik Ya Saya akan menghargai jika seseorang bisa mengatakan kepada saya di mana itu salah, - saya tidak bisa mengetahuinya (hanya memiliki sedikit pemahaman tentang apa yang seharusnya dilakukan tidak membantu).
Bob
Akan menarik untuk melihat bagaimana hal ini terjadi dengan .NET Native blogs.msdn.com/b/dotnet/archive/2014/04/02/...
Rick Minerich
@Lembik Saya baru saja membahas semua itu, sejauh yang saya tahu itu harus identik dengan solusi Python lainnya ... sekarang saya benar - benar bingung.
Bob
4

Haskell: ~ 2000x speedup per core

Kompilasi dengan 'ghc -O3 -funbox-strict-fields -threaded -fllvm', dan jalankan dengan '+ RTS -Nk' di mana k adalah jumlah core pada mesin Anda.

import Control.Parallel.Strategies
import Data.Bits
import Data.List
import Data.Word
import System.Random

n = 6 :: Int
iters = 1000 :: Int

data G = G !Word !Word !Word !Word deriving (Eq, Show)

gen :: G -> (Word, G)
gen (G x y z w) = let t  = x `xor` (x `shiftL` 11)
                      w' = w `xor` (w `shiftR` 19) `xor` t `xor` (t `shiftR` 8)
                  in (w', G y z w w')  

mask :: Word -> Word
mask = (.&.) $ (2 ^ n) - 1

gen_nonzero :: G -> (Word, G)
gen_nonzero g = let (x, g') = gen g 
                    a = mask x
                in if a == 0 then gen_nonzero g' else (a, g')


data F = F {zeros  :: !Word, 
            posneg :: !Word} deriving (Eq, Show)

gen_f :: G -> (F, G)       
gen_f g = let (a, g')  = gen_nonzero g
              (b, g'') = gen g'
          in  (F a $ mask b, g'')

inner :: Word -> F -> Int
inner s (F zs pn) = let s' = complement $ s `xor` pn
                        ones = s' .&. zs
                        negs = (complement s') .&. zs
                    in popCount ones - popCount negs

specialised_convolve :: Word -> F -> (Int, Int)
specialised_convolve s f@(F zs pn) = (inner s f', inner s f) 
    where f' = F (zs `shiftL` 1) (pn `shiftL` 1)

ss :: [Word]
ss = [0..2 ^ (n + 1) - 1]

main_loop :: [G] -> (Int, Int)
main_loop gs = foldl1' (\(fz, bz) (fz', bz') -> (fz + fz', bz + bz')) . parMap rdeepseq helper $ zip ss gs
    where helper (s, g) = go 0 (0, 0) g
                where go k u@(fz, bz) g = if k == iters 
                                              then u 
                                              else let (f, g') = gen_f g
                                                       v = case specialised_convolve s f
                                                               of (0, 0) -> (fz + 1, bz + 1)
                                                                  (0, _) -> (fz + 1, bz)
                                                                  _      -> (fz, bz)
                                                   in go (k + 1) v g'

seed :: IO G                                        
seed = do std_g <- newStdGen
          let [x, y, z, w] = map fromIntegral $ take 4 (randoms std_g :: [Int])
          return $ G x y z w

main :: IO ()
main = (sequence $ map (const seed) ss) >>= print . main_loop
pengguna1502040
sumber
2
Jadi dengan 4 core lebih dari 9000 ?! Tidak mungkin itu benar.
Cees Timmerman
Hukum Amdahl menyatakan percepatan paralelisasi tidak linier dengan jumlah unit pemrosesan paralel. alih-alih mereka hanya memberikan pengembalian yang
redup
@xaedes Speedup tampaknya pada dasarnya linier untuk jumlah core yang rendah
user1502040
3

Rubi

Ruby (2.1.0) 0.277s
Ruby (2.1.1) 0.281s
Python (Alistair Buxton) 0.330s
Python (alemi) 0.097s

n = 6
iters = 1000
first_zero = 0
both_zero = 0

choices = [-1, 0, 0, 1].repeated_permutation(n).select{|v| [0] != v.uniq}

def convolve(v1, v2)
  [0, 1].map do |i|
    r = 0
    6.times do |j|
      r += v1[i+j] * v2[j]
    end
    r
  end
end

[-1, 1].repeated_permutation(n+1) do |s|
  iters.times do
    f = choices.sample
    fs = convolve s, f
    if 0 == fs[0]
      first_zero += 1
      if 0 == fs[1]
        both_zero += 1
      end
    end
  end
end

puts 'firstzero %i' % first_zero
puts 'bothzero %i' % both_zero
Landstander
sumber
3

utas tidak akan lengkap tanpa PHP

6.6x lebih cepat

PHP v5.5.9 - 1.223 0.646 dtk;

vs.

Python v2.7.6 - 8.072 dtk

<?php

$n = 6;
$iters = 1000;
$firstzero = 0;
$bothzero = 0;

$x=time();
$y=34353;
$z=57768;
$w=1564; //PRNG seeds

function myRand() {
    global $x;
    global $y;
    global $z;
    global $w;
    $t = $x ^ ($x << 11);
    $x = $y; $y = $z; $z = $w;
    return $w = $w ^ ($w >> 19) ^ $t ^ ($t >> 8);
}

function array_cartesian() {
    $_ = func_get_args();
    if (count($_) == 0)
        return array();
    $a = array_shift($_);
    if (count($_) == 0)
        $c = array(array());
    else
        $c = call_user_func_array(__FUNCTION__, $_);
    $r = array();
    foreach($a as $v)
        foreach($c as $p)
            $r[] = array_merge(array($v), $p);
    return $r;
}

function rand_array($a, $n)
{
    $r = array();
    for($i = 0; $i < $n; $i++)
        $r[] = $a[myRand()%count($a)];
    return $r;
}

function convolve($a, $b)
{
    // slows down
    /*if(count($a) < count($b))
        return convolve($b,$a);*/
    $result = array();
    $w = count($a) - count($b) + 1;
    for($i = 0; $i < $w; $i++){
        $r = 0;
        for($k = 0; $k < count($b); $k++)
            $r += $b[$k] * $a[$i + $k];
        $result[] = $r;
    }
    return $result;
}

$cross = call_user_func_array('array_cartesian',array_fill(0,$n+1,array(-1,1)));

foreach($cross as $S)
    for($i = 0; $i < $iters; $i++){
        while(true)
        {
            $F = rand_array(array(-1,0,0,1), $n);
            if(in_array(-1, $F) || in_array(1, $F))
                break;
        }
        $FS = convolve($S, $F);
        if(0==$FS[0]) $firstzero += 1;
        if(0==$FS[0] && 0==$FS[1]) $bothzero += 1;
    }

echo "firstzero $firstzero\n";
echo "bothzero $bothzero\n";
  • Menggunakan generator acak khusus (dicuri dari jawaban C), PHP yang menyebalkan dan angka tidak cocok
  • convolve fungsinya disederhanakan sedikit agar lebih cepat
  • Memeriksa hanya array-dengan-nol juga sangat dioptimalkan (lihat $Fdan $FSperiksa).

Output:

$ time python num.py 
firstzero 27050
bothzero 11990

real    0m8.072s
user    0m8.037s
sys 0m0.024s
$ time php num.php
firstzero 27407
bothzero 12216

real    0m1.223s
user    0m1.210s
sys 0m0.012s

Sunting. Skrip versi kedua hanya berfungsi untuk 0.646 sec:

<?php

$n = 6;
$iters = 1000;
$firstzero = 0;
$bothzero = 0;

$x=time();
$y=34353;
$z=57768;
$w=1564; //PRNG seeds

function myRand() {
    global $x;
    global $y;
    global $z;
    global $w;
    $t = $x ^ ($x << 11);
    $x = $y; $y = $z; $z = $w;
    return $w = $w ^ ($w >> 19) ^ $t ^ ($t >> 8);
}

function array_cartesian() {
    $_ = func_get_args();
    if (count($_) == 0)
        return array();
    $a = array_shift($_);
    if (count($_) == 0)
        $c = array(array());
    else
        $c = call_user_func_array(__FUNCTION__, $_);
    $r = array();
    foreach($a as $v)
        foreach($c as $p)
            $r[] = array_merge(array($v), $p);
    return $r;
}

function convolve($a, $b)
{
    // slows down
    /*if(count($a) < count($b))
        return convolve($b,$a);*/
    $result = array();
    $w = count($a) - count($b) + 1;
    for($i = 0; $i < $w; $i++){
        $r = 0;
        for($k = 0; $k < count($b); $k++)
            $r += $b[$k] * $a[$i + $k];
        $result[] = $r;
    }
    return $result;
}

$cross = call_user_func_array('array_cartesian',array_fill(0,$n+1,array(-1,1)));

$choices = call_user_func_array('array_cartesian',array_fill(0,$n,array(-1,0,0,1)));

foreach($cross as $S)
    for($i = 0; $i < $iters; $i++){
        while(true)
        {
            $F = $choices[myRand()%count($choices)];
            if(in_array(-1, $F) || in_array(1, $F))
                break;
        }
        $FS = convolve($S, $F);
        if(0==$FS[0]){
            $firstzero += 1;
            if(0==$FS[1])
                $bothzero += 1;
        }
    }

echo "firstzero $firstzero\n";
echo "bothzero $bothzero\n";
Vitaly Dyatlov
sumber
3

Solusi F #

Runtime adalah 0,030s ketika dikompilasi ke x86 pada CLR Core i7 4 (8) @ 3,4 Ghz

Saya tidak tahu apakah kodenya benar.

  • Optimalisasi fungsional (inline fold) -> 0,026s
  • Membangun melalui Proyek Konsol -> 0,022s
  • Menambahkan algoritma yang lebih baik untuk menghasilkan array permutasi -> 0,018s
  • Mono untuk Windows -> 0,089s
  • Menjalankan skrip Python Alistair -> 0.259s
let inline ffoldi n f state =
    let mutable state = state
    for i = 0 to n - 1 do
        state <- f state i
    state

let product values n =
    let p = Array.length values
    Array.init (pown p n) (fun i ->
        (Array.zeroCreate n, i)
        |> ffoldi n (fun (result, i') j ->
            result.[j] <- values.[i' % p]
            result, i' / p
        )
        |> fst
    )

let convolute signals filter =
    let m = Array.length signals
    let n = Array.length filter
    let len = max m n - min m n + 1

    Array.init len (fun offset ->
        ffoldi n (fun acc i ->
            acc + filter.[i] * signals.[m - 1 - offset - i]
        ) 0
    )

let n = 6
let iters = 1000

let next =
    let arrays =
        product [|-1; 0; 0; 1|] n
        |> Array.filter (Array.forall ((=) 0) >> not)
    let rnd = System.Random()
    fun () -> arrays.[rnd.Next arrays.Length]

let signals = product [|-1; 1|] (n + 1)

let firstzero, bothzero =
    ffoldi signals.Length (fun (firstzero, bothzero) i ->
        let s = signals.[i]
        ffoldi iters (fun (first, both) _ ->
            let f = next()
            match convolute s f with
            | [|0; 0|] -> first + 1, both + 1
            | [|0; _|] -> first + 1, both
            | _ -> first, both
        ) (firstzero, bothzero)
    ) (0, 0)

printfn "firstzero %i" firstzero
printfn "bothzero %i" bothzero
David Grenier
sumber
2

Q, 0,296 segmen

n:6; iter:1000  /parametrization (constants)
c:n#0           /auxiliar constant (sequence 0 0.. 0 (n))
A:B:();         /A and B accumulates results of inner product (firstresult, secondresult)

/S=sequence with all arrays of length n+1 with values -1 and 1
S:+(2**m)#/:{,/x#/:-1 1}'m:|n(2*)\1 

f:{do[iter; F:c; while[F~c; F:n?-1 0 0 1]; A,:+/F*-1_x; B,:+/F*1_x];} /hard work
f'S               /map(S,f)
N:~A; +/'(N;N&~B) / ~A is not A (or A=0) ->bitmap.  +/ is sum (population over a bitmap)
                  / +/'(N;N&~B) = count firstResult=0, count firstResult=0 and secondResult=0

Q adalah bahasa berorientasi koleksi (kx.com)

Kode ditulis ulang untuk mengeluarkan Q idiomatik, tetapi tidak ada optimisasi pintar lainnya

Bahasa scripting mengoptimalkan waktu programmer, bukan waktu eksekusi

  • Q bukan alat terbaik untuk masalah ini

Usaha pengkodean pertama = bukan pemenang, tetapi waktu yang wajar (kira-kira 30x percepatan)

  • cukup kompetitif di antara penerjemah
  • berhenti dan pilih masalah lain

CATATAN.-

  • program menggunakan seed default (eksekutif yang dapat diulang) Untuk memilih seed lain untuk penggunaan generator acak \S seed
  • Hasilnya diberikan sebagai satu kuadrat dari dua int, sehingga ada akhiran-i akhir pada nilai kedua 27421 12133i -> dibaca sebagai (27241, 12133)
  • Waktu tidak termasuk startup juru bahasa. \t sentence mesures waktu dikonsumsi oleh kalimat itu
J. Sendra
sumber
Sangat menarik terima kasih.
1

Julia: 12.149 6.929 s

Terlepas dari klaim mereka untuk mempercepat , waktu kompilasi JIT awal menahan kita!

Perhatikan bahwa kode Julia berikut ini secara efektif merupakan terjemahan langsung dari kode Python asli (tidak ada optimisasi yang dibuat) sebagai demonstrasi bahwa Anda dapat dengan mudah mentransfer pengalaman pemrograman ke bahasa yang lebih cepat;)

require("Iterators")

n = 6
iters = 1000
firstzero = 0
bothzero = 0

for S in Iterators.product(fill([-1,1], n+1)...)
    for i = 1:iters
        F = [[-1 0 0 1][rand(1:4)] for _ = 1:n]
        while all((x) -> round(x,8) == 0, F)
            F = [[-1 0 0 1][rand(1:4)] for _ = 1:n]
        end
        FS = conv(F, [S...])
        if round(FS[1],8) == 0
            firstzero += 1
        end
        if all((x) -> round(x,8) == 0, FS)
            bothzero += 1
        end
    end
end

println("firstzero ", firstzero)
println("bothzero ", bothzero)

Sunting

Menjalankan dengan n = 8membutuhkan waktu 32,935 s. Menimbang bahwa kompleksitas dari algoritma ini O(2^n), maka 4 * (12.149 - C) = (32.935 - C), Cadalah konstanta yang mewakili waktu kompilasi JIT. Memecahkan untuk Ckami menemukan itu C = 5.2203, menunjukkan bahwa waktu eksekusi aktual n = 6adalah 6,929 dtk.

agar gesit
sumber
Bagaimana dengan meningkatkan n ke 8 untuk melihat apakah Julia menjadi miliknya sendiri?
Ini mengabaikan banyak tips kinerja di sini: julia.readthedocs.org/en/latest/manual/performance-tips . Lihat juga entri Julia lainnya yang secara signifikan lebih baik. Kiriman ini dihargai :-)
StefanKarpinski
0

Rust, 6,6 ms, speedup 1950x

Cukup banyak terjemahan langsung kode Alistair Buxton ke Rust. Saya mempertimbangkan untuk menggunakan beberapa core dengan rayon (concurrency tanpa rasa takut!), Tetapi ini tidak meningkatkan kinerja, mungkin karena itu sudah sangat cepat.

extern crate itertools;
extern crate rand;
extern crate time;

use itertools::Itertools;
use rand::{prelude::*, prng::XorShiftRng};
use std::iter;
use time::precise_time_ns;

fn main() {
    let start = precise_time_ns();

    let n = 6;
    let iters = 1000;
    let mut first_zero = 0;
    let mut both_zero = 0;
    let choices_f: Vec<Vec<i8>> = iter::repeat([-1, 0, 0, 1].iter().cloned())
        .take(n)
        .multi_cartesian_product()
        .filter(|i| i.iter().any(|&x| x != 0))
        .collect();
    // xorshift RNG is faster than default algorithm designed for security
    // rather than performance.
    let mut rng = XorShiftRng::from_entropy(); 
    for s in iter::repeat(&[-1, 1]).take(n + 1).multi_cartesian_product() {
        for _ in 0..iters {
            let f = rng.choose(&choices_f).unwrap();
            if f.iter()
                .zip(&s[..s.len() - 1])
                .map(|(a, &b)| a * b)
                .sum::<i8>() == 0
            {
                first_zero += 1;
                if f.iter().zip(&s[1..]).map(|(a, &b)| a * b).sum::<i8>() == 0 {
                    both_zero += 1;
                }
            }
        }
    }
    println!("first_zero = {}\nboth_zero = {}", first_zero, both_zero);

    println!("runtime {} ns", precise_time_ns() - start);
}

Dan Cargo.toml, karena saya menggunakan dependensi eksternal:

[package]
name = "how_slow_is_python"
version = "0.1.0"

[dependencies]
itertools = "0.7.8"
rand = "0.5.3"
time = "0.1.40"

Perbandingan kecepatan:

$ time python2 py.py
firstzero: 27478
bothzero: 12246
12.80user 0.02system 0:12.90elapsed 99%CPU (0avgtext+0avgdata 23328maxresident)k
0inputs+0outputs (0major+3544minor)pagefaults 0swaps
$ time target/release/how_slow_is_python
first_zero = 27359
both_zero = 12162
runtime 6625608 ns
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 2784maxresident)k
0inputs+0outputs (0major+189minor)pagefaults 0swaps

6625608 ns adalah sekitar 6,6 ms. Ini berarti speedup 1950 kali. Ada banyak optimasi yang mungkin dilakukan di sini, tetapi saya lebih memilih keterbacaan daripada kinerja. Salah satu optimasi yang mungkin adalah menggunakan array bukan vektor untuk menyimpan pilihan, karena mereka akan selalu memiliki nelemen. Ini juga memungkinkan untuk menggunakan RNG selain XorShift, karena sementara Xorshift lebih cepat dari HC-128 CSPRNG default, ini lebih lambat dari naivest dari algoritma PRNG.

Konrad Borowski
sumber