Wie langsam ist Python wirklich? (Oder wie schnell ist deine Sprache?)


149

Ich habe diesen Code, den ich in Python / NumPy geschrieben habe

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)

Es wird gezählt, wie oft die Faltung von zwei zufälligen Arrays, von denen eines länger als das andere ist, mit einer bestimmten Wahrscheinlichkeitsverteilung eine 0 an der ersten Position oder eine 0 an beiden Positionen hat.

Ich hatte eine Wette mit einem Freund, der sagt, dass Python eine schreckliche Sprache ist, um Code zu schreiben, der schnell sein muss. Auf meinem Computer dauert es 9s. Er sagt, es könnte 100-mal schneller gemacht werden, wenn es in einer "richtigen Sprache" geschrieben wäre.

Die Herausforderung besteht darin, herauszufinden, ob dieser Code in einer Sprache Ihrer Wahl tatsächlich 100-mal schneller gemacht werden kann. Ich werde Ihren Code testen und der schnellste in einer Woche wird gewinnen. Wenn jemand unter 0,09 s fällt, gewinnt er automatisch und ich verliere.

Status

  • Python . 30-fache Geschwindigkeit von Alistair Buxon! Obwohl es nicht die schnellste Lösung ist, ist es in der Tat mein Favorit.
  • Oktave . 100-fache Beschleunigung durch @Thethos.
  • Rust . 500-fache Beschleunigung durch @dbaupp.
  • C ++ . 570-fache Beschleunigung durch Guy Sirton.
  • C . 727-fache Beschleunigung durch @ace.
  • C ++ . Unglaublich schnell von @Stefan.

Die schnellsten Lösungen sind jetzt zu schnell, um sinnvoll Zeit zu haben. Ich habe daher n auf 10 erhöht und Iter = 100000 gesetzt, um die besten zu vergleichen. Unter dieser Maßnahme sind die schnellsten.

  • C . 7,5s von @ace.
  • C ++ . 1s von @Stefan.

Mein Computer Die Timings werden auf meinem Computer ausgeführt. Dies ist eine Ubuntu-Standardinstallation auf einem AMD FX-8350 Eight-Core-Prozessor. Dies bedeutet auch, dass ich in der Lage sein muss, Ihren Code auszuführen.

Follow-up veröffentlicht Da dieser Wettbewerb zu einfach war, um ein x100-Speedup zu erhalten, habe ich ein Follow-up für diejenigen veröffentlicht, die ihr Speed-Guru-Know-how ausüben möchten. Sehen Sie, wie langsam Python wirklich ist (Teil II)?

Antworten:


61

C ++ bisschen Magie

0,84 ms mit einfachem RNG, 1,67 ms mit c ++ 11 std :: knuth

0,16 ms mit geringfügiger algorithmischer Änderung (siehe Bearbeitung unten)

Die Python-Implementierung läuft auf meinem Rig in 7,97 Sekunden. Das ist also 9488- bis 4772-mal schneller, je nachdem, welches RNG Sie auswählen.

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

Kompilieren Sie in 64-Bit für zusätzliche Register. Bei Verwendung des einfachen Zufallsgenerators laufen die Schleifen in convolve () ohne Speicherzugriff, alle Variablen werden in den Registern gespeichert.

Wie es funktioniert: anstatt zu speichern Sund Fals in-Speicherarrays, als Bits in einem uint32_t gespeichert ist.
Für Swerden die nniedrigstwertigen Bits verwendet, wobei ein gesetztes Bit +1 und ein nicht gesetztes Bit -1 bezeichnet.
FBenötigt mindestens 2 Bits, um eine Verteilung von [-1, 0, 0, 1] zu erstellen. Dies erfolgt durch Erzeugen von Zufallsbits und Untersuchen der 16 niedrigstwertigen (aufgerufen r) und 16 höchstwertigen (aufgerufen l) Bits . Wenn l & ~rwir annehmen, dass F +1 ist, ~l & rnehmen wir an, dass dies F-1 ist. Ansonsten Fist es 0. Dies erzeugt die gesuchte Distribution.

Jetzt haben wir S, posBitsmit einem gesetzten Bit an jedem Ort , an dem F == 1 und negBitsmit einem gesetzten Bit an jedem Ort , an dem F == -1.

