Наскільки повільно насправді Python? (Або як швидка ваша мова?)


149

У мене є цей код, який я написав у 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)

Він підраховує кількість разів згортання двох випадкових масивів, одного, який довший іншого, при певному розподілі ймовірностей, має 0 в першій позиції або 0 в обох позиціях.

Я мав ставку з другом, який каже, що Python - це жахлива мова, щоб писати код, на якому потрібно швидко. На моєму комп’ютері це займає 9 с. Він каже, що це можна зробити в 100 разів швидше, якщо це було написано "належною мовою".

Завдання полягає в тому, щоб зрозуміти, чи справді цей код можна зробити в 100 разів швидшим на будь-якій обраній вами мові. Я перевірю ваш код і найшвидший за тиждень переможе. Якщо хтось потрапить нижче 0,09, тоді він автоматично виграє, і я програю.

Статус

  • Пітон . 30 разів прискорити Алістера Баксона! Хоча це не найшвидше рішення, воно насправді є моїм улюбленим.
  • Октава . У 100 разів пришвидшити @Thethos.
  • Іржа . 500 разів пришвидшити @dbaupp.
  • C ++ . Гай Сіртон у 570 разів швидше.
  • C . 727 разів пришвидшити @ace.
  • C ++ . Неймовірно швидко від @Stefan.

Зараз найшвидші рішення зараз занадто швидкі, щоб обґрунтувати час. Тому я збільшив n до 10 і встановив ітери = 100000 для порівняння найкращих. Під цим заходом знаходяться найшвидші.

  • C . 7,5s від @ace.
  • C ++ . 1s від @Stefan.

My Machine Часи синхронізуються на моїй машині. Це стандартна установка ubuntu на восьмиядерний процесор AMD FX-8350. Це також означає, що мені потрібно мати можливість запускати ваш код.

Наступні публікації Оскільки ця конкуренція була надто простою для отримання прискорення x100, я опублікував подання для тих, хто хоче вправити свій досвід гуру швидкості. Подивіться, наскільки повільним є справді Python (частина II)?

Відповіді:


61

C ++ бітова магія

0,84 мс з простим RNG, 1,67 мс з c ++ 11 ст :: кнут

0,16 мс із незначною алгоритмічною модифікацією (див. Редагування нижче)

Реалізація пітона працює на моїй установці за 7,97 секунд. Отже, це в 9488 - 4772 рази швидше, залежно від того, який RNG ви виберете.

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

Компілюйте в 64-розрядні для додаткових регістрів. При використанні простого випадкового генератора петлі в convolve () працюють без доступу до пам'яті, всі змінні зберігаються в регістрах.

Як це працює: замість того, щоб зберігати Sі Fяк масиви в пам'яті, він зберігається як біти в uint32_t.
Для Sцього використовуються nнайменш значущі біти, коли набір бітів позначає +1, а невідомий біт позначає -1.
Fдля створення розподілу [-1, 0, 0, 1] потрібно щонайменше 2 біта. Це робиться шляхом генерації випадкових бітів та вивчення 16 найменш значущих (називаних r) та 16 найбільш значущих бітів (названих l). Якщо l & ~rприпустити, що F дорівнює +1, якщо ~l & rприпустити, що Fце -1. Інакше Fдорівнює 0. Це генерує дистрибуцію, яку ми шукаємо.

Тепер у нас є S, posBitsз набором біт на кожному місці , де F == 1 і negBitsз набором біт на кожному місці , де F == -1.

Ми можемо довести, що F * S(де * позначає множення) оцінюється на +1 за умовою (S & posBits) | (~S & negBits). Ми також можемо створити подібну логіку для всіх випадків, коли F * Sоцінюється до -1. І нарешті, ми знаємо, що sum(F * S)оцінюється на 0, якщо і лише тоді, коли в результаті є рівна кількість -1 та + 1. Це дуже просто обчислити, просто порівнявши кількість +1 біт і -1 біт.

Ця реалізація використовує 32 бітні вставки, а максимально nприйняте - 16. Можна масштабувати реалізацію до 31 біта, змінивши випадковий код генерування, і до 63 біта, використовуючи uint64_t замість uint32_t.

