Quanto è lento Python? (O quanto è veloce la tua lingua?)


149

Ho questo codice che ho scritto in 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)

Conta il numero di volte in cui la convoluzione di due array casuali, uno più lungo dell'altro, con una distribuzione di probabilità particolare, ha uno 0 nella prima posizione o uno 0 in entrambe le posizioni.

Ho scommesso con un amico che dice che Python è un linguaggio terribile in cui scrivere il codice che deve essere veloce. Ci vogliono 9 secondi sul mio computer. Dice che potrebbe essere reso 100 volte più veloce se scritto in un "linguaggio corretto".

La sfida è vedere se questo codice può effettivamente essere reso 100 volte più veloce in qualsiasi lingua di tua scelta. Metterò alla prova il tuo codice e vincerà la più veloce tra una settimana. Se qualcuno scende al di sotto di 0,09 s, allora vincono automaticamente e io perdo.

Stato

  • Python . 30 volte più veloce di Alistair Buxon! Sebbene non sia la soluzione più veloce, in realtà è la mia preferita.
  • Ottava . 100 volte più veloce di @Thethos.
  • Rust . 500 volte più veloce di @dbaupp.
  • C ++ . 570 volte accelerate da Guy Sirton.
  • C . 727 volte accelerato da @ace.
  • C ++ . Incredibilmente veloce da @Stefan.

Le soluzioni più veloci sono ora troppo veloci per un tempo ragionevole. Ho quindi aumentato n a 10 e impostato iters = 100000 per confrontare i migliori. Sotto questa misura i più veloci sono.

  • C . 7.5s di @ace.
  • C ++ . 1s di @Stefan.

La mia macchina I tempi verranno eseguiti sulla mia macchina. Questa è un'installazione ubuntu standard su un processore a otto core AMD FX-8350. Questo significa anche che devo essere in grado di eseguire il tuo codice.

Follow-up pubblicato Dal momento che questa competizione è stata un po 'troppo facile per ottenere un speedup x100, ho pubblicato un follow-up per coloro che vogliono esercitare la loro esperienza nel speed guru. Vedi quanto è lento Python (parte II)?

Risposte:


61

C ++ bit magic

0,84 ms con RNG semplice, 1,67 ms con c ++ 11 std :: knuth

0.16ms con leggera modifica algoritmica (vedi modifica sotto)

L'implementazione di Python viene eseguita in 7,97 secondi sul mio rig. Quindi, questo è da 9488 a 4772 volte più veloce a seconda di quale RNG scegli.

#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;
}

Compilare in 64 bit per registri extra. Quando si utilizza il semplice generatore casuale i loop in convolve () vengono eseguiti senza alcun accesso alla memoria, tutte le variabili vengono memorizzate nei registri.

Come funziona: piuttosto che archiviare Se Fcome array in memoria, viene memorizzato come bit in un uint32_t.
Per S, i nbit meno significativi sono usati dove un bit impostato indica un +1 e un bit non impostato indica un -1.
Frichiede almeno 2 bit per creare una distribuzione di [-1, 0, 0, 1]. Questo viene fatto generando bit casuali ed esaminando i 16 bit meno significativi (chiamati r) e 16 più significativi (chiamati l). Se l & ~rassumiamo che F sia +1, se ~l & rassumiamo che Fsia -1. Altrimenti Fè 0. Questo genera la distribuzione che stiamo cercando.

Ora abbiamo S, posBitscon un bit impostato in ogni posizione in cui F == 1 e negBitscon un bit impostato in ogni posizione in cui F == -1.

Possiamo dimostrare che F * S(dove * indica la moltiplicazione) viene valutato a +1 nella condizione (S & posBits) | (~S & negBits). Possiamo anche generare una logica simile per tutti i casi in cui viene F * Svalutato -1. E infine, sappiamo che viene sum(F * S)valutato a 0 se e solo se nel risultato vi è una quantità uguale di -1 e + 1. Questo è molto facile da calcolare semplicemente confrontando il numero di +1 bit e -1 bit.

Questa implementazione utilizza 32 bit ints e il massimo naccettato è 16. È possibile ridimensionare l'implementazione su 31 bit modificando il codice di generazione casuale e su 63 bit utilizzando uint64_t anziché uint32_t.

modificare

La folle funzione concava:

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);
}

