Python Gerçekten Ne Kadar Yavaş mı (Bölüm II)?


52

Python gerçekte ne kadar yavaş? (Veya diliniz ne kadar hızlı?) .

Son soruma x100 hız kazandırmak biraz zor oldu. Bu zorluğun tadını çıkaran ancak düşük seviye becerilerini gerçekten kullanabilecekleri daha zor bir şey isteyenler için, işte bölüm II. Buradaki zorluk bilgisayarımda test edildiği şekilde aşağıdaki python kodu için x100 hızlandırması sağlamak.

Daha da zorlaştırmak için bu sefer pypy kullanıyorum. Benim için şimdiki zamanlama pypy 2.2.1 kullanılarak 1 dakika ve 7 saniyedir .

kurallar

  1. Çalıştırabileceğim, kodumu gönderdiğim ilk kişi doğru ve makinemde x100 kat daha hızlı olduğu için 50 puan ödül kazanacak.
  2. Bir hafta sonra kazananı en hızlı koda vereceğim.
import itertools 
import operator 
import random

n = 8 
m  = 8 
iters = 1000  

# creates an array of 0s with length m
# [0, 0, 0, 0, 0, 0, 0, 0]
leadingzerocounts = [0]*m

# itertools.product creates an array of all possible combinations of the 
# args passed to it.
#
# Ex:
#   itertools.product("ABCD", "xy") --> Ax Ay Bx By Cx Cy Dx Dy
#   itertools.product("AB", repeat=5) --> [
#    ('A', 'A', 'A', 'A', 'A'),
#    ('A', 'A', 'A', 'A', 'B'),
#    ('A', 'A', 'A', 'B', 'A'),
#    ('A', 'A', 'A', 'B', 'B'),
#    etc.
#   ]
for S in itertools.product([-1,1], repeat = n+m-1):
    for i in xrange(iters):
        F = [random.choice([-1,0,0,1]) for j in xrange(n)]

        # if the array is made up of only zeros keep recreating it until
        # there is at least one nonzero value.
        while not any(F):
            F = [random.choice([-1,0,0,1]) for j in xrange(n)]

        j = 0
        while (j < m and sum(map(operator.mul, F, S[j:j+n])) == 0):
            leadingzerocounts[j] +=1
            j += 1
print leadingzerocounts

Çıktı benzer olmalıdır

[6335185, 2526840, 1041967, 439735, 193391, 87083, 40635, 19694]

Kodunuzda rastgele bir tohum kullanmalısınız ve yukarıdakilere yakın cevaplar verebilecek kadar iyi olan herhangi bir rasgele sayı üreteci kabul edilecektir.

Benim Makine zamanlamaları benim makinede işletilecek. Bu, AMD FX-8350 Sekiz Çekirdekli İşlemciye standart bir ubuntu kurulumudur. Bu ayrıca kodunuzu çalıştırabilmem gerektiği anlamına geliyor.

Kod açıklaması

Bu kod, -1s ve 1s için oluşturulan tüm uzunlukları n + m-1 S dizilerini yineler. Her S dizisi için, her değeri alarak 1/4, 1/2, / 14 olasılık ile -1,0 veya 1 den oluşan 1000 sıfır sıfır olmayan rastgele diziyi F uzunluğu -1. Daha sonra, F ile sıfır olmayan bir iç ürün bulana kadar n uzunluktaki her bir pencere arasındaki F iç değerini hesaplar. leadingzerocountsSıfır bir iç ürün bulduğu her pozisyona 1 ila 1 ekler .

durum

  • Perl . @ Tobyink tarafından 2,7 kat yavaşlama. (Pypy ile karşılaştırıldığında cpython değil.)

  • J . @Eelvex tarafından 39 kez hızlandırılmış.

  • C . @Ace ile 59 kat daha hızlı.
  • Julia . 197 kat daha hızlı, başlama zamanı dahil değil, bir dakika daha. Başlangıç ​​zamanı dahil 8,5 kat hızlanma (bu durumda 8'den 4 işlemci kullanmak daha hızlıdır).
  • Fortran . 438 kat @ yarı-dışlayıcı tarafından hızlandırılır.
  • Rpython . 258 kat @primo tarafından hızlandırılmış.
  • C ++ . @İlmale tarafından 508 kat daha hızlı.

(Yeni geliştirmeleri zamanlamayı bıraktım çünkü çok hızlılar ve çok küçüktü.)


Bir saniyenin altındaki zamanlamaların güvenilir olmadığı ve ayrıca bazı dillerin başlangıç ​​maliyeti olduğu belirtildi. Argüman, C / C ++ vb. Derleme zamanını da eklemeniz gerektiğidir. İşte en hızlı kodun zamanlamaları, yineleme sayısı 100.000'e yükselmiştir.

  • Julia . Dakikada 42 saniye @.
  • C ++ . @GuySirton tarafından 14 saniye.
  • Fortran . 14 s @ @ yarı ekstrinsiktir.
  • C ++ . 12s, @ilmale tarafından.
  • Rpython . 18s (@primo) Tarafından Çekilen En Yeni Fotoğrafları İncele
  • C ++ . @Stefan tarafından 5s.

Kazanan .. Stefan!

Takip zorluğu kaydedildi. Ne kadar yükseğe gidebilirsin? (Bir kodlama + algoritmalar mücadelesi) . Bu daha zor.


3
Kodun neyi başarması gerektiğinin bir açıklaması iyi olurdu, bu yüzden tekrar yazabiliriz ve basitçe
gösterebiliriz

6
Çalıştırabileceğim, kodumu gönderdiğim ilk kişi doğru ve makinemde x100 kat daha hızlı kazanıyor ve rekabet kapanıyor. ” Yarışmayı böyle kapatmanın amacı nedir? Neden diğerleri gibi bir tarih son tarihi kullanmıyoruz, bu yüzden diğer dillerde daha da azaldığını görebiliriz?
grovesNL

5
@Einacio Bu güzel bir fikir. Kimsenin umursamayacağını umduğum kuralları değiştirdim.

1
@Lembik Fortran versiyonumu geliştirdim, makinemde 2 kat daha hızlı hale getirdim. Tekrar zaman verebilir misin? :)
yarı extrinsic

1
@ yarı ekstrinsik Done.

Yanıtlar:


12

C ++ bit büyüsü

~ 16ms okuyuculu, 56ms okuyuculu. 4000 hızlanma.

(hızlanma, i7-2820QM'mde çok iş parçacığı koduna ve soruda belirtilen 1 dak. 9 saniyeye dayanıyor. OP'nin sistemi işlemcimden daha fazla tek dişli performansa sahip olduğundan, daha iyi çoklu dişli performansının bu rakamın doğru olmasını bekliyorum)

Çok iş parçacıklı parça, dişlerin ortaya çıkması nedeniyle oldukça verimsizdir. Özel iş kütüphanemi kullanarak muhtemelen daha iyisini yapabilirdim, ancak bunun unix sistemleri altında hataları var. Konuya ilişkin bir açıklama ve neredeyse aynı kod için https://codegolf.stackexchange.com/a/26485/20965 adresini ziyaret edin .

Düzenle

Her iş parçasına kendi RNG'sini verdim ve bit uzunluğunu 32'ye düşürdüm, bu da çalışma süresini birkaç ms azalttı.

#include <iostream>
#include <bitset>
#include <random>
#include <chrono>
#include <stdint.h>
#include <cassert>
#include <array>
#include <tuple>
#include <memory>
#include <thread>
#include <future>
#include <string.h>


#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