редагувати

Функція згортання:

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

скорочує час виконання до 0,160-0,161 мс. Ручне розкручування циклу (не на фотографії вище) складає 0,150. Менш тривіальний n = 10, iter = 100000 випадок працює за 250 мс. Я впевнений, що я можу отримати його за 50 мс, використовуючи додаткові сердечники, але це занадто просто.

Це робиться, зробивши внутрішню гілку петлі вільною і замінивши петлю F і S.
Якщо bothZeroцього не потрібно, я можу скоротити час виконання до 0,02 мс, рідко перебираючи всі можливі S масиви.


3
Чи можете ви надати gcc дружню версію, а також, яким буде ваш командний рядок? Я не впевнений, що можу зараз перевірити це.

Я нічого не знаю про це, але Google говорить мені, що __builtin_popcount може бути заміною _mm_popcnt_u32 ().

3
Код оновлений, використовує перемикач #ifdef для вибору правильної команди popcnt. Він компілює -std=c++0x -mpopcnt -O2і займає 1,01 мс для запуску в 32-бітовому режимі (у мене немає 64-бітної версії GCC під рукою).
Стефан

Не могли б ви змусити його друкувати вихід? Я не впевнений, чи справді щось робить на даний момент :)

7
Ви явно майстер. + 1
BurntPizza

76

Python2.7 + Numpy 1.8.1: 10.242 с

Фортран 90+: 0,029 с 0,003 с 0,022 с 0,010 с

Чорт прямо, ти програв ставку! Ні краплі паралелізації тут, просто прямо Fortran 90+.

EDIT Я взяв алгоритм Гая Сіртона для перетворення масиву S(хороша знахідка: D). У мене, мабуть, також були -g -tracebackактивні прапорці компілятора, які сповільнювали цей код приблизно до 0,017. Наразі я складаю це як

ifort -fast -o convolve convolve_random_arrays.f90

Для тих, хто цього не має ifort, можна використовувати

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

EDIT 2 : Скорочення часу виконання - це тому, що я робив щось не так раніше і отримав неправильну відповідь. Зробити це правильним шляхом, мабуть, повільніше. Я досі не можу повірити, що C ++ швидше мого, тому я, мабуть, збираюся витратити деякий час на цьому тижні, намагаючись виправити це лайно, щоб пришвидшити його.

EDIT 3 : Просто змінивши розділ RNG за допомогою одного на основі RNG BSD (як це запропонував Sampo Smolander) та усунувши постійний поділ на m1, я скоротив час виконання так само, як відповідь C ++ від Гая Сіртона . Використання статичних масивів (як це запропонував Sharpie) зменшує час роботи під час С ++! Так, Фортран! : D

EDIT 4 Мабуть, це не компілюється (з gfortran) та запускається правильно (неправильні значення), оскільки цілі числа перевищують їх межі. Я вніс виправлення, щоб переконатися, що він працює, але для цього потрібно мати або ifort 11+, або gfortran 4.7+ (або інший компілятор, що дозволяє iso_fortran_envі int64типу F2008 ).

Ось код:

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

Я припускаю, що питання зараз у тому, чи перестанеш ти використовувати повільний Python з повільними темпами та використовувати Fortran з швидкими рухами як електрони;).


1
Невже заява про справу все-таки не буде швидшою, ніж функція генератора? Якщо ви не очікуєте швидкого прогнозування гілки / кеш-лінії / тощо?
OrangeDog

17
Швидкість слід порівнювати на одній машині. Який час ви отримали за кодом ОП?
nbubis

3
Відповідь C ++ реалізує власний, дуже легкий генератор випадкових чисел. У вашій відповіді використовується типова версія компілятора, що може бути повільніше?
Сампо Смоландер

3
Крім того, схоже, що на прикладі C ++ використовується статично розподілений масив. Спробуйте скористатися масивами фіксованої довжини, які встановлюються під час компіляції, і подивіться, чи не голиться якийсь час відключення.
Шарпі

