¿Qué tan lento es realmente Python? (¿O qué tan rápido es tu idioma?)


149

Tengo este código que he escrito en 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)

Está contando el número de veces que la convolución de dos matrices aleatorias, una que es una más larga que la otra, con una distribución de probabilidad particular, tiene un 0 en la primera posición o un 0 en ambas posiciones.

Aposté con un amigo que dice que Python es un lenguaje terrible para escribir código que debe ser rápido. Se necesitan 9 segundos en mi computadora. Él dice que podría hacerse 100 veces más rápido si está escrito en un "lenguaje apropiado".

El desafío es ver si este código se puede hacer 100 veces más rápido en cualquier idioma de su elección. Probaré su código y la semana más rápida ganará. Si alguien se pone por debajo de 0.09s, automáticamente ganan y yo pierdo.

Estado

  • Python . ¡Alistair Buxon acelera 30 veces! Aunque no es la solución más rápida, de hecho es mi favorita.
  • Octave . Acelera 100 veces por @Thethos.
  • Óxido . 500 veces acelerado por @dbaupp.
  • C ++ . 570 veces acelerado por Guy Sirton.
  • C . 727 veces acelerado por @ace.
  • C ++ . Increíblemente rápido por @Stefan.

Las soluciones más rápidas ahora son demasiado rápidas para el tiempo sensato. Por lo tanto, aumenté n a 10 y configuré iters = 100000 para comparar los mejores. Bajo esta medida los más rápidos son.

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

Mi máquina Los tiempos se ejecutarán en mi máquina. Esta es una instalación estándar de ubuntu en un procesador AMD FX-8350 de ocho núcleos. Esto también significa que necesito poder ejecutar su código.

Seguimiento publicado Como esta competencia fue demasiado fácil para obtener una aceleración x100, he publicado un seguimiento para aquellos que desean ejercer su experiencia en gurú de la velocidad. ¿ Ves lo lento que es realmente Python (Parte II)?

Respuestas:


61

C ++ bit magic

0.84ms con RNG simple, 1.67ms con c ++ 11 std :: knuth

0,16 ms con una ligera modificación algorítmica (ver edición a continuación)

La implementación de Python se ejecuta en 7.97 segundos en mi plataforma. Entonces, esto es de 9488 a 4772 veces más rápido dependiendo de qué RNG elijas.

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

Compile en 64 bits para registros adicionales. Cuando se usa el generador aleatorio simple, los bucles en convolve () se ejecutan sin acceso a la memoria, todas las variables se almacenan en los registros.

Cómo funciona: en lugar de almacenar Sy Fcomo matrices en memoria, se almacena como bits en un uint32_t.
Para S, los nbits menos significativos se utilizan donde un bit establecido denota un +1 y un bit no establecido denota un -1.
Frequiere al menos 2 bits para crear una distribución de [-1, 0, 0, 1]. Esto se realiza generando bits aleatorios y examinando los 16 bits menos significativos (llamados r) y los 16 bits más significativos (llamados l). Si l & ~rsuponemos que F es +1, si ~l & rsuponemos que Fes -1. De Flo contrario, es 0. Esto genera la distribución que estamos buscando.

Ahora tenemos S, posBitscon un bit establecido en cada ubicación donde F == 1 y negBitscon un bit establecido en cada ubicación donde F == -1.

Podemos demostrar que F * S(donde * denota multiplicación) se evalúa a +1 bajo la condición (S & posBits) | (~S & negBits). También podemos generar una lógica similar para todos los casos donde se F * Sevalúa a -1. Y finalmente, sabemos que se sum(F * S)evalúa a 0 si y solo si hay una cantidad igual de -1 y + 1 en el resultado. Esto es muy fácil de calcular simplemente comparando el número de +1 bits y -1 bits.

Esta implementación usa entradas de 32 bits, y el máximo naceptado es 16. Es posible escalar la implementación a 31 bits modificando el código de generación aleatorio, y a 63 bits usando uint64_t en lugar de uint32_t.

editar

La siguiente función de convolución:

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