void convolve()
{
    static const unsigned threadCount = 32;
    static const unsigned n = 8;
    static const unsigned m = 8;
    static const unsigned totalIters = 1000;
    static_assert( n <= 16, "packing of F fails when n > 16.");
    static uint32_t fmask = (1 << n) -1; fmask |= fmask << 16;

    std::array< uint32_t, m * threadCount > out;
    std::vector< std::future<void> > threads;

    for( int threadId = 0; threadId < threadCount; threadId++)
    {
        threads.emplace_back( std::async( [&, threadId]
        {
            std::random_device rd;
            std::knuth_b gen(rd());
            uint32_t nextRandomNumber = gen();

            const unsigned iters = totalIters / threadCount;

            std::array< uint32_t, m > leadingZeros;
            for( auto& x : leadingZeros )
                x = 0;

            for( unsigned i = 0; i < iters; i++ )
            {
                // generate random bit mess
                uint32_t F;
                do {
                    // this funky looking construction shortens the dependancy chain of F
                    F = nextRandomNumber & fmask;
                    nextRandomNumber = gen();
                } 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 + m -1));
                // generate all possible N+1 bit strings
                // 1 = +1
                // 0 = -1
                while ( S-- )
                {
                    for( int shift = 0; shift < m; shift++ )
                    {
                        uint32_t s = (S >> shift) % ( 1 << n );
                        auto firstBits = (s & mask) ^ adjF;

                        if ( desiredBits == popcnt( firstBits ) )
                        {
                            leadingZeros[shift] = leadingZeros[shift] + 1;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }

            memcpy( out.data() + (threadId * m), leadingZeros.data(), sizeof( leadingZeros[0] ) * m );
        } ));

    };

    std::array< uint32_t, m > leadingZeros;
    for( auto& x : leadingZeros )
        x = 0;

    for( auto& thread : threads )
    {
        thread.wait();
    }

    for( int i = 0; i < (threadCount * m); i++ )
    {
        leadingZeros[i % m] += out[i];
    }


    for( auto x : leadingZeros )
        std::cout << x << ", ";

    std::cout << std::endl;
}

int main()
{
    typedef std::chrono::high_resolution_clock clock;
    int rounds = 100;

    // do some rounds to get the cpu up to speed..
    for( int i = 0; i < rounds / 10; i++ )
    {
        convolve();
    }


    auto start = clock::now();

    for( int i = 0; i < rounds; i++ )
        convolve();

    auto end = clock::now();
    double seconds = std::chrono::duration_cast< std::chrono::microseconds >( end - start ).count() / 1000000.0;

    std::cout << seconds/rounds*1000 << " msec/round" << std::endl;

    return 0;
}

Örnek çıktı:

   6317312, 2515072, 1034368, 434048, 190144, 85200, 39804, 19168,
   6226944, 2481408, 1031168, 438080, 192896, 86816, 40484, 19490,
   6321152, 2524672, 1045376, 442880, 195680, 88464, 41656, 20212,
   6330624, 2517504, 1031104, 430208, 187696, 83976, 38976, 18708,
   6304768, 2510336, 1030720, 433056, 190880, 86824, 40940, 19840,
   6272512, 2494720, 1028160, 432352, 189168, 84752, 39540, 19052,
   6233600, 2507520, 1046912, 447008, 198224, 89984, 42092, 20292,

Çıktı doğru değil bence, belki bir hata var? Sorudakilerle karşılaştırın. Özellikle, son sütun 19180'e yakın bir sayı olmalıdır.

@Lembik ne demek istediğini anlayabiliyorum. Rastgele çıktının yeterince korkak olmadığını düşünüyorum, bu bazen korkak çıktılar yaratıyor. C ++ 11 rasgele üreteci ile iyi çalışıyor. Bugün kodu daha sonra düzelteceğim.
Stefan,

Stefan.cpp: 104: 101: error: 'memcpy' bu kapsamda ilan edilmedi memcpy (out.data () + (threadId * m), liderZeros.data (), sizeof (leadingZeros [0]) * m );

String.h'yi eklemem gerektiğini düşünüyorum. Tekrar dene.
Stefan,

Bunu g ++ -O3 -std = c ++ 0x -pthread -Wl, - gerekli olmayan Stefan.cpp -o Stefan

16

C ++ x 150 x 450 x 530

Dizi yerine bit (ve kara büyü) kullandım.
Daha hızlı rastgele işlev için teşekkürler @ace.

Nasıl çalışır: tamsayının ilk 15 biti sdiziyi temsil eder S[15]; sıfırlar -1'i, sıfırlar +1'i temsil eder. Dizi Fbenzer şekilde inşa edilmiştir. Ancak her sembol için iki bit ile.

  • 00 -1'i temsil eder
  • 01 ve 10, 0'ı temsil eder.
  • 11 1'i temsil eder

Sebebi Sve Ffarklı bir temsili var . SKıyaslanabilmek için kendisiyle iç içe geçmem gerekiyor F.

  • 0 (-1) 00 (-1 gösteriminde F) oldu
  • 1 (+1), 11 (+1 gösteriminde F) oldu

Şimdi sadece iç ürünü hesaplamak için Carnot'u kullanabiliriz. Bir değişkenin yalnızca 00 veya 11 değerini alabileceğini unutmayın.

0. 00 = 11 (-1 * -1 = +1)
0. 01 = 10 (-1 * 0 = 0)
0. 10 = 01 (-1 * 0 = 0)
0. 11 = 00 (-1 * + 1 = -1)
1. 00 = 00 (+1 * -1 = -1)
1. 10 = 10 (+1 * 0 = 0)
1. 01 = 01 (+1 * 0 = 0)
1. 11 = 11 (+1 * +1 = +1)

Bana xor değil gibi görünüyor. :)

Bunları toplayın sadece vardiya ve maske oyunudur, gerçekten karmaşık bir şey değil.

#include <array>
#include <ctime>

// From standford bithacks
// http://graphics.stanford.edu/~seander/bithacks.html
inline int32_t interleaveBit(int32_t x)
{
   static const uint32_t B[] = { 0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF };
   x = (x | ( x << 8)) & B[3];
   x = (x | ( x << 4)) & B[2];
   x = (x | ( x << 2)) & B[1];
   x = (x | ( x << 1)) & B[0];
   return x | (x << 1);
}

inline int32_t sumOnes(int32_t v)
{
   static int b[] = { 1, 0, 0, 1};
   int s = 0;
   for( int i = 0; i < 8; ++i)
   {
      const int a = 3&(v>>(i*2));
      s += b[a];
   }
   return s;
}

inline int32_t sumArray(int32_t v)
{
   static int b[] = { -1, 0, 0, 1};
   int s = 0;
   for( int i = 0; i < 8; ++i)
   {
      const int a = 3&(v>>(i*2));
      s += b[a];
   }
   return s;
}

uint32_t x, y = 24252, z=57768, w=1564; //PRNG seeds

int32_t myRand()
{
   uint32_t t;
   t = x ^ (x<<1);
   x = y;
   y = z;
   z = w;
   w = w ^ ( w >> 19) ^ t ^ (t >> 8);
   return w;
}

int main()
{
   std::array<int, 8> leadingZero{0};
   x = static_cast<int32_t>(time(nullptr)); // seed PRNG
   const int maxS = 1 << 15;
   for(int s = 0; s < maxS; ++s)
   {
      const int32_t x = interleaveBit(s);
      for(int i = 0; i < 1000; ++i)
      {
         int32_t random;
         do
         {
            random = 0xFFFF & myRand();
         }while(sumOnes(random) == 0);
         int j = 7;
         while( j >= 0 )
         {
            const int32_t h = (x >> (j*2));
            const int32_t l = 0xFFFF & (~(random ^ h)); // inner product
            if(sumArray(l) == 0)
            {
               leadingZero[j]++;
            } else
            {
               break;
            }
            j--;
         }

      }
   }
   for(int i = 7; i >= 0; --i)
   {
      printf("%d ", leadingZero[i]);
   }
   printf("\n");
   return 0;
}

İşte bir örnek çıktı:

6332350 2525218 1041716 438741 192917 87159 41023 19908 

real 0m0.372s
user 0m0.371s
sys  0m0.001s

Program ile derlendi:

gcc -std=c++11 -O3 -msse4.2 -Wall -lstdc++ 26371.cpp -o fastPy

Fedora 20'de gcc 4.8.2 ile İşlemci bir i7 8 çekirdeklidir.

Muhtemelen bazı ms tweaking derleyici parametreleri kazanabilirim.

Makinemde OP çözüm zamanı bu olsa da:

time pypy 26371.py
[6330609, 2523914, 1040885, 439303, 192708, 86987, 40710, 19498]

real 0m53.061s
user 0m53.016s
sys  0m0.022s

Düzenle:

Sadece openmp ekleyerek ve x3 kazançlı olduğum için sırasını değiştirerek OP koduna karşı x450 performansında bir iyileşme sağladım. : D Bu durumda, leadingZerodizinin atomik olması gerekir. Rastgele küresel ... rastgele, daha rasgele olacaklar.

 #pragma omp parallel for
 for(int i = 0; i < 1000; ++i)
 {
    int32_t random;
    do
    {
       random = 0xFFFF & myRand();
    }while(sumOnes(random) == 0);
    for(int s = 0; s < maxS; ++s)
    {
       const int32_t x = interleaveBit(s);
       int j = 7;
       while( j >= 0 )
       {
          const int32_t h = (x >> (j*2));
          const int32_t l = 0xFFFF & (~(random ^ h)); // inner product
          if( sumArray(l) == 0 )
          {
             leadingZero[j]++;
          } else
          {
             break;
          }
          j--;
       }
    }
 }

