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)

特定の確率分布で、一方が他方よりも長い2つのランダム配列の畳み込みが、最初の位置に0を持つか、両方の位置に0を持つ回数をカウントしています。

Pythonはコードを書くのにひどい言語であり、高速である必要があるという友人との賭けをしました。私のコンピューターでは9秒かかります。彼は、「適切な言語」で記述すれば、100倍高速化できると言います。

課題は、このコードが実際に選択した言語で100倍高速化できるかどうかを確認することです。あなたのコードをテストし、今から1週間で最速のものが勝ちます。誰かが0.09秒を下回ると、自動的に勝ち、負けます。

状態

  • Pythonの。Alistair Buxonにより30倍のスピードアップ!最速のソリューションではありませんが、実際には私のお気に入りです。
  • オクターブ。@Thethosにより100倍高速化。
  • 。@dbauppにより500倍高速化。
  • C ++。Guy Sirtonによって570倍のスピードアップ。
  • C。@aceによって727倍に高速化されます。
  • C ++。@Stefanによる信じられないほど速い。

最速のソリューションは、あまりにも高速であるため、十分に時間をかけることができません。したがって、nを10に増やし、iters = 100000に設定して、最良のものを比較しました。この測定では、最速は次のとおりです。

  • C。7.5秒@ace。
  • C ++。@Stefanによる1。

私のマシンタイミングは私のマシンで実行されます。これは、AMD FX-8350 8コアプロセッサへの標準のUbuntuインストールです。これは、コードを実行できる必要があることも意味します。

フォローアップの投稿このコンペティションはx100のスピードアップを得るにはあまりにも簡単だったので、スピードの達人の専門知識を行使したい人のためにフォローアップを投稿しました。Pythonの実際の速度(パートII)をご覧ください

回答:


61

C ++ビットマジック

シンプルなRNGで0.84ms、c ++ 11 std :: knuthで1.67ms

わずかなアルゴリズム変更を伴う0.16ms(以下の編集を参照)

Pythonの実装は、リグで7.97秒で実行されます。そのため、選択するRNGに応じて9488〜4772倍の速度になります。

#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()のループが実行され、すべての変数がレジスターに格納されます。

仕組み:なく格納よりSFインメモリアレイとして、それのuint32_tのビットとして格納されます。
の場合Sn最下位ビットが使用され、セットビットは+1を示し、未設定ビットは-1を示します。
F[-1、0、0、1]の分布を作成するには、少なくとも2ビットが必要です。これは、ランダムビットを生成し、16 r個の最下位ビット(と呼ばれる)と16個の最上位ビット(と呼ばれるl)を調べることで行われます。た場合l & ~r、我々はFが1であると仮定した場合、~l & r我々は仮定し、それはF-1です。それ以外の場合Fは0です。これにより、探している分布が生成されます。

今、私たちは持っているSposBitsF == 1とすべての場所に設定されたビットとnegBitsFの== -1すべての場所に設定されたビットと。

F * S条件の下で(*は乗算を表す)が+1に評価されることを証明できます(S & posBits) | (~S & negBits)。またF * S、-1と評価されるすべてのケースに対して同様のロジックを生成できます。そして最後sum(F * S)に、結果に等しい量の-1と+1がある場合にのみ、0に評価されることを知っています。これは、+ 1ビットと-1ビットの数を比較するだけで簡単に計算できます。

この実装は32ビットintを使用し、n受け入れられる最大値は16です。ランダム生成コードを変更することで31ビットに、uint32_tの代わりにuint64_tを使用して63ビットに拡張することができます。

編集する

次の畳み込み関数:

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.161msにカットします。手動ループアンロール(上図にはありません)により、0.150になります。それほど重要ではないn = 10、iter = 100000のケースは250ms未満で実行されます。追加のコアを活用することで50ミリ秒未満に到達できると確信していますが、それはあまりにも簡単です。

これは、内側のループ分岐をフリーにし、FループとSループを交換することで実行されます。必要ない
場合bothZeroは、可能性のあるすべてのS配列をまばらにループすることにより、実行時間を0.02msに短縮できます。


3
gccに対応したバージョンを提供してもらえますか?また、コマンドラインはどうなりますか?現在テストできるかどうかはわかりません。