1
@KyleKanos @Lembik Проблема полягає в тому, що присвоєння цілого числа у fortran не використовує неявно специфікацію int64, отже, числа є int32 перед будь-яким перетворенням. Код повинен бути: integer(int64) :: b = 3141592653_int64для всіх int64. Це частина стандарту fortran і очікується програмістом мовою програмування, оголошеною типом. (Зверніть увагу , що налаштування по замовчуванням звичайно може перевизначити це)
Нульовий

69

Python 2.7 - 0.882s 0.283s

(Оригінал ОП: 6.404)

Редагувати: Оптимізація Стівена Румбальського за допомогою попереднього обчислення значень F. За допомогою цієї оптимізації cpython перемагає 0,365s 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

Оригінальний код OP використовує такі крихітні масиви, що використовувати Numpy немає користі, як це демонструє ця чиста реалізація python. Але дивіться також цю нудну реалізацію, яка знову втричі швидша, ніж мій код.

Я також оптимізую, пропускаючи решту згортки, якщо перший результат не дорівнює нулю.


11
З pypy це працює приблизно за 0,5 секунди.
Алістер Бакстон

2
Якщо ви встановите n = 10., ви отримаєте набагато переконливішу швидкість, і я отримаю 19s проти 4.6s для cpython проти pypy.

3
Іншою оптимізацією було б попередньо обчислити можливості, Fоскільки їх лише 4032. Визначте choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))зовні петлі. Потім у внутрішній петлі визначте F = random.choice(choicesF). Я отримую 3-кратну швидкість при такому підході.
Стівен Румбальський

3
Як щодо компіляції цього в Cython? Тоді додамо кілька тактовних статичних типів?
Thane Brimhall

2
Поставте все у функцію і назвіть це в кінці. Це локалізує імена, що також робить оптимізацію, запропоновану роботою @riffraff. Крім того, перемістіть створення із range(iters)циклу. Загалом я отримую швидкість приблизно на 7% за вашу дуже приємну відповідь.
WolframH

44

Іржа: 0,011с

Оригінальний Python: 8.3

Прямий переклад оригіналу 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)
    }
}
  • Укладено з --opt-level=3
  • Мій компілятор іржі - це нещодавна щоночі : ( rustc 0.11-pre-nightly (eea4909 2014-04-24 23:41:15 -0700)якщо бути точним)

Я отримав його для складання, використовуючи нічну версію іржі. Однак я думаю, що код неправильний. Вихід повинен бути чимось близьким до firstzero 27215 Bothzero 12086. Замість цього він дає 27367 6481

@ Лембік, да, змішав мої as і bs змішані в згортці; виправлено (помітно не змінює час виконання).
хун

4
Це дуже приємна демонстрація швидкості іржі.

39

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

Python 2.7.6 / Numpy 1.8.1 - 12s

Швидкість ~ x800.

Розрив був би набагато меншим, якби згорнуті масиви були дуже великими ...

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

Кілька приміток:

  • Викликається випадкова функція в циклі, тому я пішов на дуже легкий лінійний конгруентний генератор (але щедро подивився на MSB).
  • Це справді лише вихідна точка для оптимізованого рішення.
  • Не потрібно було так довго писати ...
  • Я повторюю всі значення S, приймаючи S[0]як "найменш значущу" цифру.

Додайте цю головну функцію для самостійного прикладу:

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

1
Справді. Невеликий розмір масивів у коді OP означає використання numpy насправді на порядок повільніше, ніж прямий пітон.
Алістер Бакстон

2
Тепер x800 - це те, про що я говорю!

Дуже хороша! Я збільшив швидкість роботи свого коду через вашу advanceфункцію, тому мій код зараз швидший, ніж ваш: P (але дуже хороша конкуренція!)
Kyle Kanos

1
@lembik так, як каже Мат. Вам потрібна підтримка C ++ 11 та основна функція. Дайте мені знати, чи вам потрібна додаткова допомога, щоб змусити її запуститись ...
Гай Сіртон,

2
Я лише перевірив це і міг поголити ще 20%, використовуючи звичайні масиви замість std :: vector ..
PlasmaHH

21

С

На моїй машині займає 0,015 секунди, оригінальний код OP приймає ~ 7,7. Намагався оптимізувати, генеруючи випадковий масив і об'єднуючись в одному циклі, але, схоже, це не має великої різниці.