Wir können beweisen, dass F * S(wobei * für Multiplikation steht) unter der Bedingung +1 ergibt (S & posBits) | (~S & negBits). Wir können auch eine ähnliche Logik für alle Fälle generieren, in denen F * S-1 ausgewertet wird. Und schließlich wissen wir, dass sum(F * S)genau dann 0 ergibt, wenn das Ergebnis die gleiche Anzahl von -1 und +1 enthält. Dies ist sehr einfach zu berechnen, indem einfach die Anzahl von +1 Bits und -1 Bits verglichen werden.

Diese Implementierung verwendet 32-Bit-Ints, und das nakzeptierte Maximum ist 16. Es ist möglich, die Implementierung durch Ändern des Zufallsgenerierungscodes auf 31 Bit und durch Verwenden von uint64_t anstelle von uint32_t auf 63 Bit zu skalieren.

bearbeiten

Die folgende Faltungsfunktion:

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

verkürzt die Laufzeit auf 0.160-0.161ms. Durch manuelles Abrollen der Schlaufe (oben nicht abgebildet) ergibt sich ein Wert von 0,150. Der weniger triviale Fall n = 10, iter = 100000 läuft unter 250ms. Ich bin mir sicher, dass ich es unter 50 ms schaffen kann, wenn ich zusätzliche Kerne nutze, aber das ist zu einfach.

Dies geschieht, indem der innere Loop-Zweig frei gemacht und der F- und S-Loop vertauscht werden.
Wenn bothZeroes nicht erforderlich ist, kann ich die Laufzeit auf 0,02 ms reduzieren, indem ich alle möglichen S-Arrays sparsam durchschleife.


3
Könnten Sie eine gcc-freundliche Version bereitstellen und auch, was Ihre Befehlszeile bitte sein würde? Ich bin nicht sicher, ob ich es aktuell testen kann.

Ich weiß nichts darüber, aber Google sagt mir, dass __builtin_popcount ein Ersatz für _mm_popcnt_u32 () sein könnte.

3
Code aktualisiert, verwendet die Option #ifdef, um den richtigen Befehl popcnt auszuwählen. Es kompiliert mit -std=c++0x -mpopcnt -O2und benötigt 1,01 ms, um im 32-Bit-Modus ausgeführt zu werden (ich habe keine 64-Bit-GCC-Version zur Hand).
Stefan

Könnten Sie es die Ausgabe drucken lassen? Ich bin mir nicht sicher, ob es derzeit tatsächlich etwas tut :)

7
Sie sind eindeutig ein Zauberer. + 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

Verdammt klar, du hast deine Wette verloren! Auch hier kein Tropfen Parallelisierung, nur gerade Fortran 90+.

BEARBEITEN Ich habe Guy Sirtons Algorithmus zum Permutieren des Arrays verwendet S(guter Fund: D). Ich hatte anscheinend auch die -g -tracebackCompiler-Flags aktiviert, die diesen Code auf ungefähr 0.017s verlangsamten. Derzeit kompiliere ich dies als

ifort -fast -o convolve convolve_random_arrays.f90

Für diejenigen, die nicht haben ifort, können Sie verwenden

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

EDIT 2 : Die Laufzeitverringerung ist darauf zurückzuführen, dass ich zuvor etwas falsch gemacht habe und eine falsche Antwort erhalten habe. Es ist anscheinend langsamer, es richtig zu machen. Ich kann immer noch nicht glauben, dass C ++ schneller ist als meins, also werde ich wahrscheinlich einige Zeit in dieser Woche damit verbringen, den Mist herauszuarbeiten, um ihn zu beschleunigen.

EDIT 3 : Indem ich einfach die RNG-Sektion mit einer auf BSDs RNG basierenden Sektion ändere (wie von Sampo Smolander vorgeschlagen) und die konstante Teilung durch eliminiere m1, reduziere ich die Laufzeit auf dieselbe wie die C ++ - Antwort von Guy Sirton . Bei Verwendung von statischen Arrays (wie von Sharpie vorgeschlagen) wird die Laufzeit auf unter die C ++ - Laufzeit gesenkt! Yay Fortran! : D

EDIT 4 Offensichtlich kompiliert dies nicht (mit gfortran) und wird nicht korrekt ausgeführt (falsche Werte), da die Ganzzahlen ihre Grenzen überschreiten. Ich habe Korrekturen vorgenommen, um sicherzustellen, dass es funktioniert, aber dazu muss entweder ifort 11+ oder gfortran 4.7+ (oder ein anderer Compiler, der dies zulässt, iso_fortran_envund der Typ F2008 int64) vorhanden sein.