corta el tiempo de ejecución a 0.160-0.161ms. El desenrollado manual del bucle (no se muestra arriba) hace que 0.150. El menos trivial n = 10, iter = 100000 casos se ejecuta por debajo de 250 ms. Estoy seguro de que puedo obtener menos de 50 ms aprovechando núcleos adicionales, pero eso es demasiado fácil.

Esto se hace liberando la rama del bucle interno e intercambiando el bucle F y S.
Si bothZerono es necesario, puedo reducir el tiempo de ejecución a 0.02 ms haciendo un bucle escaso sobre todas las matrices S posibles.


3
¿Podría proporcionar una versión compatible con gcc y también cuál sería su línea de comando, por favor? No estoy seguro de poder probarlo actualmente.

No sé nada de esto, pero Google me dice que __builtin_popcount podría ser un reemplazo de _mm_popcnt_u32 ().

3
Código actualizado, utiliza el interruptor #ifdef para seleccionar el comando popcnt correcto. Se compila -std=c++0x -mpopcnt -O2y tarda 1.01 ms en ejecutarse en modo de 32 bits (no tengo una versión GCC de 64 bits disponible).
Stefan

¿Podría hacer que imprima la salida? No estoy seguro si realmente está haciendo algo actualmente :)

77
Eres claramente 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

¡Maldita sea, perdiste tu apuesta! No es una gota de paralelización aquí también, solo Fortran 90+.

EDITAR He tomado el algoritmo de Guy Sirton para permutar la matriz S(buen hallazgo: D). Aparentemente, también tenía -g -tracebackactivadas las marcas del compilador, que ralentizaban este código a aproximadamente 0.017s. Actualmente, estoy compilando esto como

ifort -fast -o convolve convolve_random_arrays.f90

Para aquellos que no tienen ifort, pueden usar

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

EDIT 2 : La disminución en el tiempo de ejecución se debe a que estaba haciendo algo mal anteriormente y obtuve una respuesta incorrecta. Hacerlo de la manera correcta es aparentemente más lento. Todavía no puedo creer que C ++ sea más rápido que el mío, por lo que probablemente voy a pasar algo de tiempo esta semana tratando de modificar esto para acelerarlo.

EDITAR 3 : simplemente cambiando la sección de RNG usando una basada en RNG de BSD (como lo sugiere Sampo Smolander) y eliminando la división constante entre m1, reduzco el tiempo de ejecución al mismo que la respuesta de C ++ de Guy Sirton . ¡El uso de matrices estáticas (como sugiere Sharpie) reduce el tiempo de ejecución a menos del tiempo de ejecución de C ++! Yay Fortran! :RE

EDITAR 4 Aparentemente esto no se compila (con gfortran) y se ejecuta correctamente (valores incorrectos) porque los enteros están sobrepasando sus límites. He realizado correcciones para garantizar que funciona, pero esto requiere que uno tenga ifort 11+ o gfortran 4.7+ (u otro compilador que lo permita iso_fortran_envy el int64tipo F2008 ).

Aquí está el código:

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

Supongo que la pregunta ahora es si dejarás de usar Python lento como la melaza y usarás Fortran rápido como electrones;


1
¿No sería la declaración del caso más rápida que una función generadora de todos modos? ¿A menos que esperes algún tipo de aceleración de predicción de rama / línea de caché / etc.?
OrangeDog

17
La velocidad debe compararse en la misma máquina. ¿Qué tiempo de ejecución obtuviste para el código del OP?
nbubis

3
La respuesta de C ++ implementa su propio generador de números aleatorios muy liviano. Su respuesta usó el valor predeterminado que viene con el compilador, que podría ser más lento.
Sampo Smolander

3
Además, el ejemplo de C ++ parece estar usando matrices asignadas estáticamente. Intente usar matrices de longitud fija que se configuran en tiempo de compilación y vea si se reduce el tiempo libre.
Sharpie