Перший масив генерується, беручи ціле число, виписуйте його у двійковій формі та змінюйте всі 1 на -1 та всі 0 на 1. Решта має бути дуже зрозумілим.

Редагувати: замість того, щоб мати nяк ангіну int, тепер ми маємо nяк макровизначену константу, тому ми можемо використовувати int arr[n];замість malloc.

Edit2: Замість вбудованої rand()функції, тепер реалізовано PRS-модуль xorshift. Також багато умовних висловлювань видаляються при генерації випадкового масиву.

Інструкції з компіляції:

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

Код:

#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
Я це перевірив. Це дуже швидко (спробуйте n = 10) і дає правильний вигляд результатів. Дякую.

Ця реалізація не відповідає оригіналу, тому що якщо випадковий вектор буде всіма нулями, то знову буде генерований останній елемент. В оригіналі весь вектор був би. Вам потрібно вкласти цю петлю do{}while(!flag)або щось для цього. Я не сподіваюся, що це значно змінить час виконання (може зробити це швидше).
Гай Сіртон

@Guy Sirton Зверніть увагу , що перед continue;затвердженням я призначив -1до k, так kбуде цикл від 0 раз.
ace_HongKongIndependence

1
@ace ах! ти правий. Я сканував занадто швидко, і, схоже, це було, -=а не =-:-) Час циклу буде читабельнішим.
Гай Сіртон

17

J

Я не сподіваюся перемогти будь-які складені мови, і щось говорить про те, що це знадобиться чудодійній машині, щоб отримати менше 0,09 с, але я хотів би подати цей J все одно, тому що він досить гладкий.

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

Це займає приблизно 0,5 с на ноутбуці попереднього десятиліття, лише приблизно в 20 разів швидше, ніж у відповіді Python. Більшість часу витрачається на те, convщо ми пишемо це ліниво (обчислюємо всю згортку) і в повній загальності.