Hier ist der Code:

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

Ich nehme an, die Frage ist jetzt, ob Sie aufhören werden, langsam wie Melasse Python zu verwenden und schnell wie Elektronen Fortran bewegen können.


1
Wäre die case-Anweisung nicht sowieso schneller als eine Generatorfunktion? Es sei denn, Sie erwarten eine Art Verzweigungsvorhersage / Cache-Line / usw. Beschleunigung?
OrangeDog

17
Die Geschwindigkeit sollte auf derselben Maschine verglichen werden. Welche Laufzeit haben Sie für den OP-Code erhalten?
Nbubis

3
Die C ++ - Antwort implementiert einen eigenen, sehr leichten Zufallszahlengenerator. In Ihrer Antwort wurde die mit dem Compiler gelieferte Standardeinstellung verwendet, die möglicherweise langsamer ist.
Sampo Smolander

3
Das C ++ - Beispiel scheint auch statisch zugewiesene Arrays zu verwenden. Versuchen Sie, Arrays mit fester Länge zu verwenden, die zur Kompilierungszeit festgelegt sind, und prüfen Sie, ob Sie eine Pause einlegen müssen.
Sharpie

1
@KyleKanos @Lembik Das Problem ist, dass die Ganzzahlzuweisung in fortran nicht implizit die int64-Spezifikation verwendet. Daher sind die Zahlen int32, bevor eine Konvertierung durchgeführt wird. Der Code sollte lauten: integer(int64) :: b = 3141592653_int64für alle int64. Dies ist Teil des fortran-Standards und wird vom Programmierer in einer typdeklarierten Programmiersprache erwartet. (Beachten Sie, dass die Standardeinstellungen dies natürlich außer Kraft setzen können)
nullter

69

Python 2.7 - 0.882s 0.283s

(OP Original: 6.404s)

Edit: Steven Rumbalskis Optimierung durch Vorberechnung von F-Werten. Mit dieser Optimierung schlägt Cpython Pypys 0.365s.

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

Der ursprüngliche Code von OP verwendet so winzige Arrays, dass die Verwendung von Numpy keinen Nutzen bringt, wie diese reine Python-Implementierung zeigt. Aber siehe auch diese blöde Implementierung, die dreimal schneller ist als mein Code.

Ich optimiere auch, indem ich den Rest der Faltung überspringe, wenn das erste Ergebnis nicht Null ist.


11
Bei pypy dauert dies ca. 0,5 Sekunden.
Alistair Buxton

2
Sie erhalten eine viel überzeugendere Beschleunigung, wenn Sie n = 10 einstellen. Ich erhalte 19s gegenüber 4.6s für cpython gegenüber pypy.

3
Eine weitere Optimierung wäre die Vorausberechnung der Möglichkeiten, Fda es nur 4032 davon gibt. Definieren Sie choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))außerhalb der Schleifen. Dann in der inneren Schleife definieren F = random.choice(choicesF). Mit einem solchen Ansatz bekomme ich eine dreifache Beschleunigung.
Steven Rumbalski

3
Wie wäre es, dies in Cython zu kompilieren? Dann fügen Sie ein paar taktvolle statische Typen hinzu?
Thane Brimhall

2
Stecke alles in eine Funktion und rufe sie am Ende auf. Dadurch werden die Namen lokalisiert, wodurch auch die von @riffraff vorgeschlagene Optimierung funktioniert. Verschieben Sie auch die Erstellung von range(iters)aus der Schleife. Insgesamt bekomme ich eine Beschleunigung von ca. 7% über Ihre sehr nette Antwort.
WolframH

44

Rost: 0,011 s

Ursprüngliches Python: 8.3

Eine gerade Übersetzung des ursprünglichen 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)
    }
}
  • Kompiliert mit --opt-level=3
  • Mein Rost-Compiler ist eine aktuelle Nacht : ( rustc 0.11-pre-nightly (eea4909 2014-04-24 23:41:15 -0700)um genau zu sein)

Ich habe es mit der nächtlichen Version von Rust kompilieren lassen. Allerdings denke ich, dass der Code falsch ist. Die Ausgabe sollte in der Nähe von firstzero 27215 bothzero 12086 liegen. Stattdessen wird 27367 6481