riduce il tempo di esecuzione a 0,160-0,161 ms. Lo svolgimento manuale del loop (non nella foto sopra) rende 0,150. Il meno banale n = 10, iter = 100000 case funziona sotto i 250ms. Sono sicuro di riuscire a ottenere meno di 50 ms sfruttando core aggiuntivi, ma è troppo facile.

Questo viene fatto rendendo libero il ramo del loop interno e scambiando i loop F e S.
Se bothZeronon è necessario, posso ridurre il tempo di esecuzione a 0,02 ms eseguendo un ciclo scarsamente su tutti i possibili array S.


3
Potresti fornire una versione di gcc e anche quale sarebbe la tua riga di comando per favore? Non sono sicuro di poterlo testare al momento.

Non ne so nulla, ma google mi dice che __builtin_popcount potrebbe essere un sostituto di _mm_popcnt_u32 ().

3
Codice aggiornato, utilizza l'opzione #ifdef per selezionare il comando popcnt corretto. Si compila con -std=c++0x -mpopcnt -O2e impiega 1,01ms per funzionare in modalità 32 bit (non ho una versione GCC a 64 bit a portata di mano).
Stefan,

Potresti farlo stampare l'output? Non sono sicuro se attualmente stia facendo qualcosa :)

7
Sei chiaramente un mago. + 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

Accidenti hai perso la scommessa! Anche qui non c'è una goccia di parallelismo, solo dritto Fortran 90+.

EDIT Ho preso l'algoritmo di Guy Sirton per permutare l'array S(buona scoperta: D). Apparentemente avevo anche i -g -tracebackflag del compilatore attivi che stavano rallentando questo codice fino a circa 0,017 secondi. Attualmente, sto compilando questo come

ifort -fast -o convolve convolve_random_arrays.f90

Per coloro che non hanno ifort, è possibile utilizzare

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

EDIT 2 : La riduzione del tempo di esecuzione è perché stavo facendo qualcosa di sbagliato in precedenza e ho ottenuto una risposta errata. Farlo nel modo giusto è apparentemente più lento. Non riesco ancora a credere che il C ++ sia più veloce del mio, quindi probabilmente trascorrerò un po 'di tempo questa settimana cercando di modificare questa stronzata per accelerarlo.

EDIT 3 : Semplicemente cambiando la sezione RNG usando una basata sull'RNG di BSD (come suggerito da Sampo Smolander) ed eliminando la divisione costante di m1, ho ridotto il tempo di esecuzione allo stesso della risposta C ++ di Guy Sirton . L'uso di array statici (come suggerito da Sharpie) riduce il tempo di esecuzione al di sotto del tempo di esecuzione C ++! Yay Fortran! : D

EDIT 4 Apparentemente questo non viene compilato (con gfortran) ed eseguito correttamente (valori errati) perché gli interi stanno superando i propri limiti. Ho apportato correzioni per assicurarmi che funzioni, ma per questo è necessario avere ifort 11+ o gfortran 4.7+ (o un altro compilatore che lo consenta iso_fortran_enve il int64tipo F2008 ).

Ecco il codice:

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

Suppongo che la domanda ora sia: smetterai di usare Python lento come melassa e di usare Fortran; veloce come gli elettroni possono muovere;).


1
L'affermazione del caso non sarebbe comunque più veloce di una funzione del generatore? A meno che non ti aspetti una sorta di accelerazione di previsione del ramo / cache-line / etc?
OrangeDog,

17
La velocità deve essere confrontata sulla stessa macchina. Quale runtime hai ottenuto per il codice OP?
nbubis,

3
La risposta C ++ implementa il suo generatore di numeri casuali molto leggero. La tua risposta ha utilizzato il valore predefinito fornito con il compilatore, che potrebbe essere più lento?
Sampo Smolander,

3
Inoltre, l'esempio C ++ sembra utilizzare array allocati staticamente. Prova a utilizzare array a lunghezza fissa impostati in fase di compilazione e verifica se si rade in qualsiasi momento.
Sharpie,

1
@KyleKanos @Lembik il problema è che l'assegnazione di numeri interi in fortran non utilizza implicitamente la specifica int64, quindi i numeri sono int32 prima di effettuare qualsiasi conversione. Il codice dovrebbe essere: integer(int64) :: b = 3141592653_int64per tutti int64. Questo fa parte dello standard fortran ed è previsto dal programmatore in un linguaggio di programmazione dichiarato dal tipo. (nota che ovviamente le impostazioni predefinite possono sovrascrivere questo)
zeroth