-fopenmpderleyici bayrağına eklemeniz gerekir


Düzenleme: 2 user71404 tarafından öneri olarak sumOnes ve sumArray işlevlerini değiştirdim ve şimdi çok hızlı.

real  0m0.101s
user  0m0.101s
sys   0m0.000s

Openmp ile daha yavaş, atomların tepegöz fazla eklemesine neden olur.

real  0m0.253s
user  0m1.870s
sys   0m0.001s

Atom olmadan daha hızlı, ama yanlış sonuç alıyorum.

2137992 1147218 619297 321243 155815 70946 32919 15579

real   0m0.048s
user   0m0.338s
sys    0m0.001s

SumArray'i anlamak için, 16 bit'in 8 sayının temsil edildiğini ve dizisini olduğunu düşünün.
00 no 1 ve temsil -1
01 ve 10 tane 1 ve 0 temsil
11 iki 1'leri var ve 1 temsil
Böylece yerleşik 1 [ayarlı bit sayısını http://en.wikipedia.org/wiki/ Hamming_weight] ve her gruba 1. kaldırıyoruz.

sumOnes sadece kara büyüdür.

İşte en yeni derleme bayrakları ve kodları.

gcc -std = c ++ 11 -mfpmath = sse -O3 -flto -march = yerel -funroll-döngüler -Duvar-lstdc ++

#include <cstdint>
#include <cstdio>
#include <ctime>

inline int32_t interleaveBit(int32_t x)
{
   static const uint32_t B[] = { 0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF };
   x = (x | ( x << 8)) & B[3];
   x = (x | ( x << 4)) & B[2];
   x = (x | ( x << 2)) & B[1];
   x = (x | ( x << 1)) & B[0];
   return x | (x << 1);
}

inline int32_t sumOnes(int32_t v)
{
   /* 0xAAAA == 0b1010 1010 1010 1010 */
   return !!(0xAAAA & (v ^ ~(v << 1)));
}

inline int32_t sumArray(int32_t v)
{
   return __builtin_popcount(v) - 8;
}

uint32_t x, y = 24252, z = 57768, w = 1564; //PRNG seeds

int32_t myRand()
{
   uint32_t t;
   t = x ^ (x << 1);
   x = y;
   y = z;
   z = w;
   w = w ^ ( w >> 19) ^ t ^ (t >> 8);
   return w;
}

int main()
{
   int leadingZero[8] = { 0 };
   x = static_cast<int32_t>(time(nullptr)); // seed PRNG
   const int maxS = 1 << 15;
   for( int i = 0; i < 1000; ++i )
   {
      int32_t random;
      do
      {
         random = 0xFFFF & myRand();
      } while(sumOnes(random) == 0 );
      for( int s = 0; s < maxS; ++s )
      {
         const int32_t x = interleaveBit(s);
         int j = 7;
         while( j >= 0 )
         {
            const int32_t h = (x >> (j * 2));
            const int32_t l = 0xFFFF & (~(random ^ h)); // inner product
            if( sumArray(l) == 0 )
            {
               leadingZero[j]++;
            } else
            {
               break;
            }
            j--;
         }
      }
   }
   printf("[%d, %d, %d, %d, %d, %d, %d, %d]\n",
      leadingZero[7], leadingZero[6],
      leadingZero[5], leadingZero[4],
      leadingZero[3], leadingZero[2],
      leadingZero[1], leadingZero[0]);
   return 0;
}

Şimdi bunu test etmek için sabırsızlanıyorum! Ne yazık ki bu birkaç saat sürmeyecek.

1
Aşağıdaki önerilen bir düzenlemeydi, ancak yorum olarak daha uygun olabileceğini düşünüyorum. SumOnes, sumArray komutunu aşağıdakilerle değiştirebilirsiniz (openmp sürümünde 2 kat hızlanma sağlıyor). inline int32_t sumOnes(int32_t v) { /* 0xAAAA == 0b1010 1010 1010 1010 */ return !! (0xAAAA & (v ^ ~(v << 1))); } inline int32_t sumArray(int32_t v) { return __builtin_popcount(v) - 8; }bu @ user71404
ace_HongKongIndependence

@ user71404: profiler, programın tüm zamanlarını bu iki işlevde harcadığını söyledi, ancak dün daha iyi bir şey düşündüğüm için yorgundum. Bu akşam deneyeceğim (UTC). Teşekkürler.
ilmale

İkinci kod pasajını tam kopya ve yapıştırılabilir kod olarak değiştirmeyi düşünür müsünüz? Openmp kodunu çalıştırma girişimlerimde yanlış bir şey yapıyor olmalıyım ki bu çok yardımcı olacak.

Güzel. Bunun bit işlemleriyle yapılabileceğini düşündüm.
Guy Sirton

10

Julia: 0.7s, 120x daha hızlı

User20768'in gösterdiği gibi, kodun Julia'ya basit bir portu PyPy'den iki kat daha hızlıdır. Fakat bundan daha iyisini yapabiliriz.

function pleadingzerocounts(; n = 8,
                              m = 8,
                              iters = 1000)
  @parallel (+) for S = 1:2^(8+8-1)
    leading_counts = zeros(Int, m)
    F = Array(Int, n)
    for i = 1:iters
      flag = 0
      while flag == 0
        for i = 1:n
          v = (1-(rand(Int8)&3))%2
          @inbounds F[i] = v
          flag += v & 1
        end
      end
      for j = 1:m
        sum = 0
        for i = 1:n
          @inbounds sum += S & (1 << (j + i - 2)) > 0 ? F[i] : -F[i]
        end
        sum == 0 ?
          (leading_counts[j] += 1) :
          break
      end
    end
    leading_counts
  end
end

function main()
  # Warm up the JIT
  pleadingzerocounts()
  # Then go for real
  println(@time pleadingzerocounts())
end

Bunu kullanarak çalıştırabilirsiniz julia -p 8 -e 'require("golf.jl");main()'(8 işlem sayısıdır, onunla oynamak isteyebilirsiniz). En son Julia prelelease bu PyPy için 0.7s vs 1m22s alır.

Bilgisayarınızda yeterince çekirdek varsa ve belki birkaç AWS örneği döndürdüyseniz, biraz daha tıraş olabilirsiniz :)


Zamanlamayı yanlış ölçtüğünden eminim. PYPY ile Python ayrıca JIT tabanlı dildir, ancak OP tarafından yapılan zamanlamaları dahil JIT derleme zamanı. Hariç tutuyorsunuz. En son Julia git sürümünü yükledim ve kodunuzu test ettim ve makinemde 8 işlemle komutunuzun tamamlanması 6.6 saniye sürüyor, ancak "geçen süre 0.588 .. saniye" yazdırıyor.
yarı ekstrinsik,

Python zamanlaması PyPy başlangıcını ve JIT ısınmasını içerir, ancak bu en fazla saniye sürer - bir dakikalık çalışma süresindeki fark önemsizdir. OP, Python'un zamanlama şeklini değiştirdiği için mutluyum (herhangi bir fark yaratmayacak), ancak Julia'nın başlangıç ​​saati de dahil olmak üzere makul olmazdı.
bir-daha fazla-dakika

OP'ye orijinal soruya yaptığı yorumları sordum ve “Zamanlamalar JIT dilleri için her şeyi içermeli” dedi. Yetkili, Julia'yı yarışmada bırakarak çözümlerin 1 saniyeden daha uzun süreceği yeni bir mücadele yaratacağını belirtti.
yarı-extrinsik

Bu durumda en uygun çözüm, yaklaşık 2 saniye süren bir seri algoritma kullanmaktır. Kodu yayınlardım ama bu rekabet artık biraz fazla kaldı çünkü herkes zaten C ++ 'ın her şeyden daha hızlı başladığını biliyor.
bir-daha fazla-dakika

Fortran çözümümü yeni yayınladım, bu yüzden neden en hızlı Julia'yı neden göndermemelisiniz (zaten kodunuz varsa).
yarı extrinsik

5

C, 1.210s

OP kodunu makinemde 1m45.729s çalışıyor.

Derleme:

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

Özel teşekkürler: derleme bayrakları için @dyp ve optimizasyonlar için fikirler.

#include <stdio.h>
#include <time.h>

#define n (8)
#define m (8)
#define iters (1000)
int leadingzerocounts[m]; // declared as global so initialised to 0
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 dotproduct(int*F, int*S) {
    unsigned int i;
    int sum=0;
    for(i=0; i<n; i++) {
        sum+=F[i]*S[i];
    }
    return sum;
}