@Lembik, whoops, hat meine as und bs in der Faltung verwechselt ; behoben (ändert die Laufzeit nicht merklich).
Huon

4
Es ist eine sehr schöne Demonstration der Geschwindigkeit von Rost.

39

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

Python 2.7.6 / Numpy 1.8.1 - 12s

Beschleunigung ~ x800.

Die Lücke wäre viel kleiner, wenn die gefalteten Arrays sehr groß wären ...

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

Ein paar Anmerkungen:

  • Die Zufallsfunktion wird in der Schleife aufgerufen, also habe ich mich für einen sehr leichten linearen Kongruenzgenerator entschieden (aber die MSBs großzügig betrachtet).
  • Dies ist wirklich nur der Ausgangspunkt für eine optimierte Lösung.
  • Hat nicht so lange gedauert, um zu schreiben ...
  • Ich durchlaufe alle Werte von S und nehme S[0]die "niedrigstwertige" Ziffer.

Fügen Sie diese Hauptfunktion für ein eigenständiges Beispiel hinzu:

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

1
Tatsächlich. Die winzige Größe der Arrays im OP-Code bedeutet, dass die Verwendung von Numpy um eine Größenordnung langsamer ist als die von Straight Python.
Alistair Buxton

2
Jetzt rede ich über x800!

Sehr schön! Ich habe die Geschwindigkeit meines Codes aufgrund Ihrer advanceFunktion erhöht , daher ist mein Code jetzt schneller als Ihr: P (aber sehr gute Konkurrenz!)
Kyle Kanos

1
@lembik ja wie Mat sagt. Sie benötigen C ++ 11-Unterstützung und eine Hauptfunktion. Lassen Sie mich wissen, wenn Sie mehr Hilfe benötigen, um dies zum Laufen zu bringen ...
Guy Sirton

2
Ich habe dies gerade getestet und konnte weitere 20% mit einfachen Arrays anstelle von std :: vector
rasieren

21

C

Nimmt 0.015s auf meiner Maschine, wobei der ursprüngliche OP-Code ~ 7.7s benötigt. Versucht zu optimieren, indem das zufällige Array generiert und in derselben Schleife gewickelt wird, aber es scheint keinen großen Unterschied zu machen.

Das erste Array wird erzeugt, indem eine ganze Zahl genommen, in Binärform ausgegeben und alle 1 in -1 und alle 0 in 1 geändert wird. Der Rest sollte sehr einfach sein.

Edit: anstelle von nals int, jetzt haben wir nals ein Makro definierte Konstante, so können wir verwenden int arr[n];statt malloc.

Edit2: Anstelle der eingebauten rand()Funktion wird jetzt ein Xorshift-PRNG implementiert. Außerdem werden beim Generieren des Zufallsarrays viele bedingte Anweisungen entfernt.

Kompilieranleitung:

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

Code:

#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
Ich habe das getestet. Es ist sehr schnell (versuchen Sie es mit n = 10) und liefert eine korrekt aussehende Ausgabe. Danke.

Diese Implementierung folgt nicht dem Original, da bei einem Zufallsvektor mit Nullen nur das letzte Element neu generiert wird. Im Original wäre der gesamte Vektor. Sie müssen diese Schleife do{}while(!flag)oder etwas in diesem Sinne einschließen . Ich erwarte nicht, dass sich dadurch die Laufzeit stark ändert (möglicherweise schneller).
Guy Sirton

@Guy Sirton Beachten Sie, dass vor der continue;Anweisung, die ich zugewiesen -1habe k, wieder keine Schleife von 0 ausgeführt wird.
ace_HongKongIndependence

1
@ace ah! Du hast recht. Ich habe zu schnell gescannt und es sah -=eher so aus als =-:-) Eine while-Schleife wäre besser lesbar.
Guy Sirton

17

J

Ich erwarte nicht, kompilierte Sprachen zu schlagen, und irgendetwas sagt mir, dass es eine wundersame Maschine braucht, um weniger als 0,09 Sekunden damit zu schaffen, aber ich möchte dieses J trotzdem einreichen, weil es ziemlich clever ist.

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

Dies dauert auf einem Laptop aus dem letzten Jahrzehnt etwa 0,5 s, nur etwa 20x so schnell wie der Python in der Antwort. Die meiste Zeit verbringen convwir damit, weil wir es faul (wir berechnen die gesamte Faltung) und in voller Allgemeinheit schreiben.