1
@KyleKanos @Lembik el problema es que la asignación de enteros en fortran no está usando implícitamente la especificación int64, por lo tanto, los números son int32 antes de realizar cualquier conversión. El código debe ser: integer(int64) :: b = 3141592653_int64para todos los int64. Esto es parte del estándar fortran y el programador lo espera en un lenguaje de programación de tipo declarado. (nótese que la configuración por defecto, por supuesto, puede anular este)
cero

69

Python 2.7 - 0.882s 0.283s

(Original de OP: 6.404s)

Editar: optimización de Steven Rumbalski al precalcular los valores de F. Con esta optimización, cpython supera los 0.365s de 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

El código original de OP utiliza matrices tan pequeñas que no hay ningún beneficio en usar Numpy, como demuestra esta implementación pura de Python. Pero vea también esta implementación numpy que es tres veces más rápida que mi código.

También optimizo saltando el resto de la convolución si el primer resultado no es cero.


11
Con pypy esto se ejecuta en aproximadamente 0.5 segundos.
Alistair Buxton

2
Obtendrá una aceleración mucho más convincente si establece n = 10. Obtengo 19s versus 4.6s para cpython versus pypy.

3
Otra optimización sería calcular previamente las posibilidades Fporque solo hay 4032 de ellas. Definir choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))fuera de los bucles. Luego, en el bucle interno definir F = random.choice(choicesF). Tengo una aceleración 3x con este enfoque.
Steven Rumbalski

3
¿Qué tal compilar esto en Cython? ¿Luego agregando algunos tipos estáticos con tacto?
Thane Brimhall

2
Pon todo en una función y llámalo al final. Eso localiza los nombres, lo que también hace que la optimización propuesta por @riffraff funcione. Además, mueva la creación range(iters)fuera del bucle. En total, obtengo una aceleración de alrededor del 7% sobre su muy buena respuesta.
WolframH

44

Moho: 0.011s

Python original: 8.3

Una traducción directa del Python original.

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)
    }
}
  • Compilado con --opt-level=3
  • Mi compilador de óxido es un reciente nocturno : ( rustc 0.11-pre-nightly (eea4909 2014-04-24 23:41:15 -0700)para ser precisos)

Lo conseguí compilar usando la versión nocturna del óxido. Sin embargo, creo que el código está mal. La salida debería ser algo similar a firstzero 27215 bothzero 12086. En cambio, da 27367 6481

@Lembik, gritos, tengo mi as y bs mezclado en la convolución; fijo (no cambia notablemente el tiempo de ejecución).
Huon

44
Es una muy buena demostración de la velocidad del óxido.

39

C ++ (VS 2012) - 0.026s 0.015s

Python 2.7.6 / Numpy 1.8.1 - 12s

Aceleración ~ x800.

La brecha sería mucho menor si las matrices enrevesadas fueran muy grandes ...

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

Algunas notas

  • La función aleatoria se llama en el bucle, así que elegí un generador congruencial lineal muy liviano (pero analicé generosamente los MSB).
  • Este es realmente el punto de partida para una solución optimizada.
  • No tardé tanto en escribir ...
  • I itero a través de todos los valores de S tomando S[0]como el dígito "menos significativo".

Agregue esta función principal para un ejemplo autónomo:

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

1
En efecto. El pequeño tamaño de las matrices en el código de OP significa que usar numpy es en realidad un orden de magnitud más lento que la pitón recta.
Alistair Buxton

2
¡Ahora x800 es de lo que estoy hablando!

¡Muy agradable! He aumentado la velocidad de mi código debido a su advancefunción, por lo que mi código ahora es más rápido que el suyo: P (¡pero muy buena competencia!)
Kyle Kanos

1
@lembik sí, como dice Mat. Necesita C ++ 11 supprt y una función principal. Avíseme si necesita más ayuda para que esto funcione ...
Guy Sirton

2
Acabo de probar esto y podría reducir otro 20% utilizando matrices simples en lugar de std :: vector ..
PlasmaHH

21

C

Toma 0.015s en mi máquina, con el código original de OP tomando ~ 7.7s. Intenté optimizar generando la matriz aleatoria y convolucionando en el mismo bucle, pero no parece hacer mucha diferencia.