69

Python 2.7 - 0.882s 0.283s

(OP originale: 6.404s)

Modifica: ottimizzazione di Steven Rumbalski precompilando i valori F. Con questa ottimizzazione cpython batte le 0,365 di 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

Il codice originale di OP utilizza array così piccoli che non vi è alcun vantaggio nell'uso di Numpy, come dimostra questa implementazione di Python puro. Ma vedi anche questa implementazione insensibile che è tre volte più veloce del mio codice.

Ottimizzo anche saltando il resto della convoluzione se il primo risultato non è zero.


11
Con pypy questo funziona in circa 0,5 secondi.
Alistair Buxton,

2
Si ottiene uno speedup molto più convincente se si imposta n = 10. Ottengo 19s contro 4.6s per cpython contro pypy.

3
Un'altra ottimizzazione sarebbe quella di pre-calcolare le possibilità Fperché ce ne sono solo 4032. Definire choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))al di fuori dei loop. Quindi nel ciclo interno definire F = random.choice(choicesF). Ottengo uno speedup 3x con un simile approccio.
Steven Rumbalski,

3
Che ne dici di compilare questo in Cython? Quindi aggiungere alcuni tipi statici delicati?
Thane Brimhall,

2
Metti tutto in una funzione e chiamalo alla fine. Ciò localizza i nomi, il che rende efficace anche l'ottimizzazione proposta da @riffraff. Inoltre, sposta la creazione di range(iters)out of the loop. Complessivamente, ottengo uno speedup di circa il 7% rispetto alla tua bella risposta.
WolframH,

44

Ruggine: 0,011 s

Python originale: 8.3

Una traduzione diretta dell'originale Python.

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)
    }
}
  • Compilato con --opt-level=3
  • Il mio compilatore di ruggine è un nightly recente : ( rustc 0.11-pre-nightly (eea4909 2014-04-24 23:41:15 -0700)per essere precisi)

Ho ottenuto per compilare usando la versione notturna di ruggine. Comunque penso che il codice sia sbagliato. L'output dovrebbe essere qualcosa di simile a firstzero 27215 bothzero 12086. Invece dà 27367 6481

@Lembik, whoops, ottenuto il mio as e bs mescolato nella circonvoluzione; risolto (non modifica notevolmente il runtime).
huon,

4
È una bella dimostrazione della velocità della ruggine.

39

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

Python 2.7.6 / Numpy 1.8.1 - 12s

Accelerazione ~ x800.

Il divario sarebbe molto più piccolo se gli array contorti fossero molto grandi ...

#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
}

Alcune note:

  • La funzione casuale viene chiamata nel loop, quindi ho optato per un generatore congruenziale lineare molto leggero (ma ho guardato generosamente gli MSB).
  • Questo è davvero solo il punto di partenza per una soluzione ottimizzata.
  • Non ci è voluto molto tempo per scrivere ...
  • Esamino tutti i valori di S prendendo S[0]come cifra "meno significativa".

Aggiungi questa funzione principale per un esempio autonomo:

int main(int argc, char** argv)
{
  for(int i=0; i<1000; ++i) // run 1000 times for stop-watch
  {
      convolve_random_arrays();
  }
}

1
Infatti. La minuscola dimensione delle matrici nel codice di OP significa che l'uso del numpy è in realtà un ordine di grandezza più lento del pitone diritto.
Alistair Buxton,

2
Ora x800 è quello di cui sto parlando!

Molto bella! Ho aumentato la velocità del mio codice grazie alla tua advancefunzione, quindi il mio codice è ora più veloce del tuo: P (ma ottima concorrenza!)
Kyle Kanos

1
@lembik sì, come dice Mat. È necessario C ++ 11 supprt e una funzione principale. Fammi sapere se hai bisogno di ulteriore aiuto per far funzionare questo ...
Guy Sirton,

2
Ho appena provato questo e ho potuto radere di un altro 20% usando array semplici invece di std :: vector ..
PlasmaHH,

21

C

Prende 0,015s sulla mia macchina, con il codice originale di OP che prende ~ 7,7s. Ho cercato di ottimizzare generando l'array casuale e contorcendosi nello stesso loop, ma non sembra fare molta differenza.