Оскільки ми знаємо , що про Sта Fми можемо прискорити процес , зробивши конкретні оптимізацій для цієї програми. Найкраще, що мені вдалося придумати, - conv =: ((num, num+1) { +//.)@:(*/)"1виділити конкретно два числа, що відповідають діагональній сумі до найдовших елементів згортки, що приблизно вдвічі менше часу.


6
J завжди варто подавати, людина :)
Віталій Дятлов

17

Perl - 9,3X швидше ... 830% покращення

На моїй стародавній нетбуці код ОП займає 53 секунди; Версія Алістера Бакстона займає близько 6,5 секунд, а наступна версія Perl займає близько 5,7 секунди.

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 з mkl прив’язками - 0,086s

(Оригінал ОП: 6.404) (чистий пітон Бакстона: 0,270)

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

Як зазначає Бакстон, оригінальний код OP використовує такі крихітні масиви, що використовувати Numpy немає користі. Ця реалізація використовує безліч, виконуючи всі випадки F і S одночасно, орієнтованим на масив. Це в поєднанні з mkl прив'язками для python призводить до дуже швидкої реалізації.

Зауважимо також, що лише завантаження бібліотек та запуск інтерпретатора займає 0,076 сек, тому власне обчислення займає ~ 0,01 секунди, подібно до рішення C ++.


Що таке прив'язки mkl і як їх отримати в ubuntu?

Біг python -c "import numpy; numpy.show_config()"покаже вам , якщо ваша версія NumPy скомпільовано проти Блас / атлас / мкл і т.д. ATLAS є вільним прискорюються математики пакет , який NumPy може бути пов'язаний з , Intel MKL ви зазвичай повинні платити за (якщо ви не академічна) і може бути пов'язаний з numpy / scipy .
алемі

Для легкого способу використовуйте розподілення анаконди пітона та використовуйте пакет прискорення . Або скористайтеся розподілом енергії .
алемі

Якщо ви знаходитесь у Windows, просто завантажте нумез звідси . Заздалегідь складені numpy інсталятори, пов’язані з MKL.
Підроблена назва

9

MATLAB 0,024s

Комп'ютер 1

  • Оригінальний код: ~ 3.3 с
  • Код Алістара Бакстона: ~ 0,51 с
  • Новий Код Алістара Бакстона: ~ 0,25 с
  • Код Матлаба: ~ 0,024 с (Matlab вже працює)

Комп'ютер 2

  • Оригінальний код: ~ 6.66 s
  • Код Алістара Бакстона: ~ 0,64 с
  • Новий Код Алістара Бакстона:
  • Матлаб: ~ 0,07 с (Matlab вже працює)
  • Октава: ~ 0,07 с

Я вирішив спробувати ой так повільний Matlab. Якщо ви знаєте як, ви можете позбутися більшості петель (у Matlab), що робить це досить швидко. Однак вимоги до пам'яті вищі, ніж для петельних рішень, але це не буде проблемою, якщо у вас немає дуже великих масивів ...

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

Ось що я роблю:

  • використовувати функцію Kyle Kanos для перестановки через S
  • обчислити всі n * iters випадкових чисел одразу
  • карта від 1 до 4 до [-1 0 0 1]
  • використовувати матричне множення (сума елементів (F * S (1: 5)) дорівнює матричному множенню F * S (1: 5) '
  • для Bothzero: обчислюйте лише членів, які виконують першу умову

Я припускаю, що у вас немає матлаба, що дуже погано, тому що я дійсно хотів би побачити, як він порівнює ...

(Функція може бути повільнішою при першому її запуску.)


Ну, у мене є октава, якщо ви можете змусити її працювати ...?

Я можу спробувати - хоча я ніколи не працював з октавою.
mathause

Гаразд, я можу запустити його як в октаві, якщо я покладу код у файл з назвою call_convolve_random_arrays.m, а потім викликаю його з октави.
mathause

Чи потрібен ще якийсь код, щоб насправді змусити його щось робити? Коли я роблю "octave call_convolve_random_arrays.m", він нічого не виводить. Дивіться bpaste.net/show/JPtLOCeI3aP3wc3F3aGf

вибачте, спробуйте відкрити октаву і запустити її потім. Він повинен відображати firstzero, bothzero та час виконання.
mathause

7

Юлія: 0,30 с

Op's Python: 21,36 с (дует Core2)

71х прискорення

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

Я вніс кілька змін у відповідь Джулії Армана: Перш за все, я обернув її функцією, оскільки глобальні змінні ускладнюють висновок типу Джулії та JIT: Глобальна змінна може змінити свій тип у будь-який час, і потрібно перевіряти кожну операцію . Потім я позбувся анонімних функцій і розуміння масиву. Вони насправді не потрібні і все ще досить повільні. Юлія швидша з абстракціями нижчого рівня.

Є набагато більше способів зробити це швидше, але це робить гідну роботу.


Ви вимірюєте час у REPL або запускаєте весь файл із командного рядка?
Адітя

обидва від REPL.
користувач20768

6

Гаразд, я публікую це лише тому, що мені здається, що тут потрібно представити Java. Мені страшно з іншими мовами, і, зізнаюся, точно не розумію проблеми, тому мені знадобиться допомога, щоб виправити цей код. Я вкрав більшу частину прикладу С-коду з кодом, а потім позичив деякі фрагменти з інших. Я сподіваюся, що це не штучний па ...

Хочеться зазначити одне, що мови, які оптимізуються під час виконання, потрібно виконувати кілька / багато разів, щоб отримати повну швидкість. Я думаю, що виправдано брати повністю оптимізовану швидкість (або принаймні середню швидкість), тому що більшість речей, якими ви переймаєтеся швидким бігом, буде працювати багато разів.

Код все-таки потрібно виправити, але я все одно запускав його, щоб побачити, в який час я отримаю.

Ось результати на процесорі Intel (R) Xeon (R) E3-1270 V2 @ 3,50 ГГц на Ubuntu, який працює в 1000 разів:

сервер: / tmp # час java8 -cp. Тестер

firstzero 40000

obozero 20000

час першого запуску: 41 мс, час останнього виконання: 4 мс

реальний користувач 0m5.014s 0m4.664s sys 0m0.268s

Ось мій хитрий код:

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

І я спробував запустити код python після оновлення python та встановлення python-numpy, але я отримую це:

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'

Коментарі: Ніколи не використовуйте currentTimeMillisдля бенчмаркінгу (використовуйте нано-версію в Системі), а 1-х прогонів може бути недостатньо для залучення JIT (за замовчуванням буде 1,5k для клієнта і 10k для сервера, хоча ви називаєте myRand досить часто, що це буде JITed, що повинно спричинити компіляцію деяких функцій на стадії виклику, яка може працювати тут). Останнім, але не в останню чергу, слабкий PNRG обманює, але це стосується рішення C ++ та інших, тому я думаю, це не надто несправедливо.
Во

У Windows потрібно уникати currentTimeMillis, але для Linux для всіх, але дуже тонких вимірювань деталей вам не потрібно нано-часу, а виклик отримати нано-час набагато дорожче, ніж міліс. Тому я дуже не погоджуюся з тим, що НІКОЛИ не використовуйте його.
Кріс Селін

Отже, ви пишете код Java для однієї конкретної ОС та JVM-реалізації? Насправді я не впевнений, яку ОС ви використовуєте, тому що я щойно перевірив своє дерево розробників HotSpot і Linux використовує gettimeofday(&time, NULL)протягом milliSeconds, що не є монотонним і не дає жодних гарантій точності (тому на деяких платформах / ядрах точно так само проблеми, як поточна реалізація WindowsTimeMillis - так що і штрафу теж немає або немає). nanoTime, з іншого боку, використовує, clock_gettime(CLOCK_MONOTONIC, &tp)що, очевидно, також є правильним для використання при бенчмаркінгу в Linux.
Во

Це ніколи не викликало проблем для мене, оскільки я кодував Java на будь-якому дистрибутиві чи ядрі Linux.
Кріс Селін

6

Golang версія 45X python на моїй машині нижче кодів 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)
}