La primera matriz se genera tomando un número entero, escribiéndolo en binario y cambiando todo 1 a -1 y todo 0 a 1. El resto debería ser muy sencillo.

Editar: en lugar de tener ncomo un int, ahora tenemos ncomo una constante macrodefinida, por lo que podemos usar en int arr[n];lugar de malloc.

Edit2: en lugar de la rand()función incorporada, esto ahora implementa un xorshift PRNG. Además, se eliminan muchas declaraciones condicionales al generar la matriz aleatoria.

Compilar instrucciones:

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

Código:

#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
Probé esto. Es muy rápido (intente n = 10) y proporciona un resultado de aspecto correcto. Gracias.

Esta implementación no sigue el original porque si el vector aleatorio es todo ceros, solo se volverá a generar el último elemento. En el original, todo el vector sería. Necesita encerrar ese bucle do{}while(!flag)o algo por el estilo. No espero que cambie mucho el tiempo de ejecución (puede hacerlo más rápido).
Guy Sirton

@Guy Sirton en cuenta que antes de la continue;declaración he asignado -1a k, por lo que kse repetirá desde 0 otra vez.
ace_HongKongIndependence

1
@ace, ¡ah! tienes razón. Estaba escaneando demasiado rápido y parecía que era -=más que =-:-) Un ciclo while sería más legible.
Guy Sirton

17

J

No espero superar ningún lenguaje compilado, y algo me dice que se necesitaría una máquina milagrosa para obtener menos de 0.09 s con esto, pero me gustaría enviar este J de todos modos, porque es bastante hábil.

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

Esto toma aproximadamente 0.5 s en una computadora portátil de la década anterior, solo alrededor de 20 veces más rápido que el Python en la respuesta. La mayor parte del tiempo se gasta convporque lo escribimos perezosamente (calculamos toda la convolución) y en general.