Il primo array viene generato prendendo un numero intero, scriverlo in binario e cambiare tutto da 1 a -1 e tutti da 0 a 1. Il resto dovrebbe essere molto semplice.

Modifica: invece di avere ncome int, ora abbiamo nuna costante definita da macro, quindi possiamo usare al int arr[n];posto di malloc.

Edit2: Invece della rand()funzione integrata, ora implementa un PRNG xorshift. Inoltre, molte istruzioni condizionali vengono rimosse quando si genera l'array casuale.

Compila le istruzioni:

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

Codice:

#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;
}

1
Ho provato questo. È molto veloce (prova n = 10) e fornisce un output dall'aspetto corretto. Grazie.

Questa implementazione non segue l'originale perché se il vettore casuale è tutto azzerato, solo l'ultimo elemento verrebbe rigenerato. Nell'originale sarebbe l'intero vettore. Devi racchiudere quel loop do{}while(!flag)o qualcosa in tal senso. Non mi aspetto che cambierà molto il tempo di esecuzione (potrebbe renderlo più veloce).
Guy Sirton,

@Guy Sirton noti che prima della continue;dichiarazione ho assegnato -1a k, quindi kin loop da 0 di nuovo.
ace_HongKongIndipendenza

1
@ace ah! hai ragione. Stavo scansionando troppo velocemente e sembrava che fosse -=piuttosto che =-:-) Un ciclo while sarebbe più leggibile.
Guy Sirton,

17

J

Non mi aspetto di battere qualsiasi linguaggio compilato, e qualcosa mi dice che ci vorrebbe una macchina miracolosa per ottenere meno di 0,09 secondi con questo, ma mi piacerebbe comunque inviare questo J, perché è piuttosto fluido.

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