і нижче копійовані пітони, скопійовані зверху:

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

і час нижче:

$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
Ви думали про використання "github.com/yanatan16/itertools"? Ви також сказали б, що це буде добре працювати в кількох процедурах?
ymg

5

С # 0,135с

C # на основі простого пітона Алістера Бакстона : 0.278s
Паралелізований C #: 0.135s
Python з питання: 5.907s
Простий пітон Alistair: 0.853s

Я фактично не впевнений, що ця реалізація правильна - її результат відрізняється, якщо дивитись на результати внизу.

Звичайно, є оптимальніші алгоритми. Я щойно вирішив використовувати дуже схожий алгоритм з Python.

Однопоточний C

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

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

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

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

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

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

Паралель C #:

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

Тестовий вихід:

Windows (.NET)

C # набагато швидше в Windows. Можливо, тому що .NET швидше, ніж моно.

Час користувача та sys, здається, не працює (використовується git bashдля встановлення часу).

$ 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 (моно)

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
Я не думаю, що код є правильним, як ви говорите. Виходи неправильні.

@ Лембік Так. Буду вдячний, якщо хтось міг би сказати мені, де це неправильно - я не можу це зрозуміти (лише мінімальне розуміння того, що він повинен робити, не допомагає).
Боб

Було б цікаво подивитися, як це відбувається з .NET Native blogs.msdn.com/b/dotnet/archive/2014/04/02/…
Rick Minerich

@Lembik Я щойно переглянув усе це, наскільки я можу сказати, він повинен бути тотожним іншим рішенням Python ... тепер я дуже розгублений.
Боб

4

Haskell: ~ 2000x прискорення на ядро

Компілюйте з 'ghc -O3 -funbox-strict-polja -threaded -fllvm' та запустіть з '+ RTS -Nk', де k - кількість ядер на вашій машині.

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
Так що з 4 ядрами це понад 9000 ?! Не може бути правильним.
Cees Timmerman

Закон Амдала стверджує, що швидкість паралелізації не є лінійною до кількості паралельних одиниць обробки. натомість вони забезпечують лише зменшення прибутку
xaedes

@xaedes Прискорення здається лінійним для низької кількості ядер
user1502040

3

Рубін

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

нитка не буде повною без PHP

6.6x швидше

PHP v5.5.9 - 1.223 0.646 сек;