Dado que sabemos cosas sobre Sy F, podemos acelerar las cosas haciendo optimizaciones específicas para este programa. Lo mejor que he podido encontrar es conv =: ((num, num+1) { +//.)@:(*/)"1—seleccionar específicamente los dos números que corresponden desde las sumas diagonales a los elementos más largos de la convolución— que reduce a la mitad aproximadamente el tiempo.


66
J siempre vale la pena enviarlo, hombre :)
Vitaly Dyatlov

17

Perl: 9.3 veces más rápido ... 830% de mejora

En mi netbook antiguo, el código del OP tarda 53 segundos en ejecutarse; La versión de Alistair Buxton tarda unos 6,5 segundos, y la siguiente versión de Perl tarda unos 5,7 segundos.

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 enlaces mkl - 0.086s

(Original de OP: 6.404s) (Python puro de 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

Como señala Buxton, el código original de OP utiliza matrices tan pequeñas que no hay ningún beneficio en usar Numpy. Esta implementación aprovecha numpy haciendo todos los casos F y S a la vez en una forma orientada a la matriz. Esto combinado con enlaces mkl para python conduce a una implementación muy rápida.

Tenga en cuenta también que solo cargar las bibliotecas e iniciar el intérprete toma 0.076s, por lo que el cálculo real tarda ~ 0.01 segundos, similar a la solución C ++.


¿Qué son los enlaces mkl y cómo los consigo en ubuntu?

La ejecución python -c "import numpy; numpy.show_config()"le mostrará si su versión de numpy está compilada contra blas / atlas / mkl, etc. ATLAS es un paquete matemático acelerado gratuito con el que numpy se puede vincular , Intel MKL generalmente tiene que pagar (a menos que sea un académico) y se puede vincular a numpy / scipy .
alemi

Para una manera fácil, use la distribución anaconda python y use el paquete de aceleración . O use la distribución entusiasta .
alemi

Si está en Windows, simplemente descargue numpy desde aquí . Instaladores numpy precompilados vinculados contra MKL.
Nombre falso

9

MATLAB 0.024s

Computadora 1

  • Código original: ~ 3.3 s
  • Código de Alistar Buxton: ~ 0.51 s
  • El nuevo código de Alistar Buxton: ~ 0.25 s
  • Código Matlab: ~ 0.024 s (Matlab ya se está ejecutando)

Computadora 2

  • Código original: ~ 6.66 s
  • Código de Alistar Buxton: ~ 0.64 s
  • El nuevo código de Alistar Buxton:?
  • Matlab: ~ 0.07 s (Matlab ya se está ejecutando)
  • Octava: ~ 0.07 s

Decidí probar el tan lento Matlab. Si sabe cómo, puede deshacerse de la mayoría de los bucles (en Matlab), lo que lo hace bastante rápido. Sin embargo, los requisitos de memoria son más altos que para las soluciones en bucle, pero esto no será un problema si no tiene matrices muy grandes ...

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

Esto es lo que hago:

  • use la función Kyle Kanos para permutar a través de S
  • calcular todos los n * iters números aleatorios a la vez
  • mapa 1 a 4 a [-1 0 0 1]
  • usar la multiplicación matricial (la suma de elementos (F * S (1: 5)) es igual a la multiplicación matricial de F * S (1: 5) '
  • Para ambos cero: solo calcule miembros que cumplan la primera condición

Supongo que no tienes matlab, lo cual es una pena, ya que realmente me hubiera gustado ver cómo se compara ...

(La función puede ser más lenta la primera vez que la ejecuta).


Bueno, tengo octava si puedes hacer que funcione para eso ...

Puedo intentarlo, aunque nunca trabajé con octava.
mathause

Ok, puedo ejecutarlo como está en octava si pongo el código en un archivo llamado call_convolve_random_arrays.m y luego lo llamo desde octava.
mathause

¿Necesita más código para que haga algo? Cuando hago "octave call_convolve_random_arrays.m" no da salida a nada. Ver bpaste.net/show/JPtLOCeI3aP3wc3F3aGf

lo siento, intenta abrir la octava y ejecutarla entonces. Debería mostrar primero cero, ambos cero y tiempo de ejecución.
mathause

7

Julia: 0,30 s

Python de operación: 21.36 s (dúo Core2)

71x aceleración

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

Hice algunas modificaciones de la respuesta de Arman a Julia: en primer lugar, la envolví en una función, ya que las variables globales dificultan la inferencia de tipos de Julia y JIT: una variable global puede cambiar su tipo en cualquier momento, y debe verificarse en cada operación . Luego, me deshice de las funciones anónimas y las comprensiones de matriz. No son realmente necesarios y siguen siendo bastante lentos. Julia es más rápida con abstracciones de nivel inferior en este momento.

Hay muchas más formas de hacerlo más rápido, pero esto hace un trabajo decente.


¿Estás midiendo el tiempo en el REPL o ejecutando todo el archivo desde la línea de comandos?
Aditya

ambos de la REPL.
user20768

6

Ok, estoy publicando esto solo porque siento que Java necesita ser representado aquí. Soy terrible con otros idiomas y confieso que no entiendo el problema exactamente, por lo que necesitaré ayuda para solucionar este código. Robé la mayor parte del código C del ejemplo as, y luego tomé prestados algunos fragmentos de otros. Espero que no sea un paso en falso ...

Una cosa que me gustaría señalar es que los lenguajes que se optimizan en tiempo de ejecución deben ejecutarse varias / muchas veces para alcanzar la velocidad máxima. Creo que está justificado tomar la velocidad totalmente optimizada (o al menos la velocidad promedio) porque la mayoría de las cosas con las que te preocupa correr rápido se ejecutarán muchas veces.

El código todavía necesita ser reparado, pero lo ejecuté de todos modos para ver qué horas obtendría.

Aquí están los resultados en una CPU Intel (R) Xeon (R) E3-1270 V2 @ 3.50GHz en Ubuntu ejecutándola 1000 veces:

servidor: / tmp # time java8 -cp. Ensayador

firstzero 40000

Bothzero 20000

primer tiempo de ejecución: 41 ms último tiempo de ejecución: 4 ms

usuario real 0m5.014s 0m4.664s sys 0m0.268s

Aquí está mi código de mierda:

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 intenté ejecutar el código de Python después de actualizar Python e instalar Python-numpy, pero obtengo esto:

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'

Comentarios: Nunca lo use currentTimeMillispara la evaluación comparativa (use la versión nano en el Sistema) y las ejecuciones de 1k pueden no ser suficientes para involucrar al JIT (1.5k para el cliente y 10k para el servidor serían los valores predeterminados, aunque llame a myRand con la frecuencia suficiente para que eso sea JITed, que debería hacer que se compilen algunas funciones en la pila de llamadas, lo que puede funcionar aquí). Por último, pero no menos importante, el débil PNRG está engañando, pero también lo hace la solución C ++ y otras, así que supongo que eso no es demasiado injusto.
Voo

En Windows, debe evitar currentTimeMillis, pero para Linux para todas las mediciones de granularidad, pero muy finas, no necesita nano time, y la llamada para obtener nano time es mucho más costosa que millis. Así que no estoy de acuerdo con que NUNCA lo uses.
Chris Seline

¿Entonces está escribiendo código Java para una implementación particular de SO y JVM? En realidad, no estoy seguro de qué sistema operativo está utilizando, porque acabo de registrar mi árbol de desarrollo de HotSpot y Linux lo usa gettimeofday(&time, NULL)para milisegundos que no es monotónico y no ofrece ninguna garantía de precisión (por lo que en algunas plataformas / núcleos son exactamente iguales) problemas como la implementación actual de Windows de TimeMillis, por lo que también está bien o ninguno lo está). nanoTime, por otro lado, usa lo clock_gettime(CLOCK_MONOTONIC, &tp)que claramente también es lo correcto para usar cuando se compara en Linux.
Voo

Nunca me ha causado un problema desde que codifiqué Java en cualquier distribución o kernel de Linux.
Chris Seline

6

Golang versión 45X de python en mi máquina en los siguientes códigos de 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)
}