int main() {
    unsigned int i, j, tmp;
    x=(int)time(NULL); //seed PRNG

    int S[n+m-1];
    for(i=0; i<(1<<(n+m-1)); i++) {
        tmp=i;
        for(j=0; j<n+m-1; j++) {
            S[j]=(tmp&1)*(-2)+1;
            tmp>>=1;
        }
        for(j=0; j<iters; j++) {
            int F[n];
            unsigned int k, flag=0;
            do {
                for(k=0; k<n; k++) {
                    F[k]=(1-(myRand()&3))%2;
                    flag+=(F[k]&1);
                }
            } while(!flag);
            for(k=0; k<m&&!dotproduct(F, S+k); k++) {
                leadingzerocounts[k]++;
            }
        }
    }
    for(i=0; i<m; i++) printf("%d ", leadingzerocounts[i]);
    return 0;
}

Örnek çıktı:

6334411 2527506 1042239 439328 192914 87005 40847 19728

1
Gerçekten de, tüm bu optimizasyon bayraklarını düşürürken benzer gözlemler yapabilirim. -march=native -fwhole-program -fstrict-aliasing -ftree-vectorizeBtw'yi dene . Bir MT19937 artı a da dahil olmak üzere bazı C ++ 11 kullanarak <4 s'ye düştüm uniform_int_distribution.
Nisan’da

1
1.119'lar kabaca 59 hız kazanıyor!

1
@ ace Evet, sadece bunu vurgulamak istedim. C ++ 'da bazı standart PRNG kütüphanelerini denemek benim için daha kolaydı. 8 giriş üretmek için bir PRNG'den bir 32 bit tam sayı sonucu kullanabileceğinizi unutmayın F.
dyp

1
Yana neşittir 8, muhtemelen uygun olan dotProduct hesaplamak için AVX (veya 2 * SSE) kullanabilir Sdepolama.
Michael M.

2
SSE versiyonu, küçük hız-yukarı: gist.github.com/anonymous/11394210 (dahil etmeyi unutmayın smmintrin.h)
Michael M.

5

Perl

Bu, C çözümünün kadar hızlı bir yer değil, fakat bence üst düzey bir yorumlanmış dil için oldukça hızlı. Python uygulamasının çalışma süresinin yaklaşık% 40'ını tıraş eder.

#!/usr/bin/env perl

use v5.10;
use strict;
use warnings;
use Algorithm::Combinatorics qw( variations_with_repetition );
use List::Util qw( any sum );

use constant {
  N        => 8,
  M        => 8,
  ITERS    => 1000,
};

my @leadingzerocounts;

my $variations = variations_with_repetition([-1, 1], N + M - 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;
    }

    my $j = 0;
    while ($j < M)
    {
      last if sum map $F[$_]*$S->[$j+$_], 0..N-1;
      $leadingzerocounts[$j++]++;
    }
  }
}

say join ", ", @leadingzerocounts;

Algorithm :: Combinatorics, Ubuntu'da ( sudo apt-get install libalgorithm-combinatorics-perl) mevcuttur. Kullanılan diğer modüller çekirdek Perl modülleridir, bu nedenle zaten Ubuntu kurulumunun bir parçası olarak kurulmalıdır.


1
Hızı etkilemeyecek, ama en 0..N-1sondaki menzili map, değil mi? Unuttun use warningsmu :-) OP'deki mantık kafa karıştırıcı olsa da, sürgülü pencere asla son elemanına ulaşmıyor S.
user2846289

Ah. Dizilerin eşleşmeyen boyutlarda olduğunu düşündüm, bu yüzden warningseksik öğelerin sıfır olarak ele alınmasına izin verdim . N-1bunu iyileştirir. Ve aslında hızı çok az artırıyor - Python uygulamasından% 40 daha hızlı.
tobyink

Kodunuzun List :: Util'in çok modern bir sürümünü gerektirdiğini düşünüyorum. "Herhangi bir" List :: Util modülü tarafından ihraç edilmez ubuntu 14.04 ben olsun

Oh evet, bu doğru - muhtemelen List :: Util off CPAN kurmanız gerekir. anyalternatif olarak, çekirdek modül olmasa da en yaygın kullanılan CPAN modüllerinden biri olmasına rağmen List :: MoreUtils'te bulunabilir.
tobyink

4

Julia: 4.66x daha yavaş!

Gerçekten kendi Web sitesinde istatistiklerinden şüphe etmeye başlıyorum ...

Aşağıdaki Julia kodunun OP'nin Python kodunun herhangi bir optimizasyon olmadan doğrudan bir transkripsiyonu olduğuna dikkat edin. Bu time()fonksiyonun Julia'nın yavaş başlangıç ​​zamanını dışlamak için kullanıyorum ...

srand(27182818284590)
t = time()

require("Iterators")

n = 8
m = 8
iters = 1000
bothzero = 0
leadingzerocounts = zeros(m)

for S in Iterators.product(fill([-1,1], n+m-1)...)
    for i = 1:iters
        F = [[-1 0 0 1][rand(1:4)] for j = 1:n]
        while all((x) -> x == 0, F)
            F = [[-1 0 0 1][rand(1:4)] for j = 1:n]
        end
        j = 1
        while j <= m && sum(map(*, F, S[j:j+n-1])) == 0
            leadingzerocounts[j] += 1
            j += 1
        end
    end
end

println(leadingzerocounts)

t = time() - t
println("$t seconds")

Julia: 5 m 32,912 s

OP'nin PyPy'deki kodu: 1 m 11.506 s

Julia çıktısı:

6332170
2525472
1041522
438761
193119
86873
40705
19662

7
<s> utanmazlığınız </s> sporcuğunuz için +1.
ace_HongKongIndependence

Global değişkenler, ithalatlar ve dizi kavramaları yavaştır. Bu, performans için tipik olarak bir Julia programını yazmanın bir yolu değildir.
Alex A.

4

RPython 0.187s (258x daha hızlı)

Orijinal Kaynak w / PyPy2.2.1: 1m 6.718s

Şimdi iş parçacığı ile, standart Python için geri destek bırakıldı. Çalışan iş parçacığı sayısı bir komut satırı parametresi olarak belirtilebilir, varsayılan ikidir.

from time import time, sleep
from math import fmod

from rpython.rlib.rthread import start_new_thread, allocate_lock, get_ident
class Random:
  __slots__ = ['s']

  def __init__(self, s=1):
    self.s = s

  def init_genrand(self, seed):
    self.s = seed

  def genrand32(self):
    # xorshift PRNG with period 2^32-1
    # see http://core.kmi.open.ac.uk/download/pdf/6250138.pdf
    self.s ^= (self.s << 13)
    self.s ^= (self.s >> 17)
    self.s ^= (self.s << 5)
    return self.s

class ThreadEnv:
  __slots__ = ['n', 'm', 'iters', 'counts', 'running', 'lock']

  def __init__(self):
    self.n = 8
    self.m = 8
    self.iters = 1000
    self.counts = [0]*8
    self.running = 0
    self.lock = None

env = ThreadEnv()
truth = [-1,0,0,1]

def main(argv):
  argc = len(argv)
  if argc < 4 or argc > 5:
    print 'Usage: %s N M ITERS [NUM_THREADS=2]'%argv[0]
    return 1

  if argc == 5:
    num_threads = int(argv[4])
  else:
    num_threads = 2

  env.n = int(argv[1])
  env.m = int(argv[2])
  env.iters = int(argv[3]) // num_threads
  env.counts = [0]*env.m
  env.lock = allocate_lock()

  for i in xrange(num_threads-1):
    start_new_thread(run,())
    env.running += 1

  env.running += 1

  # use the main process as a worker
  run()

  # wait for any laggers
  while env.running:
    sleep(0.01)

  print env.counts
  return 0

def run():
  n, m, iters = env.n, env.m, env.iters
  counts = [0]*m
  sbits = [0]*(n+m-1)

  random = Random()
  seed = int(fmod(time(), 2147483.648)*1000) ^ get_ident()
  random.init_genrand(seed)

  for S in xrange(1<<n+m-1):
    i = 0
    sbit = 0
    while not sbit:
      sbits[i] ^= 3
      sbit = sbits[i]
      i += 1

    for i in xrange(iters):
      f = 0
      while not f:
        F = random.genrand32()

        G, x = F, 0
        for k in xrange(n):
          x += truth[(G&3)^sbits[k]]
          f |= x
          G >>= 2

      if not x:
        counts[0] += 1
        for j in xrange(1, m):
          G, x = F, 0
          for k in xrange(j, n+j):
            x += truth[(G&3)^sbits[k]]
            G >>= 2
          if x: break
          counts[j] += 1

  # passing True stalls until a lock can be obtained
  env.lock.acquire(True)

  for i in xrange(m):
    env.counts[i] += counts[i]
  env.running -= 1

  env.lock.release()