проти

Python v2.7.6 - 8.072 сек

<?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";
  • Використовується користувацький випадковий генератор (викрадений з відповіді C), PHP один смокче, а цифри не відповідають
  • convolve функція трохи спростилася, щоб бути швидшою
  • Перевірка лише масиву з нулями також дуже оптимізована (див. $FІ $FSперевірки).

Виходи:

$ 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

Редагувати. Друга версія сценарію працює лише для 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 # рішення

Тривалість виконання становить 0,030 секунд при компіляції до x86 на CLR Core i7 4 (8) при 3,4 ГГц

Я поняття не маю, чи правильний код.

  • Функціональна оптимізація (вбудована складка) -> 0,026s
  • Будівництво за допомогою консольного проекту -> 0,022 с
  • Додано кращий алгоритм для генерації перестановочних масивів -> 0,018s
  • Моно для Windows -> 0,089s
  • Запуск сценарію Python Alistair -> 0,259s
let inline ffoldi n f state =
    let mutable state = state
    for i = 0 to n - 1 do
        state <- f state i
    state

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

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

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

let n = 6
let iters = 1000

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

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

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

printfn "firstzero %i" firstzero
printfn "bothzero %i" bothzero

2

Q, 0,296 с

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 - мова, орієнтована на колекцію (kx.com)

Код переписаний для використання ідіоматичного Q, але ніяких інших розумних оптимізацій немає

Мови сценаріїв оптимізують час програміста, а не час виконання

  • Q - не найкращий інструмент для вирішення цієї проблеми

Перша спроба кодування = не переможець, але розумний час (приблизно 30-кратне прискорення)

  • досить конкурентоспроможний серед перекладачів
  • зупиніться та оберіть іншу проблему

ПРИМІТКИ.-

  • Програма використовує насінні за замовчуванням (повторювані виконавці) Для вибору іншого насіння для випадкового використання генератора \S seed
  • Результат дається як складений з двох ints, тому є остаточний i-суфікс у другому значенні 27421 12133i -> read as (27241, 12133)
  • Не враховуючи час запуску перекладача. \t sentence повідомляє час, витрачений цим реченням

Дуже цікаво дякую.

1

Юлія: 12.149 6.929 с

Незважаючи на їхні претензії на швидкість , початковий час складання JIT стримує нас!

Зауважте, що наступний код Julia - це фактично прямий переклад оригінального коду Python (оптимізації не робиться) як демонстрація того, що ви можете легко перенести свій досвід програмування на більш швидку мову;)

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)

Редагувати

Біг з n = 8займає 32.935 с. Враховуючи, що складність цього алгоритму полягає в O(2^n)тому 4 * (12.149 - C) = (32.935 - C), де Cє константа, що представляє час компіляції JIT. Вирішуючи для цього Cми знаходимо, що C = 5.2203, припускаючи, що фактичний час виконання n = 6- 6.929 с.


Як щодо збільшення n до 8, щоб побачити, чи не стане Юлія тоді?

Це ігнорує багато порад щодо ефективності тут: julia.readthedocs.org/en/latest/manual/performance-tips . Дивіться також інший запис Юлії, який суттєво кращий. Подання
вдячне,

0

Іржа, 6,6 мс, швидкість 1950x

Насправді прямий переклад коду Алістера Бакстона на Руст. Я розглядав можливість використання декількох ядер з районом (безстрашна паралельність!), Але це не покращило продуктивність, напевно, тому що це вже дуже швидко.

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

І Cargo.toml, оскільки я використовую зовнішні залежності:

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

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

Порівняння швидкості:

$ 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 нс приблизно 6,6 мс. Це означає, що швидкість у 1950 разів. Тут можливе багато оптимізацій, але я йшов швидше для читання, а не для продуктивності. Однією з можливих оптимізацій було б використання масивів замість векторів для зберігання варіантів, оскільки вони завжди матимуть nелементи. Можливо також використовувати RNG, крім XorShift, оскільки, хоча Xorshift швидше, ніж HC-128 CSPRNG за замовчуванням, він найбільш повільний, ніж наївніший з алгоритмів PRNG.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.