Da wir etwas über Sund wissen F, können wir die Dinge beschleunigen, indem wir spezifische Optimierungen für dieses Programm vornehmen. Das Beste, was mir conv =: ((num, num+1) { +//.)@:(*/)"1eingefallen ist, ist: Wählen Sie genau die beiden Zahlen aus, die von den Diagonalsummen bis zu den längsten Elementen der Faltung reichen. Das halbiert ungefähr die Zeit.


6
J ist immer eine Einreichung wert, Mann :)
Vitaly Dyatlov

17

Perl - 9.3X schneller ... 830% Verbesserung

Bei meinem alten Netbook dauert die Ausführung des OP-Codes 53 Sekunden. Alistair Buxtons Version dauert ungefähr 6,5 Sekunden, und die folgende Perl-Version dauert ungefähr 5,7 Sekunden.

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 mit mkl-Bindungen - 0.086s

(OPs Original: 6.404s) (Buxtons reine Python: 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

Wie Buxton hervorhebt, verwendet der ursprüngliche OP-Code so winzige Arrays, dass die Verwendung von Numpy keinen Nutzen bringt. Diese Implementierung nutzt Numpy, indem alle F- und S-Fälle auf eine Array-orientierte Weise auf einmal ausgeführt werden. Dies zusammen mit mkl-Bindungen für Python führt zu einer sehr schnellen Implementierung.

Beachten Sie auch, dass das Laden der Bibliotheken und das Starten des Interpreters nur 0,076 Sekunden dauert, sodass die eigentliche Berechnung ~ 0,01 Sekunden dauert, ähnlich wie bei der C ++ - Lösung.


Was sind mkl-Bindungen und wie bekomme ich sie auf Ubuntu?

Laufen python -c "import numpy; numpy.show_config()"zeigt Ihnen, ob Ihre Version von numpy mit blas / atlas / mkl usw. kompiliert ist. ATLAS ist ein kostenloses, beschleunigtes Mathematikpaket, mit dem numpy verknüpft werden kann. Intel MKL, für das Sie normalerweise zahlen müssen (es sei denn, Sie sind Akademiker). und kann mit numpy / scipy verknüpft werden .
Alemi

Verwenden Sie auf einfache Weise die Anaconda- Python-Distribution und das Accelerate- Paket. Oder nutzen Sie die begeisterte Distribution.
Alemi

Wenn Sie auf Fenster sind, dann laden Sie sich einfach von numpy hier . Vorkompilierte Numpy-Installer, die mit MKL verknüpft sind.
Fake Name

9

MATLAB 0.024s

Computer 1

  • Originalcode: ~ 3,3 s
  • Alistar Buxtons Code: ~ 0,51 s
  • Alistar Buxtons neuer Code: ~ 0,25 s
  • Matlab Code: ~ 0.024 s (Matlab läuft bereits)

Computer 2

  • Originalcode: ~ 6,66 s
  • Alistar Buxtons Code: ~ 0,64 s
  • Alistar Buxtons neuer Code:?
  • Matlab: ~ 0,07 s (Matlab läuft bereits)
  • Oktave: ~ 0,07 s

Ich beschloss, das ach so langsame Matlab auszuprobieren. Wenn Sie wissen, wie, können Sie die meisten Schleifen (in Matlab) loswerden, was es ziemlich schnell macht. Die Speicheranforderungen sind jedoch höher als bei Lösungen mit Schleifen. Dies ist jedoch kein Problem, wenn Sie nicht über sehr große Arrays verfügen.

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

Folgendes mache ich:

  • Verwenden Sie die Kyle Kanos-Funktion, um durch S zu permutieren
  • Berechne alle n * iter Zufallszahlen auf einmal
  • Karte 1 bis 4 bis [-1 0 0 1]
  • verwende Matrixmultiplikation (elementweise Summe (F * S (1: 5)) ist gleich Matrixmultiplikation von F * S (1: 5) '
  • für bothzero: Berechne nur Mitglieder, die die erste Bedingung erfüllen

Ich nehme an, Sie haben kein Matlab, was schade ist, da ich mir wirklich gewünscht hätte, zu sehen, wie es verglichen wird ...

(Die Funktion kann langsamer sein, wenn Sie sie zum ersten Mal ausführen.)


Nun, ich habe eine Oktave, wenn Sie dafür sorgen können, dass es funktioniert ...?

Ich kann es versuchen - ich habe aber nie mit Oktaven gearbeitet.
Mathause

Ok, ich kann es so ausführen, wie es in der Oktave ist, wenn ich den Code in eine Datei namens call_convolve_random_arrays.m lege und ihn dann von der Oktave aus aufrufe.
Mathause

Benötigt es etwas mehr Code, um tatsächlich etwas zu tun? Wenn ich "octave call_convolve_random_arrays.m" mache, wird nichts ausgegeben. Siehe bpaste.net/show/JPtLOCeI3aP3wc3F3aGf

Entschuldigung, versuche Octave zu öffnen und starte es dann. Es sollte firstzero, bothzero und die Ausführungszeit anzeigen.
Mathause

7

Julia: 0,30 s

Ops Python: 21,36 s (Core2-Duo)

71x beschleunigen

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

Ich habe einige Änderungen an Armans Julia-Antwort vorgenommen: Zunächst habe ich sie in eine Funktion eingeschlossen, da globale Variablen Julias Typinferenz und JIT erschweren: Eine globale Variable kann ihren Typ jederzeit ändern und muss bei jeder Operation überprüft werden . Dann habe ich die anonymen Funktionen und das Array-Verständnis beseitigt. Sie sind nicht wirklich notwendig und immer noch ziemlich langsam. Julia ist momentan schneller mit Abstraktionen auf niedrigerer Ebene.

Es gibt viel mehr Möglichkeiten, es schneller zu machen, aber das macht einen anständigen Job.


Messen Sie die Zeit in der REPL oder führen Sie die gesamte Datei über die Befehlszeile aus?
Aditya

beide aus dem REPL.
user20768

6

Ok, ich poste dies nur, weil ich der Meinung bin, dass Java hier vertreten sein muss. Ich kann andere Sprachen nicht leiden und muss gestehen, dass ich das Problem nicht genau verstehe. Daher benötige ich Hilfe, um diesen Code zu reparieren. Ich habe den größten Teil des C-Beispiels von Code Ace gestohlen und mir dann einige Ausschnitte von anderen ausgeliehen. Ich hoffe, das ist kein Fauxpas ...

Eine Sache, auf die ich hinweisen möchte, ist, dass Sprachen, die zur Laufzeit optimiert werden, mehrmals ausgeführt werden müssen, um die volle Geschwindigkeit zu erreichen. Ich denke, es ist gerechtfertigt, die voll optimierte Geschwindigkeit (oder zumindest die Durchschnittsgeschwindigkeit) zu wählen, da die meisten Dinge, die Sie mit schnellem Laufen zu tun haben, ein paar Mal ausgeführt werden.

Der Code muss noch repariert werden, aber ich habe ihn trotzdem ausgeführt, um zu sehen, wann ich ihn bekommen würde.

Hier sind die Ergebnisse auf einer Intel (R) Xeon (R) -CPU E3-1270 V2 mit 3,50 GHz unter Ubuntu, die 1000-mal ausgeführt wird:

server: / tmp # time java8 -cp. Prüfer

firstzero 40000

Bothzero 20000

erste Laufzeit: 41 ms letzte Laufzeit: 4 ms

echte 0m5.014s Benutzer 0m4.664s sys 0m0.268s

Hier ist mein beschissener Code:

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

Und ich habe versucht, den Python-Code nach dem Upgrade von Python und der Installation von Python-Numpy auszuführen, aber ich bekomme Folgendes:

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'

Kommentare: Nie verwenden currentTimeMillisfür das Benchmarking (die Nano - Version im System verwenden) und 1k läuft möglicherweise nicht ausreichen , um den JIT zu bekommen beteiligt (1,5k für Client und 10k für Server die Standardwerte wäre, obwohl Sie rufen Myrand oft genug , dass das wird JITed, was dazu führen sollte, dass einige Funktionen im Callstack kompiliert werden, was hier möglicherweise funktioniert.) Nicht zuletzt betrügt das schwache PNRG, aber auch die C ++ - Lösung und andere, also denke ich, ist das nicht zu unfair.
Voo

Unter Windows müssen Sie currentTimeMillis vermeiden, aber für Linux benötigen Sie keine Nano-Zeit, außer für sehr feine Granularitätsmessungen, und der Aufruf, Nano-Zeit zu erhalten, ist viel teurer als für Millis. Daher bin ich nicht der Meinung, dass Sie es NIEMALS verwenden sollten.
Chris Seline

Sie schreiben also Java-Code für eine bestimmte Betriebssystem- und JVM-Implementierung? Eigentlich bin ich mir nicht sicher, welches Betriebssystem du verwendest, weil ich gerade meinen HotSpot- gettimeofday(&time, NULL)Entwicklungsbaum eingecheckt habe und Linux für Millisekunden verwendet, was nicht monoton ist und keine Genauigkeitsgarantien gibt (also auf einigen Plattformen / Kerneln genau das gleiche) Probleme wie die aktuelle Timemillis-Windows-Implementierung - also entweder dass man auch in Ordnung ist oder keines ist). nanoTime verwendet dagegen, clock_gettime(CLOCK_MONOTONIC, &tp)was natürlich auch beim Benchmarking unter Linux das Richtige ist.
Voo

Es hat für mich nie ein Problem verursacht, da ich Java auf einer Linux-Distribution oder einem Linux-Kernel codiert habe.
Chris Seline

6

Golang-Version 45X von Python auf meinem Computer unter Golang-Codes:

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

und die folgenden Python-Codes, die von oben kopiert wurden:

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

und die Zeit unten:

$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
Hast du darüber nachgedacht "github.com/yanatan16/itertools"? Würden Sie auch sagen, dass dies in mehreren Goroutinen gut funktionieren würde?
ymg

5

C # 0,135s

C # basierend auf Alistair Buxtons einfacher Python : 0.278s
Parallelisiert C #: 0.135s
Python aus der Frage: 5.907s
Alistairs einfacher Python: 0.853s

Ich bin mir nicht sicher, ob diese Implementierung korrekt ist - die Ausgabe ist anders, wenn man sich die Ergebnisse unten ansieht.

Es gibt sicherlich mehr optimale Algorithmen. Ich habe gerade beschlossen, einen sehr ähnlichen Algorithmus wie den von Python zu verwenden.

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

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

Testausgang:

Windows (.NET)

Das C # ist unter Windows viel schneller. Wahrscheinlich, weil .NET schneller als Mono ist.

Das Benutzer- und Sys-Timing scheint nicht zu funktionieren (wird git bashfür das Timing verwendet).

$ 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
Ich denke nicht, dass der Code korrekt ist, wie Sie sagen. Die Ausgaben stimmen nicht.

@ Lembik Ja. Ich würde es begrüßen, wenn mir jemand sagen könnte, wo es falsch ist - ich kann es nicht herausfinden (nur ein minimales Verständnis dessen, was es tun soll , hilft nicht).
Bob

Es wäre interessant zu sehen, wie dies mit .NET Native Blogs.msdn.com/b/dotnet/archive/2014/04/02/…
Rick Minerich

@Lembik Ich habe gerade alles durchgegangen, soweit ich das beurteilen kann, sollte es mit der anderen Python-Lösung identisch sein ... jetzt bin ich wirklich verwirrt.
Bob

4

Haskell: ~ 2000-fache Geschwindigkeit pro Kern

Kompilieren Sie mit 'ghc -O3 -funbox-strict-fields -threaded -fllvm' und führen Sie '+ RTS -Nk' aus, wobei k die Anzahl der Kerne auf Ihrem Computer ist.

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
Also mit 4 Kernen sind es über 9000 ?! Es gibt keinen Weg, der richtig sein könnte.
Cees Timmerman

Amdahls Gesetz besagt, dass die Beschleunigung der Parallelisierung nicht linear zur Anzahl der parallelen Verarbeitungseinheiten ist. stattdessen liefern sie nur nachlassende Renditen
xaedes

@xaedes Die Beschleunigung scheint für eine geringe Anzahl von Kernen im Wesentlichen linear zu sein
user1502040

3

Rubin

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

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

Thread wäre nicht vollständig ohne PHP

6.6x schneller

PHP v5.5.9 - 1.223 0.646 sec;

vs

Python 2.7.6 - 8.072 Sek

<?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";
  • Benutzte einen benutzerdefinierten Zufallsgenerator (gestohlen von C-Antwort), PHP ist zum Kotzen und die Zahlen stimmen nicht überein
  • convolve Funktion etwas vereinfacht, um schneller zu sein
  • Die Überprüfung auf Array-mit-Nullen-nur ist ebenfalls sehr optimiert (siehe $Fund $FSÜberprüfungen).

Ausgänge:

$ 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

Bearbeiten. Die zweite Version des Skripts funktioniert nur für 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

F # -Lösung

Die Laufzeit beträgt beim Kompilieren auf x86 auf dem CLR Core i7 4 (8) bei 3,4 GHz 0,030 Sekunden

Ich habe keine Ahnung, ob der Code korrekt ist.

  • Funktionsoptimierung (Inline-Fold) -> 0,026s
  • Erstellen über das Konsolenprojekt -> 0.022s
  • Es wurde ein besserer Algorithmus zur Erzeugung der Permutations-Arrays hinzugefügt -> 0.018s
  • Mono für Windows -> 0.089s
  • Ausführen von Alistairs Python-Skript -> 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 ist eine sammlungsorientierte Sprache (kx.com)

Code umgeschrieben, um das idiomatische Q zu untersuchen, aber keine anderen cleveren Optimierungen

Skriptsprachen optimieren die Programmierzeit und nicht die Ausführungszeit

  • Q ist nicht das beste Werkzeug für dieses Problem

Erster Codierungsversuch = kein Gewinner, aber angemessene Zeit (ca. 30-fache Geschwindigkeit)

  • ziemlich wettbewerbsfähig unter Dolmetschern
  • halt an und wähle ein anderes problem

ANMERKUNGEN.-

  • Programm verwendet Standard-Startwert (wiederholbare ausführbare Befehle) Zum Auswählen eines anderen Startwerts für die Verwendung als Zufallsgenerator \S seed
  • Das Ergebnis wird als Quadrat mit zwei Zoll angegeben, sodass beim zweiten Wert ein abschließendes i-Suffix angezeigt wird. 27421 12133i -> read as (27241, 12133)
  • Startzeit des Interpreters wird nicht mitgezählt. \t sentence misst die von diesem Satz verbrauchte Zeit

Sehr interessant, danke.

1

Julia: 12,149 6,929 s

Trotz ihres Anspruchs auf Geschwindigkeit hält uns die anfängliche JIT-Kompilierungszeit zurück!

Beachten Sie, dass der folgende Julia-Code effektiv eine direkte Übersetzung des ursprünglichen Python-Codes ist (keine Optimierungen vorgenommen), um zu demonstrieren, dass Sie Ihre Programmiererfahrung einfach auf eine schnellere Sprache übertragen können;)

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)