Ciò richiede circa 0,5 secondi su un laptop del decennio precedente, solo circa 20 volte più veloce del Python nella risposta. Passa la maggior parte del tempo convperché lo scriviamo pigramente (calcoliamo l'intera convoluzione) e in piena generalità.

Dal momento che sappiamo cose su Se F, possiamo accelerare le cose facendo ottimizzazioni specifiche per questo programma. Il migliore che sono stato in grado di conv =: ((num, num+1) { +//.)@:(*/)"1inventare è —selezionare specificamente i due numeri che corrispondono dalle somme diagonali agli elementi più lunghi della convoluzione — che dimezzano il tempo.


6
J vale sempre la pena, amico :)
Vitaly Dyatlov,

17

Perl: 9,3 volte più veloce ... miglioramento dell'830%

Sul mio antico netbook, l'esecuzione del codice OP richiede 53 secondi; La versione di Alistair Buxton dura circa 6,5 ​​secondi e la seguente versione Perl dura circa 5,7 secondi.

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;

12

Python 2.7 - numpy 1.8.1 con attacchi mkl - 0,086s

(Originale di OP: 6.404s) (Pitone puro di 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

Come sottolineato da Buxton, il codice originale di OP utilizza array così piccoli che non vi è alcun vantaggio nell'uso di Numpy. Questa implementazione sfrutta intorpidimento eseguendo tutti i casi F e S contemporaneamente in un modo orientato all'array. Questo combinato con i collegamenti mkl per Python porta ad un'implementazione molto veloce.

Si noti inoltre che il semplice caricamento delle librerie e l'avvio dell'interprete richiedono 0,076 secondi, quindi il calcolo effettivo richiede ~ 0,01 secondi, in modo simile alla soluzione C ++.


Cosa sono i collegamenti mkl e come ottenerli su Ubuntu?

L'esecuzione python -c "import numpy; numpy.show_config()"ti mostrerà se la tua versione di numpy è compilata contro blas / atlas / mkl, ecc. ATLAS è un pacchetto matematico accelerato gratuito contro cui numpy può essere collegato , Intel MKL di solito devi pagare (a meno che tu non sia un accademico) e può essere collegato a numpy / scipy .
alemi,

Per un modo semplice, usa la distribuzione python di anaconda e usa il pacchetto di accelerazione . O usa la distribuzione attenta .
alemi,

Se sei su Windows, scarica numpy da qui . Programmi di installazione numpy precompilati collegati a MKL.
Nome falso

9

MATLAB 0.024s

Computer 1

  • Codice originale: ~ 3.3 s
  • Codice Alistar Buxton: ~ 0,51 s
  • Il nuovo codice di Alistar Buxton: ~ 0,25 s
  • Codice Matlab: ~ 0.024 s (Matlab già in esecuzione)

Computer 2

  • Codice originale: ~ 6.66 s
  • Codice Alistar Buxton: ~ 0.64 s
  • Il nuovo codice di Alistar Buxton:?
  • Matlab: ~ 0.07 s (Matlab già in esecuzione)
  • Ottava: ~ 0,07 s

Ho deciso di provare Matlab così lentamente. Se sai come, puoi sbarazzarti della maggior parte dei loop (in Matlab), il che lo rende abbastanza veloce. Tuttavia, i requisiti di memoria sono più elevati rispetto alle soluzioni in loop, ma questo non sarà un problema se non si dispone di array molto grandi ...

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

Ecco cosa faccio:

  • usa la funzione Kyle Kanos per permutare attraverso S
  • calcola tutti i numeri casuali contemporaneamente
  • mappa da 1 a 4 a [-1 0 0 1]
  • usa la moltiplicazione di matrici (somma elementally (F * S (1: 5)) è uguale alla moltiplicazione di matrici di F * S (1: 5) '
  • per Bothzero: calcola solo i membri che soddisfano la prima condizione

Presumo che tu non abbia matlab, il che è un peccato perché mi sarebbe davvero piaciuto vedere come si confronta ...

(La funzione può essere più lenta la prima volta che la si esegue.)


Beh, ho un'ottava se riesci a farlo funzionare per quello ...?

Posso provarlo, non ho mai lavorato con l'ottava.
mathause,

Ok, posso eseguirlo così com'è in ottava se inserisco il codice in un file chiamato call_convolve_random_arrays.m e quindi lo chiamo da ottava.
mathause,

Ha bisogno di altro codice per farlo effettivamente fare qualcosa? Quando faccio "octave call_convolve_random_arrays.m" non emette nulla. Vedi bpaste.net/show/JPtLOCeI3aP3wc3F3aGf

scusa, prova ad aprire l'ottava ed eseguilo quindi. Dovrebbe visualizzare firstzero, bothzero e tempo di esecuzione.
mathause,

7

Giulia: 0,30 s

Op's Python: 21.36 s (Core2 duo)

71x speedup

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

Ho apportato alcune modifiche alla risposta Julia di Arman: Prima di tutto, l'ho racchiusa in una funzione, poiché le variabili globali rendono difficile l'inferenza del tipo di Julia e JIT: una variabile globale può cambiare il suo tipo in qualsiasi momento e deve essere controllata ogni operazione . Quindi, mi sono sbarazzato delle funzioni anonime e delle comprensioni dell'array. Non sono davvero necessari e sono ancora piuttosto lenti. Julia è più veloce con le astrazioni di livello inferiore in questo momento.

Ci sono molti altri modi per renderlo più veloce, ma questo fa un lavoro decente.


Stai misurando il tempo nel REPL o eseguendo l'intero file dalla riga di comando?
Aditya,

entrambi dalla REPL.
user20768,

6

Ok, sto pubblicando questo solo perché sento che Java deve essere rappresentato qui. Sono terribile con altre lingue e confesso di non capire esattamente il problema, quindi avrò bisogno di aiuto per correggere questo codice. Ho rubato la maggior parte dell'esempio C dell'asso di codice e poi ho preso in prestito alcuni frammenti da altri. Spero che non sia un passo falso ...

Una cosa che vorrei sottolineare è che le lingue che ottimizzano in fase di esecuzione devono essere eseguite più volte per ottenere la massima velocità. Penso che sia giustificato prendere la velocità completamente ottimizzata (o almeno la velocità media) perché la maggior parte delle cose che ti interessano correre veloce verrà eseguita un sacco di volte.

Il codice deve ancora essere corretto, ma l'ho eseguito comunque per vedere che ore avrei ottenuto.

Ecco i risultati su una CPU Intel (R) Xeon (R) E3-1270 V2 a 3,50 GHz su Ubuntu che la esegue 1000 volte:

server: / tmp # time java8 -cp. Tester

firstzero 40000

Bothzero 20000

prima esecuzione: 41 ms ultima esecuzione: 4 ms

reale 0m5.014s utente 0m4.664s sys 0m0.268s

Ecco il mio codice schifoso:

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));
    }
}