def target(*args):
  return main, None

RPython , C'ye çevrilebilen ve sonra RPython Toolchain kullanılarak derlenen, sınırlı bir Python alt kümesidir . Dilin amacı, dil tercümanlarının oluşturulmasına yardımcı olmaktır, ancak yukarıdaki gibi basit programları derlemek için de kullanılabilir. Python'un 'meraklısı' özelliklerinin çoğu, örneğin, itertoolshatta mapmevcut değildir.

Derlemek için mevcut pypy deposunun yerel bir klonunu yapın ve aşağıdakileri çalıştırın:

$ pypy %PYPY_REPO%/rpython/bin/rpython --thread convolution.py

Sonuçta ortaya çıkan yürütülebilir dosya convolution-cmevcut çalışma dizininde adlandırılmış veya buna benzer olacaktır .

Giriş değişkenlerini parametrelendirdim, bu nedenle program şu şekilde çalıştırılmalıdır:

convolution-c 8 8 1000

Örnek kodla eşleştirmek için


Uygulama notları

S in itertools.product([-1,1], repeat = n+m-1)olur S in xrange(1<<n+m-1), Sbir bit haritası olarak yorumlanır : [ 0, 1] → [ -1, 1]

Benzer şekilde, Fbir bit eşlemi, tek bir değer temsil eden her iki biti ile, aynı zamanda:
[ 00, 01, 10, 11] → [ -1, 0, 0, 1]

Bir mulitplikasyon yapmak yerine, ürünü aramak için bir doğruluk tablosu kullanılır.

32 bit işaretli tam sayılar kullanıldığından, n15'ten n+mbüyük ve 31'den büyük olamaz. rpython.rlib.rbigintGerekirse , modülle isteğe bağlı tamsayı desteği sağlanabilir .

Nokta-ürün döngüsünün ilk yinelemesi kontrol edilmemiş ve sıfırlık testi ile birleştirilmiştir F.

Bir homebrew PRNG kullanılmış, kaynak listelenmiştir. Makalenin yazarı 2 32 -1 dönemini gösteriyor ve bunu kişisel olarak onaylamamama rağmen tüm Diehard testlerinden geçtiğini iddia ediyor.

Rastgele tohum her milisaniyede değişir; bu, bir zaman damgasının kullanılmasına izin verecek kadar iyidir. Ek olarak, her bir işçi iş parçacığı, her xorbirinin farklı bir çekirdeğe sahip olmalarını sağlamak için işlem kimliğini bu değerle kullanır.


Örnek Zamanlamaları

2 işçi ipliği:

$ timeit convolution-c 8 8 1000 2
[6331845, 2526161, 1042330, 440018, 193724, 87147, 40943, 19603]

Elapsed Time:     0:00:00.375
Process Time:     0:00:00.687
System Calls:     6927

4 işçi ipliği:

$ timeit convolution-c 8 8 1000 4
[6334565, 2527684, 1043502, 440216, 193225, 87398, 40799, 19338]

Elapsed Time:     0:00:00.218
Process Time:     0:00:00.796
System Calls:     3417

8 çalışan iş parçacığı:

$ timeit convolution-c 8 8 1000 8
[6327639, 2522483, 1039869, 437884, 192460, 86771, 40420, 19403]

Elapsed Time:     0:00:00.187
Process Time:     0:00:00.734
System Calls:     3165

OP'nin orijinal kaynağı:

$ timeit pypy convolution-orig.py
[6330610, 2525644, 1041481, 438980, 193001, 86622, 40598, 19449]

Elapsed Time:     0:01:06.718
Process Time:     0:01:06.718
System Calls:     11599808

100000 yineleme için zamanlama:

$ timeit convolution-c 8 8 100000 8
[633156171, 252540679, 104129386, 43903716, 19307215, 8709157, 4072133, 1959124]

Elapsed Time:     0:00:16.625
Process Time:     0:01:02.406
System Calls:     171341

Daha önce hiç bir rpython programı görmemiştim. Bu harika. Şimdi pypy'nin 1.03'lerde çalıştırabileceği eşdeğer bir python programı var mı?

@Lembik Birini görmek istiyorum. Saf pitona ilk girişimin ~ 15'ler olduğunu düşünerek 4.7'lerin oldukça iyi olduğunu düşündüm.
primo

Evet, gecikme için üzgünüm. Kodu henüz çalıştırmadım ancak mümkün olan en kısa sürede çalışacak.

Bir JIT eklemeyi denemelisin. Şimdi BU hızlı olurdu!
kirbyfan64sos

@Lembik, bahsettiğiniz için teşekkür eder;) Meraktan çıktı, en hızlı 4 işçi ipliği ile mi çalıştı, yoksa 8 mi?
primo

3

Julia: 1 dak 21.4s (2.2x daha hızlı) (Arman kodunun değiştirilmesi)

Op'un PyPy'deki kodu: 3 dak 1.4 sn

Her ikisi de REPL'de yapılır, paketleri yükleme zamanı dahil değildir.

function foo()                                                                                                                                                             
    n = 8                                                                                                                                                                  
    m = 8                                                                                                                                                                  
    iters = 1000                                                                                                                                                           
    bothzero = 0                                                                                                                                                           
    leadingzerocounts = zeros(Int,m)                                                                                                                                       
    P=[-1,0,0,1]                                                                                                                                                           

    for S in Iterators.product(fill([-1,1], n+m-1)...)                                                                                                                     
        Sm=[S...]                                                                                                                                                          
        for i = 1:iters                                                                                                                                                    
            F = P[rand(1:4,n)]                                                                                                                                             
            while all(F==0)                                                                                                                                                
                F = P[rand(1:4,n)]                                                                                                                                         
            end                                                                                                                                                            
            j = 1                                                                                                                                                          

            while j <= m && dot(F,Sm[j:j+n-1]) == 0                                                                                                                        
                leadingzerocounts[j] += 1                                                                                                                                  
                j += 1                                                                                                                                                     
            end                                                                                                                                                            
        end                                                                                                                                                                
    end                                                                                                                                                                    

    println(leadingzerocounts)                                                                                                                                             
end 

Arman'ın koduyla ilgili çok yavaşlayan bazı problemler var: Gereksiz yere çok sayıda isimsiz fonksiyon ve yüksek dereceli fonksiyon kullanıyor. Tüm bir F vektörünün sıfır olup olmadığını test etmek için neden hepsinin yerine sadece tümünü yazmıyorsunuz (F == 0) (x-> x == 0, F)? Daha kısa ve kelimenin tam anlamıyla bin kat daha hızlı.

Ayrıca sadece nokta (x, y) yerine nokta ürün olarak toplamı (harita (*, x, y)) kullanır). 10k bir vektör için 650k daha yavaş ilk sürümü iki katına çıkar. Ve nokta ürün fonksiyonu saf Julia'da for döngüsü olarak uygulanmıştır.