y los siguientes códigos de Python copiados desde arriba:

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

y el tiempo a continuación:

$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
¿Has pensado en usar "github.com/yanatan16/itertools"? ¿también dirías que esto funcionaría bien en múltiples goroutines?
ymg

5

C # 0.135s

C # basado en la pitón simple de Alistair Buxton : 0.278s
C # paralelo: 0.135s
Python de la pregunta: 5.907s
Pitón simple de Alistair : 0.853s

En realidad, no estoy seguro de que esta implementación sea correcta: su resultado es diferente, si observa los resultados en la parte inferior.

Ciertamente hay algoritmos más óptimos. Simplemente decidí usar un algoritmo muy similar al de Python.

C de un solo hilo

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

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

Prueba de salida:

Windows (.NET)

El C # es mucho más rápido en Windows. Probablemente porque .NET es más rápido que mono.

La sincronización del usuario y del sistema no parece funcionar (se usa git bashpara la sincronización).

$ 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
No creo que el código sea correcto como dices. Las salidas no son correctas.

@Lembik Sí. Sin embargo, agradecería que alguien me dijera dónde está mal: no puedo entenderlo (solo tener una comprensión mínima de lo que se supone que debe hacer no ayuda).
Bob

Sería interesante ver cómo funciona esto con .NET Native blogs.msdn.com/b/dotnet/archive/2014/04/02/…
Rick Minerich

@Lembik Acabo de revisarlo todo, por lo que puedo decir, debería ser idéntico a la otra solución de Python ... ahora estoy realmente confundido.
Bob

4

Haskell: ~ 2000x de aceleración por núcleo

Compile con 'ghc -O3 -funbox-strict-fields -threaded -fllvm', y ejecute con '+ RTS -Nk' donde k es el número de núcleos en su máquina.

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
¿Entonces con 4 núcleos es más de 9000 ? No hay forma de que eso sea correcto.
Cees Timmerman

La ley de Amdahl establece que la aceleración de la paralelización no es lineal con respecto al número de unidades de procesamiento en paralelo. en cambio, solo proporcionan rendimientos decrecientes
xaedes