これについては何も知りませんが、グーグルは__builtin_popcountが_mm_popcnt_u32()の代わりになるかもしれないと言っています。

3
コードが更新され、#ifdefスイッチを使用して正しいpopcntコマンドを選択します。-std=c++0x -mpopcnt -O232ビットモードで実行するには1.01msでコンパイルします(手元に64ビットGCCバージョンはありません)。
ステファン

出力を印刷することはできますか?現在実際に何かを行っているかどうかは

7
あなたは明らかにウィザードです。+ 1
BurntPizza

76

Python2.7 + Numpy 1.8.1:10.242秒

Fortran 90+:0.029秒 0.003秒 0.022秒 0.010秒

くそっとあなたは賭けを失いました!ここでも並列化の低下ではなく、単なるFortran 90+です。

編集私は配列を並べ替えるためにガイ・サートンのアルゴリズムを取りました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

編集2:実行時間の減少は、私が以前何か間違ったことをしていて、間違った答えを得たためです。正しい方法で実行すると、明らかに遅くなります。私はまだC ++が私のものよりも速いとは信じられないので、今週中に少し時間をかけて、これを高速化するためにこのくだらないものを微調整するつもりです。

編集3BSDのRNG(Sampo Smolanderが示唆する)に基づくセクションを使用してRNGセクションを変更し、一定の除算を削除することによりm1、実行時間をGuy SirtonによるC ++の回答と同じにカットしました。静的配列を使用すると(Sharpieの提案どおり)、ランタイムがC ++ランタイムの下に低下します。イェイFortran!:D

EDIT 4整数が制限を超えているため、これは(gfortranを使用して)コンパイルおよび正しく実行されません(正しくない値)。動作するように修正しましたが、これにはifort 11+またはgfortran 4.7+(またはiso_fortran_envF2008のint64種類を許可する別のコンパイラ)が必要です。

コードは次のとおりです。

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
とにかく、caseステートメントはジェネレーター関数よりも高速ではないでしょうか?何らかの分岐予測/キャッシュライン/などの高速化を期待している場合を除きますか?
OrangeDog 14

17
同じマシンで速度を比較する必要があります。OPのコードに対してどのランタイムを取得しましたか?
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

(OPのオリジナル:6.404秒)

編集: Steven RumbalskiによるF値の事前計算による最適化。この最適化により、cpythonはpypyの0.365秒を上回ります。

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の元のコードはこのような小さな配列を使用しているため、この純粋なpython実装が示すように、Numpyを使用する利点はありません。しかし、このnumpy実装も参照してください。これは、私のコードよりも3倍高速です。

また、最初の結果がゼロでない場合、残りの畳み込みをスキップして最適化します。


11
pypyでは、これは約0.5秒で実行されます。
アリスタバクストン14

2
n = 10に設定すると、さらに説得力のある高速化が得られます。cpython対pypyで19秒対4.6秒になります。

3
別の最適化は、可能性を事前に計算するFことです。なぜなら、そのうちの4032しかないからです。choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))ループの外側で定義します。次に、innerloopで定義しますF = random.choice(choicesF)。このようなアプローチにより、3倍のスピードアップが得られます。
スティーブンランバルスキー14

3
Cythonでこれをコンパイルしてみませんか?次に、いくつかの巧妙な静的型を追加しますか?
ターネブリムホール14

2
関数にすべてを入れて、最後に呼び出します。これにより名前がローカライズされ、@ riffraffによって提案された最適化も機能します。また、range(iters)ループの作成を移動します。全体として、私はあなたの非常に良い答えに対して約7%のスピードアップを得ることができます。
WolframH 14年

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)正確には)

夜間バージョンのrustを使用してコンパイルしました。しかし、コードが間違っていると思います。出力は、firstzero 27215 bothzero 12086に近いものである必要があります。代わりに、27367 6481

@Lembik、おっと、私の持っa秒とb畳み込みで混ざっ秒; 修正済み(ランタイムを顕著に変更しません)。
フオン14

4
さびの速度の非常に良いデモンストレーションです。

39