Ayrıca dizi kavramaları yavaştır. J = 1: n] için [[-1 0 0 1] [rand (1: 4)] yerine [0,1,0, -1] [rand (1: 4, n)] yazmak daha iyidir. .

Sonunda, küresel değişkenler Julia'da kötü juju'dur. Julia, yalnızca JIT ve yazım sonuçlarının çalışmasına izin verecek şekilde kod yazdığınızda hızlıdır. Bunun büyük bir kısmı tür kararlılığıdır: Derleyici, örneğin bir döngü içinde bir değişkenin türünün değişmeyeceğinden emin olmalıdır.


Teşekkürler! Hızı hakkında iddialarda bulunmadan önce Julia Dili hakkında hala biraz bilgi sahibi olduğumu görüyorum.
çevik ağar

2

Nemrut

import times, locks, strutils, unsigned

const
  N = 8
  M = 8
  iters = 1000
  numThreads = 8

type
  SVec = array[0..N+M-1, int]
  FVec = array[0..N-1, int]
  ComputeThread = TThread[int]

var
  rngSeed = int(epochTime()*1000)
  totalLeadingZeros: array[0..M-1, int]
  lock: TLock

type
  RNGState = object
    x, y, z, w: uint32

proc newRNG(seed: int): RNGState =
  result.x = uint32(seed)

proc random(rng: var RNGState): int =
  let t = rng.x xor (rng.x shl 11)
  rng.x = rng.y; rng.y = rng.z; rng.z = rng.w
  rng.w = rng.w xor (rng.w shr 19) xor t xor (t shr 8)
  result = int(rng.w)

proc initVecRand(v: var FVec, rng: var RNGState) =
  const values = [ -1, 0, 0, 1 ]
  var rnd = rng.random
  var bitAcc = 0
  for i in 0 .. <len(v):
    let val = values[rnd and 3]
    rnd = rnd shr 2
    v[i] = val
    bitAcc = bitAcc or val
  if bitAcc == 0:
    initVecRand(v, rng)

proc convolve(s: SVec, f: FVec, offset: int): int =
  for i in 0 .. <len(f):
    result += s[i+offset]*f[i]

proc iterate(v: var SVec) =
  for i in 0 .. <len(v):
    if v[i] == -1:
      v[i] = 1
      return
    v[i] = -1

proc mainThread(id: int) {.thread.} =
  const numS = 1 shl (N+M-1)
  var
    s: SVec
    f: FVec
    leadingZeros: array[0..M-1, int]
    rng = newRNG(rngSeed + id)
  for k in 0 .. <len(s):
    s[k] = -1
  for i in 1..numS:
    for j in countUp(id, iters, numThreads):
      initVecRand(f, rng)
      if convolve(s, f, 0) == 0:
        leadingZeros[0] += 1
        for k in 1 .. <M:
          if convolve(s, f, k) == 0:
            leadingZeros[k] += 1
          else:
            break
    iterate(s)
  acquire(lock)
  for i in 0 .. <M:
    totalLeadingZeros[i] += leadingZeros[i]
  release(lock)

proc main =
  let startTime = epochTime()
  var threads: array[1..numThreads, ComputeThread]
  initLock(lock)
  for i in 1..numThreads:
    createThread(threads[i], mainThread, i)
  for i in 1..numThreads:
    joinThread(threads[i])
  echo("Leading zeros: ", @totalLeadingZeros)
  let endTime = epochTime()
  echo("Time taken:    ", formatFloat(endTime - startTime, ffDecimal, 3),
       " seconds")

main()

Örnek çıktı:

Leading zeros: @[6333025, 2525808, 1042466, 439138, 192391, 86751, 40671, 19525]
Time taken:    0.145 seconds

Nimrod C'yi derler, dolayısıyla arka uç meseleleri için de C derleyici seçimi.

Clang kullanarak, ile derleyin:

nimrod cc --threads:on --cc=clang --passc:-flto -d:release conv.nim

Gcc'yi kullanarak, ile derleyin:

nimrod cc --threads:on --cc=gcc --passc:-flto -d:release conv.nim

LTO'yu --passc:-fltodesteklemeyen daha eski bir C derleyiciniz varsa ihmal edin. --cc=...C derleyicisi için varsayılan seçimde sorun yoksa , seçeneği atlayın . Kod Nimrod 0.9.4 veya 0.9.5 gerektirir .

Quadcore iMac'imde (2.66 GHz çekirdek i5), kod PyPy 2.2.1 için 88 saniyeye kıyasla, yani ccc ile gcc 4.9, .16 saniyede yaklaşık 15 saniye, yani 500 saniyede bir hızlanma). Ne yazık ki, aynı zamanda PyPy'nin kurulu olduğu veya PyPy'yi kolayca kurabileceğim dört çekirdeğe sahip bir makineye erişemiyorum, ancak 64 çekirdekli AMD'ye 0,1 saniye (çok fazla ölçüm gürültüsü ile) gelebiliyor Opteron 6376 1.4 GHz (/ proc / cpuinfo'ya göre) gcc 4.4.6 ile.

Uygulama, okunabilirlik pahasına kodu optimize etmek yerine orjinaline sadık kalmaya çalışırken, belirgin optimizasyonlar gerektirmez. İlginçtir ki, kuyruk özyinelemesi, initVecRand()hem gcc hem de clang ile bir mola talimatıyla bir döngüden biraz daha hızlıdır. convolveTest döngüsünün bir yinelemesinin ana döngü içindeki el ile kaldırılması , muhtemelen daha iyi dal tahmininden dolayı hızlanmaya neden oldu.


Ubuntu için nimrod'u nasıl alırsın?

@Lembik Hızlı bir Google araması size nimrod-lang.org/download.html
ace_HongKongIndependence

@ Yazılan bağlantıya ayrıca bağlantı eklememiştim (şimdi siyahla mavi ile görmek zor olsa da).
Reimer

Tohum boyutunu 128 bit'e yükselterek
user60561

2

Java

Yukarıdaki C ++ çözümünü Java'ya çevirdim:

import java.util.Random;
import java.util.Arrays;

public class Bench2 {
  public static int[] bits = { 0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF };
  public static int[] oneValues = { 1, 0, 0, 1 };
  public static int[] values = { -1, 0, 0, 1 };
  public static int n = 8;
  public static int m = 8;
  public static int iters = 1000;

  private static int x,y=34353,z=57768,w=1564;

  public static void main( String[] args ) {
    x = (int) (System.currentTimeMillis()/1000l);

    int[] leadingzerocounts = new int[ m ];
    Arrays.fill( leadingzerocounts, 0 );

    int maxS = 1 << 15;

    for( int s = 0; s < maxS; s++ ) {
      int x = interleaveBit( s );

      for( int i=0; i<iters; i++ ) {
        int random;

        do {
          random = 0xFFFF & fastRandom( );
        } while( sumOnes( random ) == 0 );

        int j = 7;

        while( j >= 0 ) {
          int h = ( x >> (j*2) );
          int l = 0xFFFF & (~(random ^ h));

          if( sumArray( l ) == 0 ) {
            leadingzerocounts[ j ]++;
          } else {
            break;
          }

          j--;
        }
      }
    }

    for( int i = 7; i >= 0; --i ) {
      System.out.print( leadingzerocounts[ i ] + " " );
    }

    System.out.println( );
  }

  public static int interleaveBit( int x ) {
    x = (x | ( x << 8)) & bits[3];
    x = (x | ( x << 4)) & bits[2];
    x = (x | ( x << 2)) & bits[1];
    x = (x | ( x << 1)) & bits[0];
    return x | (x << 1);
  }

  public static int sumOnes( int v ) {
    return (0xAAAA & (v ^ ~(v << 1)));
    // int s = 0;

    // for( int i = 0; i < 8; ++i ) {
    //   int a = 3 & ( v >> (i*2) );
    //   s += oneValues[ a ];
    // }

    // return s;
  }

  public static int sumArray( int v ) {
    return Integer.bitCount( v ) - 8;
    // int s = 0;

    // for( int i=0; i<8; ++i ) {
    //   int a = 3 & ( v >> (i*2) );
    //   s += values[ a ];
    // }

    // return s;
  }

  public static int fastRandom( ) {
    long t;
    t = x ^ (x << 11);
    x = y; y = z; z = w;
    return w = (int)( w ^ (w >> 19) ^ t ^ (t >> 8));
  }
}

Makinemde Java programının çıktılarını alıyorum:

time java Bench2
6330616 2524569 1040372 439615 193290 87131 40651 19607 
java Bench2  0.36s user 0.02s system 102% cpu 0.371 total

OP'ler programı makinemde yaklaşık 53 saniye çalışıyor:

time pypy start.py
[6330944, 2524897, 1040621, 439317, 192731, 86850, 40830, 19555]
pypy start.py  52.96s user 0.06s system 99% cpu 53.271 total

C ++ programı sadece yaklaşık 0.15 saniye çalıştırıldı:

time ./benchcc
[6112256, 2461184, 1025152, 435584, 193376, 87400, 40924, 19700]
./benchcc  0.15s user 0.00s system 99% cpu 0.151 total

Bu, ilgili java çözümünden yaklaşık 2,5 kat daha hızlıdır (VM başlangıcını hariç tutmadım). Bu java çözümleri PyPy ile yürütülen programdan yaklaşık 142 kat daha hızlı.

Şahsen ilgilendiğim için, itersJava ve C ++ için 100_000 değerine ayarladım ancak bir şey büyüdüğünde 2.5 faktörü Java lehine düşmedi.

EDIT: Programları 64bit Arch Linux PC'de çalıştırdım.

EDIT2: python kodunun kaba bir çevirisi ile başladığımı eklemek istiyorum:

import java.util.Random;
import java.util.Arrays;

public class Bench {
    public static int[] values = { -1, 0, 0, 1 };
    public static int n = 8;
    public static int m = 8;
    public static int iters = 1000;

    private static int x,y=34353,z=57768,w=1564; 

    public static void main( String[] args ) {
        x = (int) (System.currentTimeMillis()/1000l);

        int[] leadingzerocounts = new int[ m ];
        Arrays.fill( leadingzerocounts, 0 );

        int[] S = new int[ n+m-1 ];
        Arrays.fill( S, -1 );

        do {
            for( int i=0; i<iters; i++ ) {
                int[] F = new int[ n ];

                do {
                    randomArray( F );
                } while( containsOnlyZeros( F ) );

                for( int j=0; j < m && check( F, S, j ); j++ ) {
                    leadingzerocounts[ j ] += 1;
                }
            }
        } while( next( S ) );

        System.out.println( Arrays.toString( leadingzerocounts ) );
    }

    public static void randomArray( int[] F ) {
        for( int i = 0; i<F.length; i++ ) {
            F[ i ] = (1-(fastRandom()&3))%2;
        }
    }

    public static boolean containsOnlyZeros( int[] F ) {
        for( int x : F ) {
            if( x != 0 ) {
                return false;
            }
        }

        return true;
    }

    public static boolean next( int[] S ) {
        for( int i=0; i<S.length; i++ ) {
            if( ( S[ i ] = -S[ i ] ) == 1 ) {
                return true;    
            }
        }

        return false;
    }

    public static boolean check( int[] F, int[] S, int j ) {
      int sum = 0;

      for( int i=0; i<n; i++ ) {
          sum += F[ i ] * S[ j + i ];
      }

      return sum == 0;
    }

    public static int fastRandom( ) {
        long t;
        t = x ^ (x << 11);
        x = y; y = z; z = w;
        return w = (int)( w ^ (w >> 19) ^ t ^ (t >> 8));
    }
}

Bu program yaklaşık 3.6 saniye sürdü:

time java Bench   
[6330034, 2524369, 1040723, 439261, 193673, 87338, 40840, 19567]
java Bench  3.64s user 0.01s system 101% cpu 3.600 total

PyPy çözümünden yaklaşık 14 kat daha hızlı. (FastRandom işlevi üzerinden standart rasgele işlev seçilmesi, 5 saniyelik bir yürütme süresine yol açar)


2

Python 3.5 + rakam 1.10.1, 3.76 saniye

Testler Macbook Pro'da yapıldı. OP'nin kodu aynı makinede ~ 6 dakika sürdü.

Aslında bu soruyu cevaplamamın nedeni, 10 itibarımız olmadığı ve I. Bölüm :-p

Geçtiğimiz birkaç gün boyunca, numpy ile büyük hacimlerle nasıl verimli bir şekilde başa çıkılacağını çözmeye çalışıyorum (üçüncü parti bir pakete, hatta scipy'e güvenmeden). Araştırmam sırasında bu zorluklarla karşılaştığımda, denemeye karar verdim. Bu oyuna geç kalmış olabilirim, ancak işte Python 3.5 ve numpy 1.10.1 kullanmaya çalışmam.

def test_convolv():
    n = 8 
    m  = 8 
    iters = 1000
    ilow = np.ceil(0+n/2).astype(int)
    ihigh = np.ceil(m+n/2).astype(int)

    leadingzerocounts = np.zeros(m)

    # Pre-compute S & F
    S = np.array(list(itertools.product([-1,1], repeat = n+m-1)))
    choicesF = np.random.choice(np.array([-1, 0, 0, 1], dtype=np.int8), size=n*iters).reshape(iters,n)
    imask = ~np.any(choicesF, axis=1)
    while np.any(imask):
        imasksize = np.count_nonzero(imask)
        choicesF[imask,:] = np.random.choice(np.array([-1, 0, 0, 1], dtype=np.int8), size=n*imasksize).reshape(imasksize, n)
        imask = ~np.any(choicesF, axis=1)

    for i in np.arange(iters):
        F = choicesF[i, :]
        # This is where the magic is: by flattening the S array, 
        # I try to take advantage of speed of the np.convolve 
        # (really numpy.multiarray.correlate). 
        FS = (np.convolve(S.reshape(-1), F, 'same').reshape(S.shape))[:, ilow:ihigh]
        jmask_not = (FS[:, 0] != 0)
        leadingzerocounts[0] = leadingzerocounts[0]+np.count_nonzero(~jmask_not)
        for j in np.arange(n-1)+1:
            jmask = (FS[jmask_not, j] != 0)
            leadingzerocounts[j] = leadingzerocounts[j] + np.count_nonzero(~jmask)
            jmask_not[(jmask_not.nonzero()[0])[jmask]] = False

    print(leadingzerocounts)

S ve F dizilerini önceden hesapladım ve (deneylerime dayanarak) np.convolve hızından yararlanabilecek olan evrişimi yaparken S dizisini düzleştirdim. Başka bir deyişle, vectorized bir evrişim rutini bulamadığım için, tüm diziyi düzleştirerek kodu sahte bir şekilde vektörleştirdim ve np.convolved 'in çalışmakta olan benim için başlık altında vectorization yapmasını umuyorum. Not mode = 'same' kullandım ve işe yaramaz olan öncü ve takip eden öğeleri kırptım.

Macbook Pro'umda test sonuçları 3,76 saniye veriyor . OP'nin kodunu çalıştırdığımda (Python 3.5 olarak değiştirildi), yaklaşık 6 dakikam var . Hızlanma yaklaşık 100 katıdır.

Bir dezavantajı, S ve F dizilerinin saklanması gerektiği için, boyutların çok büyük olması durumunda bellek gereksiniminin bir sorun olabileceğidir.

Kısım I için de aynı yöntemi kullandım ve dizüstü bilgisayarımda ~ 60-100x hızlandım.

Macbook Pro'umdaki her şeyi yaptığım gibi, biri kodumu test edebilir ve makinenizde nasıl çalıştığını bana bildirirse, çok memnun olurum!


1

J, 130x ~ 50x hızlanma?

n =: m =: 8
len =: 1000
S =: (] - 0 = ])S0=: #:i.2^<:+/n,m
k =: (n#0) -.~ (_1 0 0 1) {~ (n#4) #: i.4^n
sn =: (]-0=])#:i.2^n
ku =: ~. k
M =: 0=+/"1 sn *"1/ ku
fs =: (ku&i.)"1 k
snum =: n #.\"1 S0