@xaedes La aceleración parece esencialmente lineal para un bajo número de núcleos
user1502040

3

Rubí

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

el hilo no estaría completo sin PHP

6.6x más rápido

PHP v5.5.9 - 1.223 0.646 segundos;

vs

Python v2.7.6 - 8.072 segundos

<?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";
  • Usó un generador aleatorio personalizado (robado de la respuesta C), PHP uno apesta y los números no coinciden
  • convolve función simplificada un poco para ser más rápido
  • La verificación de array-with-zeros-only también está muy optimizada (ver $Fy $FSverificaciones).

Salidas:

$ 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

Editar. La segunda versión del script funciona por solo 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

Solución F #

El tiempo de ejecución es 0.030s cuando se compila a x86 en el CLR Core i7 4 (8) @ 3.4 Ghz

No tengo idea si el código es correcto.

  • Optimización funcional (plegado en línea) -> 0.026s
  • Construcción a través del proyecto de consola -> 0.022s
  • Se agregó un mejor algoritmo para la generación de matrices de permutación -> 0.018s
  • Mono para Windows -> 0.089s
  • Ejecución del script Python de 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 es un lenguaje orientado a la colección (kx.com)

Código reescrito para explotar Q idiomático, pero ninguna otra optimización inteligente

Los lenguajes de script optimizan el tiempo del programador, no el tiempo de ejecución

  • Q no es la mejor herramienta para este problema

Primer intento de codificación = no ganador, pero tiempo razonable (aprox. 30x de aceleración)

  • bastante competitivo entre intérpretes
  • detente y elige otro problema

NOTAS.-

  • el programa usa semilla predeterminada (ejecutables repetibles) Para elegir otra semilla para uso de generador aleatorio \S seed
  • El resultado se da como una secuencia de dos entradas, por lo que hay un sufijo final i en el segundo valor 27421 12133i -> leer como (27241, 12133)
  • Tiempo sin contar el inicio del intérprete. \t sentence mide el tiempo que consume esa oración

Muy interesante gracias.

1

Julia: 12.149 6.929 s

A pesar de sus afirmaciones de velocidad , ¡el tiempo inicial de compilación JIT nos detiene!

Tenga en cuenta que el siguiente código de Julia es efectivamente una traducción directa del código original de Python (sin optimizaciones) como una demostración de que puede transferir fácilmente su experiencia de programación a un lenguaje más rápido;)

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)

Editar

Correr con n = 8toma 32.935 s. Teniendo en cuenta que la complejidad de este algoritmo es O(2^n), entonces 4 * (12.149 - C) = (32.935 - C), donde Ces una constante que representa el tiempo de compilación JIT. Resolviendo Cesto, encontramos que C = 5.2203, sugiriendo que el tiempo de ejecución real n = 6es de 6.929 s.


¿Qué tal aumentar n a 8 para ver si Julia se recupera?

Esto ignora muchos de los consejos de rendimiento aquí: julia.readthedocs.org/en/latest/manual/performance-tips . Vea también la otra entrada de Julia que mejora significativamente. Sin embargo, la presentación es apreciada :-)
StefanKarpinski

0

Óxido, 6.6 ms, 1950x aceleración

Prácticamente una traducción directa del código de Alistair Buxton a Rust. Pensé en utilizar múltiples núcleos con rayón (concurrencia sin miedo), pero esto no mejoró el rendimiento, probablemente porque ya es muy rápido.

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

Y Cargo.toml, ya que uso dependencias externas:

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

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

Comparación de velocidad:

$ 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 es de aproximadamente 6,6 ms. Esto significa 1950 veces la aceleración. Aquí hay muchas optimizaciones posibles, pero estaba buscando legibilidad en lugar de rendimiento. Una posible optimización sería usar matrices en lugar de vectores para almacenar opciones, ya que siempre tendrán nelementos. También es posible usar RNG que no sea XorShift, ya que si bien Xorshift es más rápido que el CSPRNG HC-128 predeterminado, es más lento que los algoritmos PRNG más ingenuos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.