Bearbeiten

Laufen mit n = 8dauert 32.935 s. Bedenkt man, dass die Komplexität dieses Algorithmus ist O(2^n), dann 4 * (12.149 - C) = (32.935 - C), wo Cist eine Konstante , die JIT - Kompilierung Zeit darstellt. Wenn Cwir das lösen , finden wir, dass C = 5.2203die tatsächliche Ausführungszeit für n = 66.929 s beträgt.


Wie wäre es, wenn Sie n auf 8 erhöhen, um zu sehen, ob Julia dann zu ihrem Recht kommt?

Dies ignoriert viele der Leistungstipps hier: julia.readthedocs.org/en/latest/manual/performance-tips . Siehe auch den anderen Julia-Eintrag, der deutlich besser abschneidet. Die Einreichung ist jedoch
erwünscht

0

Rust, 6,6 ms, 1950x Beschleunigung

So ziemlich eine direkte Übersetzung von Alistair Buxtons Code nach Rust. Ich dachte darüber nach, mehrere Kerne mit Rayon zu verwenden (furchtlose Parallelität!), Aber dies verbesserte die Leistung nicht, wahrscheinlich, weil es bereits sehr schnell ist.

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

Und Cargo.toml, da ich externe Abhängigkeiten benutze:

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

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

Geschwindigkeitsvergleich:

$ 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 beträgt ca. 6,6 ms. Das bedeutet 1950-fache Beschleunigung. Hier sind viele Optimierungen möglich, aber ich habe mich eher für die Lesbarkeit als für die Leistung entschieden. Eine mögliche Optimierung wäre die Verwendung von Arrays anstelle von Vektoren zum Speichern von Auswahlen, da diese immer nElemente enthalten. Es ist auch möglich, RNG anders als XorShift zu verwenden, da Xorshift zwar schneller als der Standard-HC-128-CSPRNG ist, aber langsamer als der naivste PRNG-Algorithmus.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.