E ho provato a eseguire il codice Python dopo aver aggiornato Python e aver installato Python-Numpy, ma ottengo questo:

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'

Commenti: non utilizzare maicurrentTimeMillis per il benchmarking (utilizzare la versione nano nel sistema) e le esecuzioni 1k potrebbero non essere sufficienti per coinvolgere la JIT (1.5k per client e 10k per server sarebbero i valori predefiniti, anche se si chiama myRand abbastanza spesso che sarà JITed che dovrebbe causare la compilazione di alcune funzioni del callstack che potrebbero funzionare qui). Ultimo ma non meno importante, il PNRG debole sta tradendo, ma anche la soluzione C ++ e altre, quindi immagino che non sia troppo ingiusto.
Voo

Su Windows devi evitare currentTimeMillis, ma per Linux per tutte le misurazioni di granularità, tranne quelle molto fini, non hai bisogno di nano time e la chiamata per ottenere nano time è molto più costosa di millis. Quindi non sono assolutamente d'accordo sul fatto che non dovresti MAI usarlo.
Chris Seline,

Quindi stai scrivendo il codice Java per un particolare sistema operativo e l'implementazione JVM? In realtà non sono sicuro di quale sistema operativo stai usando, perché ho appena controllato il mio albero degli sviluppatori HotSpot e gli usi Linux gettimeofday(&time, NULL)per milliSeconds che non è monotonico e non offre garanzie di precisione (quindi su alcune piattaforme / kernel esattamente lo stesso problemi come l'implementazione corrente di Windows di TimeMill - quindi anche quella va bene o non lo è). nanoTime, d'altra parte, utilizza clock_gettime(CLOCK_MONOTONIC, &tp)che chiaramente è anche la cosa giusta da usare quando si esegue il benchmarking su Linux.
Voo

Non ha mai causato problemi per me da quando ho codificato Java su qualsiasi distribuzione Linux o kernel.
Chris Seline,

6

Golang versione 45X di Python sulla mia macchina sotto i codici 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)
}

e i seguenti codici Python copiati dall'alto:

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

e il tempo sotto:

$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

1
hai pensato di usare "github.com/yanatan16/itertools"? diresti anche che funzionerebbe bene in più goroutine?
ymg

5

C # 0.135s

C # basato sul pitone semplice di Alistair Buxton : 0.278s
Parallelizzato C #: 0.135s
Python dalla domanda: 5.907s
Pitone semplice di Alistair: 0.853s

In realtà non sono sicuro che questa implementazione sia corretta - il suo output è diverso, se si guardano i risultati in basso.

Ci sono sicuramente algoritmi più ottimali. Ho appena deciso di utilizzare un algoritmo molto simile a quello di Python.

Filetto singolo 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 }));
        }
    }
}

C # parallelo:

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);
        }
    }
}

Uscita di prova:

Windows (.NET)

Il C # è molto più veloce su Windows. Probabilmente perché .NET è più veloce di mono.

Il timing dell'utente e del sistema non sembra funzionare (utilizzato git bashper il timing).

$ 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

1
Non penso che il codice sia corretto come dici tu. Le uscite non sono giuste.

@Lembik Yea. Lo apprezzerei se qualcuno potesse dirmi dove è sbagliato, però - non riesco a capirlo (avere una minima comprensione di ciò che dovrebbe fare non aiuta).
Bob,

Sarebbe interessante vedere come va con .NET Native blogs.msdn.com/b/dotnet/archive/2014/04/02/…
Rick Minerich

@Lembik Ho appena esaminato tutto, per quanto ne so, dovrebbe essere identico all'altra soluzione Python ... ora sono davvero confuso.
Bob

4

Haskell: ~ 2000x speedup per core

Compilare con 'ghc -O3 -funbox-strict-fields -threaded -fllvm' ed eseguire con '+ RTS -Nk' dove k è il numero di core sulla macchina.

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

2
Quindi con 4 core supera i 9000 ?! Non c'è modo che possa essere giusto.
Cees Timmerman,

La legge di Amdahl afferma che l'accelerazione della parallelizzazione non è lineare rispetto al numero di unità di elaborazione parallele. invece forniscono solo ritorni
deboli