C ++(VS 2012)-0.0260.015

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をgeneしげなく見ました)。
  • これは本当に最適化されたソリューションの出発点にすぎません。
  • 書くのにそんなに時間がかからなかった...
  • 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を使用することは実際には、Pythonのストレートよりも1桁遅いことを意味します。
アリステアバクストン14

2
今私が話しているのはx800です!

非常に素晴らしい!私はあなたのadvance関数のためにコードの高速化を加速しました。それで私のコードはあなたのものよりも速くなりました:P(しかし非常に良い競争です!)
カイルカノス14

1
マットが言うように@lembikはい。C ++ 11 supprtとメイン関数が必要です。...あなたはこれを実行するために取得するにはより多くの助けが必要な場合は、私に教えてください
ガイSirton

2
私はこれをテストして、代わりにSTDのプレーンな配列を使用して::ベクトルを別の20%を剃ることができ...
PlasmaHH

21

C

私のマシンでは0.015秒かかりますが、OPの元のコードは7.7秒かかります。ランダム配列を生成して同じループ内で畳み込むことで最適化を試みましたが、大きな違いはないようです。

最初の配列は、整数を取得してバイナリで書き出し、すべて1を-1に、すべてを0から1に変更することで生成されます。残りは非常に簡単です。

編集:代わりに持つのnint、今、我々はn、マクロ定義の定数ので、我々が使用できるようint arr[n];するのではなくmalloc

Edit2:組み込みrand()関数の代わりに、これはxorshift PRNGを実装するようになりました。また、ランダム配列を生成するときに多くの条件ステートメントが削除されます。

コンパイル手順:

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)またはその効果の何かを囲む必要があります。私はそれが実行時間を大きく変えるとは思わない(より速くなるかもしれない)。
ガイサートン14

前にあることに注意してくださいSirton @Guy continue;声明私が割り当てられた-1k、そのk0からのループは再びます。
ace_HongKongIndependence 14

1
@エース!あなたが正しい。スキャンが速すぎた-=ため、=-:-) whileループの方が読みやすくなりました。
ガイサートン14

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

これには、過去10年間のラップトップで約0.5秒かかり、答えのPythonの約20倍の速さしかありません。ほとんどの時間はconv、レイジーに(畳み込み全体を計算して)記述し、完全に一般化しているために費やされます。