run =: 3 : 0
 r =: n#0
 for_t. (snum) do.
   rn =: fs{~? len # #k
   r =: r + +/"1*/\rn{"1 t{M
 end.
 r
)
echo run 0
exit''

Rastgele bir debiandaki zamanlar:

u#>time j slowpy.ijs
6334123 2526955 1041600 440039 193567 87321 40754 19714

real    0m2.453s
user    0m2.368s
sys     0m0.084s


u#>time python slow_pyth.py
[6331017, 2524166, 1041731, 438731, 193599, 87578, 40919, 19705]

real    5m25.541s
user    5m25.548s
sys     0m0.012s

İyileştirme için yer olduğunu düşünüyorum.


Python komut kullanılarak yürütülecek gerekiyordu pypy, değil pythonsenaryonuz 130x hız vermesiyle gibi görünüyor, bu yüzden de.
ace_HongKongIndependence

@ as evet fark ettim ama pypy yükleyemiyorum: - / Ben büyüklük sırası olsa da kalacak düşünüyorum.
Eelvex


Gerçekten de, mutlaka değil.
Eelvex

Pypy kurulumunda ne sorun var?

1

C ++: x200 (4 çekirdekli i7, 8 çekirdekli x400'e ölçeklendirilmelidir)

Paralelleştirme ile daha basit bir C ++ 11 (VS 2012, gcc ve clang ile test edilmiştir) çözümü için çalışıyorum.

Bunun Linux altında gcc 4.8.1 ile derlenip çalıştırılmasını sağlamak için:

g ++ -O3 -msse -msse2 -msse3 -march = native -std = c ++ 11 -pthread -Wl, - gerektiği gibi golf.cpp

Linux altında std::launch::asyncbirden fazla iş parçacığını zorlamamız gerekiyor . Daha önceki bir sürümde bunu özlüyordum.

Visual Studio'da (2012+) bu sadece işe yaramalı fakat zamanlama için bir sürüm oluşturmalı ...

Oldish dual core i3'ümde bu ~ 0,9 saniyede çalışıyor. Benim i7 dört çekirdekli bu 0,319s vs pypy 66 saniyedir.

8 çekirdekli bir i7'de bunun x400 hız aralığında olması gerekir. C tarzı dizilere geçmek hızlanacaktı ama ben C ++ kapları ile ilgilenmek istiyordum. Benim için sorunlu bölgeye nispeten yakın kalırken göreceğiniz hızı görmek ilginç ve göreceli olarak yüksek bir seviyede, C ++ 'ın gerçekten iyi olduğunu düşündüğüm bir şey. Ayrıca, C ++ 11 yapılarının kullanıldığı paralelliğin göreceli kolaylığı da not edilmelidir.

@ ilmale'nin bit çözümü çok havalı ve -1/1/0 için çalışıyor. Biri de SSE'yi buna atabilir ve belki de önemli bir hız kazanabilir.

Paralelleşmenin ötesinde, içinde toplamları azaltan başka bir "numara" da var. Örnek sonuçlar: 6332947 2525357 1041957 438353 193024 87331 40902 19649

#include <vector>
#include <iostream>
#include <thread>
#include <future>
#include <time.h>
#include <ctime>
#include <algorithm>

using namespace std;

// Bring some of these constants out to share
const size_t m = 8;
const int nthreads = 16;
const size_t cn = 15;
const int two_to_cn = 32768;

static unsigned int seed = 35;

int my_random() // not thread safe but let's call that more random!
{
   seed = seed*1664525UL + 1013904223UL; // numberical recipes, 32 bit
   return ((seed>>30)&1)-!!((seed>>30)&2); // Credit to Dave!
}

bool allzero(const vector<int>& T)
{
   for(auto x : T)
   {
      if(x!=0)
      {
         return false;
      }
   }
   return true;
}

// Return the position of the first non-zero element
size_t convolve_until_nonzero(size_t max_n, const vector<int>& v1, const vector<int>& v2)
{
   for(size_t i = 0; i<max_n; ++i)
   {
      int result = 0;
      for(size_t j = 0; j<v2.size(); ++j)
      {
         result += v1[i+j]*v2[j];
      }
      if(result!=0)
      {
         return i;
      }
   }
   return max_n;
}

void advance(vector<int>& v)
{
   for(auto &x : v)
   {
      if(x==-1)
      {
         x = 1;
         return;
      }
      x = -1;
   }
}

vector<int> convolve_random_arrays(vector<int> S, int range)
{
   const int iters = 1000;
   int bothzero = 0;
   int firstzero = 0;

   time_t current_time;
   time(&current_time);
   seed = current_time;


   vector<int> F(m);
   vector<int> leadingzerocounts(m+1);

   for(auto &x: leadingzerocounts)
   {
      x = 0;
   }

   for(int i=0; i<range; ++i)
   {
      for(int j=0; j<iters; ++j)
      {
         do
         {
            for(auto &x : F)
            {
               x = my_random();
            }
         } while(allzero(F));
         leadingzerocounts[convolve_until_nonzero(m, S, F)]++;
      }
      advance(S);
   }

   // Finish adding things up...
   for(int i=m-1; i>0; --i)
   {
      leadingzerocounts[i] += leadingzerocounts[i+1];
   }

   vector<int> withoutfirst(leadingzerocounts.begin()+1, leadingzerocounts.end());
   return withoutfirst;
}

int main(int argc, char* argv[])
{

   vector<int> leadingzerocounts(m);

   for(auto &x: leadingzerocounts)
   {
      x = 0;
   }

   clock_t start = clock();

   vector<int> S(cn);
   for(auto &x : S)
   {
      x = -1;
   }

   vector< future< vector< int > > > fs; // The future results of the threads

   // Go make threads to work on parts of the problem
   for(int i=0; i<nthreads; ++i)
   {
      vector<int> S_reversed = S; // S counts using LSBs but we want the thread start to be in MSBs
      reverse(S_reversed.begin(), S_reversed.end());
      fs.push_back(async(std::launch::async, convolve_random_arrays, S_reversed, two_to_cn/nthreads));
      advance(S);
   }
   // And now collect the data
   for(auto &f : fs)
   {
      vector<int> result = f.get();
      for(int i=0; i<result.size(); ++i)
      {
         leadingzerocounts[i] += result[i];
      }
   }

   for(auto count : leadingzerocounts)
   {
      cout << count << endl;
   }

   return 0;
}

1

Fortran: 316x

Tamam, Fortran: 4 çekirdekli bir i7 işlemcide bir Xorshift RNG ve OpenMP kullanırken 106x 155x 160x 316x hızlandırmaya başladım. Bunun dışında büyük numaralar yoktur. Yineleyicinin S'yi oluşturması için, yalnızca 16 bit tamsayının i'nin ikili gösterimini kullanıyorum. Satır içi RNG ve iteratör "iterator" / eşleme dışında kodun Python kodu kadar yüksek olduğunu not edersiniz.

Düzenleme: Xorshift'te "if" kaldırıldı, şimdi "r = w / ..." yerine "r = abs (w / ...)" kullanıldı. 106x ile 155x arasındadır.

Edit2: C ++ çözümü gibi 15x kadar rasgele sayı oluşturur. Birinin, rastgele bir int'yi Fortran'da 0'lı ve 1'li bir diziye dönüştürmek için sıfır bir tepegöz çözümü varsa, ben bütün kulaklarım olur. Sonra C ++ 'ı yenebiliriz :)

Edit3: İlk düzenleme Lembik'in dediği gibi bir hata yaptı. Bu, şu anda sabittir, hızlanmada küçük bir gelişme ile. Daha fazla hız almak için Eelvex'in önerisini kullanmaya çalışacağım.

Düzen4: Profil oluşturma, nint () öğesinin tamına ve tamına geri dönüştürülmesinin yavaş olduğunu belirtti. Bunu, hem ölçekleme hem de yuvarlama yapan bir tamsayı bölmesiyle değiştirdim, 160x'ten 316x'e kadar hızlandı.

İle derleyin:

gfortran -O3-march = yerel -fopenmp golf.f90

program golf
implicit none
integer, parameter :: m=8, n=8
integer :: F(n), S(m+n-1), leadingzerocounts(m)
integer :: j,k,bindec,enc,tmp,x=123456789,y=362436069,z=521288629,w=88675123
integer*2 :: i
real :: r

leadingzerocounts=0

!$OMP parallel do private(i,enc,j,bindec,S,F,k,tmp,x,y,z,w,r) reduction(+:leadingzerocounts) schedule(dynamic)
do i=0,32766
  enc=i
  ! Short loop to convert i into the array S with -1s and 1s
  do j=16,2,-1
    bindec=2**(j-1)
    if (enc-bindec .ge. 0) then
      S(j-1)=1
      enc=enc-bindec
    else
      S(j-1)=-1
    endif
  end do
  do j=1,1000
    F=0
    do while (.not. any(F /= 0))
      do k=1,n
        ! Start Xorshift RNG
        tmp = ieor(x,ishft(x,11))
        x = y
        y = z
        z = w
        w = ieor(ieor(w,ishft(w,-19)),ieor(tmp,ishft(tmp,-8)))
        ! End Xorshift RNG
        ! Just scale it inside the nint:
        !F(k)=nint(w/2147483648.0)
        ! Scaling by integer division is faster, but then we need the random 
        ! number to be in (-2,2) instead of [-1,1]:
        F(k)=w/1073741824

      end do
    end do
    do k=1,m
      if (dot_product(F,S(k:k+n-1)) /= 0) exit
      leadingzerocounts(k)=leadingzerocounts(k)+1
    end do
  end do
end do
!$OMP end parallel do

print *, leadingzerocounts

end

Örnek çıktı:

$ Zaman ./a.out
6329624 2524831 1039787 438809 193044 6860 40486 19517
1.45s kullanıcı 0.00s sistem 746% cpu 0.192 toplam ./a.out

OP'nin kodu:

$ time pypy golf.py
pypy golf.py 60.68s kullanıcı 0.04s sistemi 99% cpu 1: 00.74 toplam


J'de kullandığım şey, base-4'teki 4 ^ n sayısının önceden oluşturulmuş bir listesiydi, daha sonra triadic'e dönüştürüldü ve 0 hariç tutuldu. RNG bu listeden seçildi.
Eelvex

Kodunuzun doğru olduğundan emin değilim. 100.000 yineleme için 633140285 271390368 118307997 52751245 23725837 10744292 4944464 2388125’i alıyorum, ancak 633170604 252560981 104156146 43911426 19316309 8713324 4073378 19540’a yakın olması gerektiğini düşünüyorum.

1
Ah, teşekkürler, @Lembik, hızlandırma düzenlemem (if-ifadesini kaldırma) gerçekten bir hataydı. Kodumu güncellediğim için şimdi doğru olmalı. Daha sonra Eelvex'in önerisini kullanarak bir sürüm yayınlamaya çalışacağım.
yarı ekstrinsik

Bu da hızlandırdı, öyle görünüyor!

Evet, hafif hız sanırım. 1.0 eklediğimi ve ardından dar bir döngü içerisine 1.0 çıkardığımı fark ettim.
yarı ekstrinsik
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.