@xaedes Lo speedup sembra essenzialmente lineare per un basso numero di core
user1502040

3

Rubino

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

3

il thread non sarebbe completo senza PHP

6.6x più veloce

PHP v5.5.9 - 1.223 0.646 sec;

vs

Python v2.7.6 - 8.072 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 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";
  • Utilizzato un generatore casuale personalizzato (rubato dalla risposta C), PHP fa schifo e i numeri non corrispondono
  • convolve funzione semplificata un po 'per essere più veloce
  • Anche il controllo dell'array con soli zeri è molto ottimizzato (vedere $Fe $FScontrolli).

Uscite:

$ 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

Modificare. La seconda versione dello script funziona solo per 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";

3

Soluzione F #

Il tempo di esecuzione è di 0,030 secondi se compilato in x86 su CLR Core i7 4 (8) a 3,4 Ghz

Non ho idea se il codice è corretto.

  • Ottimizzazione funzionale (piega in linea) -> 0,026 s
  • Creazione tramite Progetto console -> 0,022 s
  • Aggiunto un algoritmo migliore per la generazione degli array di permutazione -> 0,018 s
  • Mono per Windows -> 0,089s
  • Esecuzione dello script Python di 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

2

Q, 0,296 seg

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 è un linguaggio orientato alla raccolta (kx.com)

Codice riscritto per esplorare la Q idiomatica, ma nessun'altra ottimizzazione intelligente

I linguaggi di scripting ottimizzano il tempo del programmatore, non il tempo di esecuzione

  • Q non è lo strumento migliore per questo problema

Primo tentativo di codifica = non un vincitore, ma un tempo ragionevole (circa 30 volte la velocità)

  • abbastanza competitivo tra gli interpreti
  • fermati e scegli un altro problema

APPUNTI.-

  • il programma utilizza seed predefinito (exec ripetibili) Per scegliere un altro seed per l'uso casuale del generatore \S seed
  • Il risultato è espresso in due pollici, quindi c'è un suffisso i finale al secondo valore 27421 12133i -> letto come (27241, 12133)
  • Tempo senza contare l'avvio dell'interprete. \t sentence misura il tempo impiegato da quella frase

Grazie molto interessante.

1

Julia: 12.149 6.929 s

Nonostante le loro affermazioni sulla velocità , il tempo iniziale di compilazione JIT ci trattiene!

Nota che il seguente codice Julia è effettivamente una traduzione diretta del codice Python originale (nessuna ottimizzazione effettuata) come dimostrazione che puoi facilmente trasferire la tua esperienza di programmazione in un linguaggio più veloce;)

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)

modificare

L'esecuzione n = 8richiede 32.935 s. Considerando che la complessità di questo algoritmo è O(2^n), quindi 4 * (12.149 - C) = (32.935 - C), dove Cè una costante che rappresenta il tempo di compilazione JIT. Risolvendo per Cquesto troviamo che C = 5.2203, suggerendo che il tempo di esecuzione effettivo per n = 6è 6.929 s.


Che ne dici di aumentare da n a 8 per vedere se Julia arriva in proprio allora?

Questo ignora molti dei suggerimenti sulle prestazioni qui: julia.readthedocs.org/en/latest/manual/performance-tips . Vedi anche l'altra voce di Julia che fa decisamente meglio. La presentazione è apprezzata però :-)
StefanKarpinski,

0

Ruggine, 6,6 ms, velocità 1950x

Praticamente una traduzione diretta del codice di Alistair Buxton su Rust. Ho preso in considerazione l'uso di più core con rayon (concorrenza senza paura!), Ma questo non ha migliorato le prestazioni, probabilmente perché è già molto veloce.

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);
}

E Cargo.toml, mentre utilizzo dipendenze esterne:

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

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

Confronto di velocità:

$ 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 è di circa 6,6 ms. Questo significa 1950 volte più veloce. Ci sono molte ottimizzazioni possibili qui, ma stavo cercando leggibilità piuttosto che prestazioni. Una possibile ottimizzazione sarebbe l'uso di array anziché di vettori per la memorizzazione delle scelte, poiché avranno sempre nelementi. È anche possibile utilizzare RNG diverso da XorShift, poiché mentre Xorshift è più veloce del CSPRNG HC-128 predefinito, è più lento degli ingenui algoritmi PRNG.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.