我々はについての事を知っているのでSそしてF、私たちは、このプログラムのための特定の最適化を行うことで、物事をスピードアップすることができます。私が思いついたのはconv =: ((num, num+1) { +//.)@:(*/)"1、対角線の合計から畳み込みの最も長い要素に対応する2つの数値を具体的に選択することです。これは時間をほぼ半分にします。


6
Jは:)、常に男提出する価値がある
ヴィタリーDyatlov

17

Perl-9.3倍高速化... 830%の改善

私の古代のネットブックでは、OPのコードの実行に53秒かかりました。Alistair Buxtonのバージョンには約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-mklバインディングを使用したnumpy 1.8.1-0.086s

(OPのオリジナル:6.404秒)(バクストンの純粋なpython: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のケースを一度に実行することにより、numpyを活用します。これは、Pythonのmklバインディングと組み合わされて、非常に高速な実装につながります。

また、ライブラリをロードしてインタプリタを起動するだけで0.076秒かかるため、実際の計算にはC ++ソリューションと同様に約0.01秒かかることに注意してください。


mklバインディングとは何ですか?また、ubuntuでそれらを取得するにはどうすればよいですか?

ランニングはpython -c "import numpy; numpy.show_config()"numpyののバージョンは、BLAS /アトラス/ MKLに対してコンパイルされている場合など、あなたが表示されます ATLASは numpyのは自由加速数学のパッケージであるに対してリンクすることができインテルMKLあなたは通常、(あなたがアカデミックでない限り)を支払う必要がありますそして、numpyの/ scipyのダウンロードにリンクすることができます
アレミ14

簡単な方法として、anaconda pythonディストリビューションを使用して、accelerateパッケージを使用してください。または、専用のディストリビューションを使用します。
アレミ14

Windowsを使用している場合は、ここからnumpyをダウンロードしてください。MKLにリンクされたプリコンパイル済みのnumpyインストーラー。
偽の名前

9

MATLAB 0.024s

コンピューター1

  • 元のコード:〜3.3秒
  • アリスターバクストンのコード:〜0.51 s
  • アリスターバクストンの新しいコード:〜0.25秒
  • Matlabコード:〜0.024秒(Matlabは既に実行中)

コンピューター2

  • 元のコード:〜6.66 s
  • アリスターバクストンのコード:〜0.64 s
  • アリスターバクストンの新しいコード:?
  • Matlab:〜0.07 s(Matlabは既に実行中)
  • オクターブ:〜0.07 s

とても遅い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

ここに私がやることがあります:

  • カイルカノス関数を使用してSを並べ替える
  • すべてのn *乱数を一度に計算します
  • 1〜4を[-1 0 0 1]にマップします
  • 行列の乗算を使用する(要素ごとの合計(F * S(1:5))は、F * S(1:5) 'の行列の乗算に等しい
  • bothzero:最初の条件を完全に満たすメンバーのみを計算します

私はあなたがmatlabを持っていないと仮定します、それは私が本当にそれを比較する方法を見たいと思っていたのであまりにも悪いです...

(この関数は、初めて実行するときに遅くなる可能性があります。)


あなたがそれのためにそれを動作させることができるなら、私はオクターブを持っています...

私はそれを試してみることができます-私はオクターブで働いたことはありませんが。
マトハウス14

OK、call_convolve_random_arrays.mという名前のファイルにコードを入れて、オクターブから呼び出すと、そのままオクターブで実行できます。
マトハウス14

実際に何かをさせるには、さらにコードが必要ですか?「octave call_convolve_random_arrays.m」を実行しても、何も出力されません。参照してくださいbpaste.net/show/JPtLOCeI3aP3wc3F3aGf

申し訳ありませんが、オクターブを開いて実行してみてください。firstzero、bothzero、およびexecution timeが表示されます。
マトハウス14

7

ジュリア:0.30秒

Opの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で時間を測定していますか、またはコマンドラインからファイル全体を実行していますか?
アディティア14

両方ともREPLから。
user20768 14

6

ここでJavaを表現する必要があると感じたからといって、これを投稿しています。私は他の言語には恐ろしく、問題を正確に理解していないと告白しているので、このコードを修正するのに助けが必要です。私はコードエースのCの例の大部分を盗み、他のスニペットをいくつか借りました。私はそれが偽物ではないことを願っています...

私が指摘したいことの1つは、実行時に最適化する言語をフルスピードにするには、数回/複数回実行する必要があるということです。完全に最適化された速度(または少なくとも平均速度)を取ることは正当であると思います。なぜなら、高速で実行することに関心を持つほとんどのことは、何度も実行されるからです。

コードはまだ修正する必要がありますが、とにかく実行して、何時になるかを確認します。

以下は、UbuntuでIntel(R)Xeon(R)CPU E3-1270 V2 @ 3.50GHzを1000回実行した結果です。

server:/ tmp#time java8 -cp。テスター

firstzero 40000

bothzero 20000

最初の実行時間:41 ms最後の実行時間:4 ms

実数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-numpyをインストールした後にpythonコードを実行しようとしましたが、私はこれを取得します:

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(システムでnanoバージョンを使用する)と、1kの実行ではJITを関与させるのに十分でない可能性があります(クライアントでは1.5k、サーバーでは10kがデフォルトになりますが、myRandを呼び出すことはよくありますが、 JITにより、コールスタックの一部の関数がコンパイルされ、ここで機能する可能性があります)最後になりましたが、弱いPNRGが不正行為を行っていますが、C ++ソリューションなども不正行為を行っているため、それは不公平ではないと思います。
Voo

WindowsではcurrentTimeMillisを回避する必要がありますが、Linuxの場合、非常に細かい粒度測定を除くすべての場合、ナノ時間は不要であり、ナノ時間を取得するための呼び出しはミリ秒よりもはるかに高価です。したがって、絶対に使用しないでください。
クリスセリーヌ

それでは、特定の1つのOSおよびJVM実装用のJavaコードを書いていますか?実際、私はどの OSを使用gettimeofday(&time, NULL)しているかわかりません。HotSpotの開発ツリーをチェックしたばかりで、LinuxはmilliSecondsを使用していますが、これは単調ではなく、正確性を保証しません(一部のプラットフォーム/カーネルではまったく同じです) currentTimeMillis Windows実装としての問題-したがって、それでも問題ないか、どちらでもありません)。一方、nanoTimeは、clock_gettime(CLOCK_MONOTONIC, &tp)Linuxでベンチマークを行う際に使用するのが正しいことを明らかに使用しています。
VOO

LinuxのディストリビューションまたはカーネルでJavaをコーディングしてきたので、問題が発生したことはありません。
クリスセリーネ

6

Golangコードの下の私のマシンのGolangバージョン45XのPython:

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

上記からコピーされた以下のPythonコード:

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 14

5

C#0.135s

Alistair Buxtonのプレーンpythonに基づくC#:0.278s
並列化されたC#:
質問からの0.135s Python:5.907s
Alistairのプレーンpython: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はモノラルよりも高速だからです。

ユーザーとシステムのタイミングが機能していないようです(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
あなたが言うように、私はコードが正しいとは思わない。出力は正しくありません。

@Lembikうん。それは間違っているところ誰かが私に言うことができる場合でも、私はそれを感謝したい-私は(だけで、何の最小限の理解持つそれを把握することができないはず行うにしても解決しません)。
ボブ14


@Lembik他のPythonソリューションと同一であると言える限り、すべてを調べました...今、私は本当に混乱しています。
ボブ14

4

Haskell:コアあたり最大2000倍の高速化

'ghc -O3 -funbox-strict-fields -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以上ですか?!正しい方法はありません。
シーズティマーマン

アムダールの法則によれば、並列化の高速化は並列処理ユニットの数に対して線形ではありません。代わりに、それらは減少するリターンのみを提供します
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.6倍高速

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の回答から盗まれた)を使用しました
  • convolve 関数を少し簡略化してより高速にした
  • array-with-zeros-onlyのチェックも非常に最適化されています(参照$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

編集。スクリプトの2番目のバージョンは、次の場合にのみ有効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#ソリューション

CLR Core i7 4(8)@ 3.4 Ghzでx86にコンパイルしたときのランタイムは0.030秒

コードが正しいかどうかはわかりません。

  • 機能の最適化(インラインフォールド)-> 0.026s
  • コンソールプロジェクトを使用したビルド-> 0.022s
  • 順列配列の生成のためのより良いアルゴリズムを追加-> 0.018s
  • Windows用モノラル-> 0.089s
  • AlistairのPythonスクリプトの実行-> 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倍の高速化)

  • 通訳者の間で非常に競争力のある
  • 停止して別の問題を選択します

ノート。-

  • プログラムはデフォルトのシード(繰り返し可能なexec)を使用します \S seed
  • 結果は2つの整数のシーケンスとして与えられるため、2番目の値27421 12133iに最後のiサフィックスがあります->(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)、ここでCJITコンパイル時間を表す定数です。を解くと、Cが見つかり、のC = 5.2203実際の実行時間n = 6が6.929秒であることがわかります。


nを8に増やして、Juliaが自分自身になったかどうかを確認してください。

ここでは、julia.readthedocs.org / en / latest / manual/ performance-tipsのパフォーマンスに関するヒントの多くを無視しています。他のJuliaエントリも参照してください。しかし、提出は高く評価されています:
StefanKarpinski 14

0

錆、6.6 ms、1950倍の高速化

Alistair BuxtonのコードをRustに直接変換したものです。レーヨン複数のコアを使用することを検討しました(大胆不敵な並行性!)が、おそらく既に非常に高速であるため、これはパフォーマンスを改善しませんでした。

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 nsは約6.6ミリ秒です。これは、1950倍の高速化を意味します。ここでは多くの最適化が可能ですが、パフォーマンスよりも読みやすさを重視していました。可能な最適化の1つは、選択肢を格納するためにベクトルではなく配列を使用することnです。これは、常に要素があるためです。XorshiftはデフォルトのHC-128 CSPRNGより高速ですが、PRNGアルゴリズムのナイベストよりも遅いため、XorShift以外のRNGを使用することもできます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.