Python thực sự chậm như thế nào (Phần II)?


52

Đây là phần tiếp theo của Python thực sự chậm như thế nào? (Hoặc ngôn ngữ của bạn nhanh như thế nào?) .

Hóa ra việc tăng tốc độ x100 cho câu hỏi cuối cùng của tôi là quá dễ dàng. Đối với những người thích thử thách nhưng muốn một cái gì đó khó hơn, nơi họ thực sự có thể sử dụng các kỹ năng cấp thấp của mình, đây là phần II. Thách thức là để có được một tốc độ tăng tốc x100 cho mã python sau đây như được thử nghiệm trên máy tính của tôi.

Để làm cho nó khó khăn hơn tôi đang sử dụng pypy lần này. Thời gian hiện tại đối với tôi là 1 phút và 7 giây bằng pypy 2.2.1.

Quy tắc

  1. Người đầu tiên gửi mã mà tôi có thể chạy, là chính xác và nhanh hơn x100 lần trên máy của tôi sẽ được thưởng 50 điểm.
  2. Tôi sẽ trao phần thắng cho mã nhanh nhất sau một tuần.
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

Đầu ra phải tương tự như

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

Bạn phải sử dụng một hạt giống ngẫu nhiên trong mã của mình và bất kỳ trình tạo số ngẫu nhiên nào đủ tốt để đưa ra câu trả lời gần với ở trên sẽ được chấp nhận.

Máy của tôi Thời gian sẽ được chạy trên máy của tôi. Đây là bản cài đặt Ubuntu tiêu chuẩn trên Bộ xử lý tám lõi AMD FX-8350. Điều này cũng có nghĩa là tôi cần để có thể chạy mã của bạn.

Giải thích mã

Mã này lặp lại trên tất cả các mảng S có độ dài n + m-1 được tạo thành cho -1 và 1 giây. Đối với mỗi mảng S, nó lấy mẫu 1000 mảng ngẫu nhiên khác không F có độ dài n được tạo thành -1,0 hoặc 1 với xác suất 1/4, 1/2, / 14 của mỗi giá trị. Sau đó, nó tính toán các sản phẩm bên trong giữa F và mỗi cửa sổ có độ dài n cho đến khi tìm thấy một sản phẩm bên trong khác không. Nó thêm 1 leadingzerocountsvào mỗi vị trí, nó tìm thấy một sản phẩm bên trong bằng không.

Trạng thái

  • Perl . Chậm lại 2,7 lần bởi @tobyink. (So ​​với pypy không cpython.)

  • J . Tăng tốc 39 lần bởi @Eelvex.

  • C . Tăng tốc 59 lần bởi @ace.
  • Julia . Nhanh hơn 197 lần không bao gồm thời gian khởi động trước @ một phút nữa. Tăng tốc 8,5 lần bao gồm cả thời gian khởi động (nhanh hơn khi sử dụng 4 bộ xử lý trong trường hợp này hơn 8).
  • Fortran . 438 lần tăng tốc bởi @ bán ngoài.
  • Rpython . Tăng tốc gấp 256 lần bởi @primo.
  • C ++ . Tăng tốc 508 lần bởi @ilmale.

(Tôi đã dừng thời gian cải tiến mới vì chúng quá nhanh và quá nhỏ.)


Nó đã chỉ ra rằng thời gian dưới một giây là không đáng tin cậy và cũng có một số ngôn ngữ có chi phí khởi đầu. Đối số là nếu bạn bao gồm rằng bạn cũng nên bao gồm thời gian biên dịch của C / C ++, v.v ... Dưới đây là thời gian cho mã nhanh nhất với số lần lặp tăng lên 100.000.

  • Julia . 42 giây bởi @ thêm một phút nữa.
  • C ++ . 14 giây bởi @GuySirton.
  • Fortran . 14s bởi @ bán ngoài.
  • C ++ . 12s bởi @ilmale.
  • Rpython . 18s bởi @primo.
  • C ++ . 5 bởi @Stefan.

Người chiến thắng là .. Stefan!

Theo dõi thử thách được đăng. Làm thế nào cao, bạn có thể đi? (Một thách thức mã hóa + thuật toán) . Cái này khó hơn.


3
một lời giải thích về những gì mã được cho là sẽ đạt được sẽ tốt, vì vậy chúng ta có thể viết lại nó và không chỉ đơn giản là chuyển nó
Einacio

6
" Người đầu tiên gửi mã mà tôi có thể chạy, là chính xác và nhanh hơn x100 lần trên máy của tôi sẽ thắng ngay lập tức và cuộc thi kết thúc. " Mục đích của việc đóng cuộc thi như thế là gì? Tại sao không sử dụng thời hạn ngày như hầu hết những người khác, vì vậy chúng ta có thể thấy nó giảm hơn nữa trong các ngôn ngữ khác?
GrovesNL

5
@Einacio Đó là một ý tưởng hay. Tôi đã thay đổi các quy tắc mà tôi hy vọng không ai sẽ bận tâm.

1
@Lembik Tôi đã cải tiến phiên bản Fortran của mình, làm cho nó nhanh hơn gấp 2 lần trên máy của tôi. Bạn có thể thời gian một lần nữa? :)
bán ngoài

1
@ bán ngoài Xong.

Câu trả lời:


12

Ma thuật bit C ++

~ 16ms đa luồng, 56ms singlethreaded. ~ 4000 tăng tốc.

(tăng tốc dựa trên mã đa luồng trên i7-2820QM của tôi và 1 phút 9 giây được đề cập trong câu hỏi. Vì hệ thống của OP có hiệu suất xử lý đơn luồng kém hơn CPU của tôi nhưng khả năng xử lý đa luồng tốt hơn tôi hy vọng con số này là chính xác)

Phần đa luồng khá kém hiệu quả do sự sinh sản của các luồng. Tôi có thể có thể làm tốt hơn bằng cách tận dụng thư viện công việc tùy chỉnh của mình nhưng cái đó có lỗi trong các hệ thống unix .. Để giải thích và mã gần như giống hệt nhau mà không cần luồng, hãy tham khảo https://codegolf.stackexchange.com/a/26485/20965 .

biên tập

Tôi đã cho mỗi luồng là RNG của riêng nó và cắt giảm độ dài bit xuống còn 32 để giảm thời gian chạy xuống vài ms.

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

Đầu ra mẫu:

   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,

Đầu ra không đúng tôi nghĩ, có lẽ có lỗi? So sánh với những gì trong câu hỏi. Cụ thể, cột cuối cùng phải là một số gần với 19180.

@Lembik tôi có thể thấy những gì bạn có ý nghĩa. Tôi nghĩ rằng đầu ra ngẫu nhiên không đủ ngẫu nhiên mà đôi khi tạo ra đầu ra sôi nổi. Với trình tạo ngẫu nhiên C ++ 11, nó hoạt động tốt. Tôi sẽ sửa mã sau hôm nay.
Stefan

Tôi nhận được Stefan.cpp: 104: 101: error: 'memcpy' không được khai báo trong phạm vi này memcpy (out.data () + (threadId * m), ledZeros.data (), sizeof (ledZeros [0]) * m );

Tôi nghĩ rằng tôi cần phải bao gồm chuỗi.h. Thử lại lần nữa.
Stefan

Bạn biên dịch cái này với g ++ -O3 -std = c ++ 0x -pthread -Wl, - không cần thiết Stefan.cpp -o Stefan

16

C ++ x150 x450 x530

Thay vì mảng tôi đã sử dụng bit (và ma thuật bóng tối).
Cảm ơn @ace cho chức năng ngẫu nhiên nhanh hơn.

Làm thế nào nó hoạt động: các bit thứ 15 đầu tiên của số nguyên sđại diện cho mảng S[15]; các số 0 đại diện cho -1, các số 0 đại diện cho +1. Các mảng Fđược xây dựng theo cách tương tự. Nhưng với hai bit cho mỗi biểu tượng.

  • 00 đại diện cho -1
  • 01 và 10 đại diện cho 0
  • 11 đại diện cho 1

Nguyên nhân SFcó một đại diện khác tôi phải xen kẽ Svới chính nó để có thể so sánh với F.

  • 0 (-1) trở thành 00 (-1 trong đại diện của F)
  • 1 (+1) trở thành 11 (+1 trong đại diện của F)

Bây giờ chúng ta chỉ cần sử dụng Carnot để tính toán sản phẩm bên trong. Hãy nhớ rằng một biến chỉ có thể giả sử giá trị 00 hoặc 11

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)

Hình như không phải là xor với tôi. :)

Sum những cái chỉ là một trò chơi của sự thay đổi và mặt nạ, không có gì thực sự phức tạp.

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

Đây là một đầu ra mẫu:

6332350 2525218 1041716 438741 192917 87159 41023 19908 

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

Chương trình đã được biên soạn với:

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

trên Fedora 20 với gcc 4.8.2 Cpu là i7 8core.

Có lẽ tôi có thể đạt được một số tham số biên dịch điều chỉnh ms.

Trong khi đây là thời gian giải pháp OP trên máy của tôi:

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

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

Biên tập:

Chỉ cần thêm openmp và thay đổi thứ tự cho tôi có mức tăng x3, dẫn đến cải thiện hiệu suất x450 so với mã OP. : D Trong trường hợp này, leadingZeromảng phải là nguyên tử. Toàn cầu ngẫu nhiên ... là ngẫu nhiên, chúng sẽ ngẫu nhiên hơn.

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

cần thêm -fopenmpvào cờ biên dịch


Chỉnh sửa: 2 Khi suggester bởi user71404 Tôi đã thay đổi các hàm sumOnes và sumArray và bây giờ nó rất nhanh.

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

Với openmp chậm hơn, khiến các nguyên tử thêm quá nhiều chi phí.

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

Không có nguyên tử thậm chí còn nhanh hơn, nhưng tôi nhận được kết quả sai.

2137992 1147218 619297 321243 155815 70946 32919 15579

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

Để hiểu sumArray, hãy xem xét rằng 16 bit đại diện và mảng 8 số.
00 không có 1 và đại diện cho -1
01 và 10 có một 1 và đại diện cho 0
11 có hai 1 và đại diện cho 1
Vì vậy, số lượng bit được tích hợp thành 1 [ http://en.wikipedia.org/wiki/ Hamming_ weight] và với mỗi nhóm, chúng tôi xóa 1. Tuyệt vời.

sumOnes chỉ là ma thuật đen.

Ở đây các cờ biên dịch mới nhất và mã.

gcc -std = c ++ 11 -mfpmath = sse -O3 -flto -march = bản địa -funroll-loops -Wall -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;
}

Bây giờ tôi không thể chờ đợi để kiểm tra điều này! Đáng buồn thay, điều này sẽ không được trong một vài giờ.

1
Sau đây là trong một chỉnh sửa được đề xuất, nhưng tôi nghĩ rằng nó có thể phù hợp hơn như là một nhận xét. Bạn có thể thay thế sumOnes, sumArray của mình bằng cách sau (dường như giúp tôi tăng tốc độ gấp 2 lần so với phiên bản openmp). 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; }điều này đã được đề xuất bởi @ user71404
ace_HongKongInependence

@ user71404: profiler nói rằng chương trình đã dành toàn bộ thời gian cho hai chức năng đó, nhưng hôm qua tôi thực sự mệt mỏi vì nghĩ điều gì đó tốt hơn thế. Tôi sẽ thử tối nay (UTC). Cảm ơn.
ilmale

Bạn có phiền khi thay đổi đoạn mã thứ hai thành bản sao đầy đủ và mã có thể dán được không? Tôi phải làm điều gì đó sai trong nỗ lực của mình để làm cho mã openmp của bạn hoạt động để điều này sẽ giúp ích rất nhiều.

Đẹp. Tôi nghĩ rằng điều này có thể được thực hiện với các hoạt động bit.
Guy Sirton

10

Julia: 0,7, nhanh hơn 120 lần

Như user20768 đã chứng minh, một cổng mã đơn giản cho Julia nhanh gấp đôi PyPy. Nhưng chúng ta có thể làm tốt hơn thế nhiều.

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

Bạn có thể chạy nó bằng cách sử dụng julia -p 8 -e 'require("golf.jl");main()'(8 là số lượng quá trình, bạn có thể muốn chơi xung quanh nó). Trên bản phát hành mới nhất của Julia, việc này mất 0,7 giây so với 1m22 cho PyPy.

Nếu bạn có đủ số lõi trên máy tính của mình và có thể quay một vài trường hợp AWS, bạn sẽ có thể tắt thêm một số :)


Tôi khá chắc chắn rằng bạn đang đo sai thời gian. Python với Pypy cũng là ngôn ngữ dựa trên JIT, nhưng thời gian được tạo bởi OP bao gồm thời gian biên dịch JIT. Bạn đang loại trừ nó. Tôi đã cài đặt phiên bản Julia git mới nhất và kiểm tra mã của bạn, và trên máy của tôi, lệnh của bạn với 8 quy trình mất 6,6 giây để hoàn thành, nhưng nó in "thời gian trôi qua 0,588 .. giây".
bán ngoài

Thời gian Python không bao gồm khởi động PyPy và khởi động JIT, nhưng mất tối đa vài giây - sự khác biệt trong một phút thời gian chạy là không đáng kể. Tôi rất vui nếu OP thay đổi cách tính thời gian của Python (nó sẽ không tạo ra sự khác biệt nào), nhưng kể cả thời gian khởi động của Julia sẽ không hợp lý.
một phút nữa

Tôi đã hỏi OP trong các bình luận cho câu hỏi ban đầu và anh ấy nói "Thời gian nên bao gồm mọi thứ cho các ngôn ngữ JIT." Ông cũng tuyên bố sẽ tạo ra một thử thách mới trong đó các giải pháp sẽ mất nhiều thời gian hơn 1 giây, khiến Julia tham gia cuộc thi.
bán ngoài

Trong trường hợp đó, giải pháp tối ưu là sử dụng thuật toán nối tiếp - mất khoảng 2 giây. Tôi đã xuất bản mã nhưng cuộc thi này hiện có phần dư thừa, vì mọi người đều biết rằng C ++ khởi động nhanh hơn mọi thứ khác.
một phút nữa

Tôi vừa đăng giải pháp Fortran của mình, vì vậy tôi không hiểu tại sao bạn không nên đăng bài Julia nhanh nhất (nếu bạn đã có mã).
bán ngoài

5

C, 1.210s

Với mã của OP chạy 1m45.729 trên máy của tôi.

Tổng hợp:

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

Cảm ơn đặc biệt: @dyp cho các cờ tổng hợp và ý tưởng để tối ưu hóa.

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

Đầu ra mẫu:

6334411 2527506 1042239 439328 192914 87005 40847 19728

1
Thật thú vị, tôi có thể thực hiện các quan sát tương tự khi thả tất cả các cờ tối ưu hóa đó. Hãy thử -march=native -fwhole-program -fstrict-aliasing -ftree-vectorizeBtw. Tôi đã giảm xuống <4 giây bằng cách sử dụng một số C ++ 11 bao gồm MT19937 cộng với a uniform_int_distribution.
dyp

1
1.119s tăng tốc khoảng 59!

1
@ace Có, tôi chỉ muốn chỉ ra điều này. Nó đơn giản hơn đối với tôi chỉ để thử một số PRNG thư viện tiêu chuẩn trong C ++. Lưu ý rằng bạn có thể sử dụng một kết quả số nguyên 32 bit từ PRNG để tạo 8 mục nhập cho F.
dyp

1
nbằng 8, nên bạn có thể sử dụng AVX (hoặc 2 * SSE) để tính toán sản phẩm dot với một Sbộ lưu trữ thích hợp .
Michael M.

2
Phiên bản SSE, tăng tốc nhỏ: gist.github.com/anonymous/11394210 (đừng quên bao gồm smmintrin.h)
Michael M.

5

Perl

Đây không phải là nhanh như giải pháp C, nhưng là khá nhanh đối với một ngôn ngữ được giải thích cấp cao tôi nghĩ. Nó giảm khoảng 40% thời gian chạy khi thực hiện Python.

#!/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;

Thuật toán :: Combinatorics có sẵn trong Ubuntu ( sudo apt-get install libalgorithm-combinatorics-perl). Các mô-đun khác được sử dụng là các mô-đun Perl lõi, do đó, nên đã được cài đặt như một phần của cài đặt Ubuntu cơ sở.


1
Nó sẽ không ảnh hưởng đến tốc độ, nhưng 0..N-1cuối cùng mapthì nó cũng phải không? Bạn đã quên use warnings? :-) Mặc dù logic trong OP khó hiểu, cửa sổ trượt không bao giờ đến phần tử cuối cùng của S.
dùng2846289

Ah. Tôi chỉ hình dung rằng các mảng có kích thước không khớp, vì vậy tôi đã vô hiệu hóa warningscho phép các phần tử bị thiếu được coi là 0. N-1cải thiện điều này. Và nó thực sự cải thiện tốc độ rất nhẹ - giờ đây nhanh hơn khoảng 40% so với triển khai Python.
tobyink

Tôi nghĩ rằng mã của bạn yêu cầu một phiên bản List :: Util rất hiện đại. Trên Ubuntu 14.04, tôi nhận được "bất kỳ" không được xuất bởi mô-đun List ::

Ồ vâng, điều đó đúng - có lẽ bạn sẽ cần phải cài đặt Danh sách :: Sử dụng CPAN. anythay vào đó có thể được tìm thấy trong List :: MoreUtils, mặc dù không phải mô-đun lõi là một trong những mô-đun CPAN được sử dụng phổ biến nhất.
tobyink

4

Julia: chậm hơn 4,66 lần!

Tôi thực sự bắt đầu nghi ngờ số liệu thống kê trên trang web của họ ...

Lưu ý rằng mã Julia sau đây thực sự là phiên âm trực tiếp mã Python của OP mà không có bất kỳ tối ưu hóa nào. Tôi sử dụng time()chức năng để loại trừ thời gian khởi động chậm của Julia ...

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 giây

Mã OP trong PyPy: 1 m 11.506 s

Đầu ra của Julia:

6332170
2525472
1041522
438761
193119
86873
40705
19662

7
+1 cho khả năng thể thao <s> không biết xấu hổ </ s> của bạn.
ace_HongKongInependence

Biến toàn cầu, nhập khẩu và hiểu mảng là chậm. Đây không phải là cách người ta thường viết một chương trình Julia cho hiệu suất.
Alex A.

4

RPython 0.187s (nhanh hơn 256 lần)

Nguồn gốc w / PyPy2.2.1: 1m 6.718s

Bây giờ với luồng, hỗ trợ ngược cho Python chuẩn đã bị loại bỏ. Số lượng luồng công nhân có thể được chỉ định làm tham số dòng lệnh, mặc định là hai.

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 là một tập hợp con hạn chế của Python, có thể được dịch sang C và sau đó được biên dịch bằng RPython Toolchain . Mục đích bày tỏ của nó là hỗ trợ việc tạo ra các trình thông dịch ngôn ngữ, nhưng nó cũng có thể được sử dụng để biên dịch các chương trình đơn giản như chương trình trên. Hầu hết các tính năng 'fancier' của Python, chẳng hạn như itertoolshoặc thậm chí mapkhông có sẵn.

Để biên dịch, tạo một bản sao cục bộ của kho lưu trữ pypy hiện tại và chạy như sau:

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

Kết quả thực thi sẽ được đặt tên convolution-choặc tương tự trong thư mục làm việc hiện tại.

Tôi đã tham số hóa các biến đầu vào, vì vậy chương trình sẽ được chạy dưới dạng:

convolution-c 8 8 1000

để phù hợp với mã mẫu.


Ghi chú thực hiện

S in itertools.product([-1,1], repeat = n+m-1)trở thành S in xrange(1<<n+m-1), diễn giải Sdưới dạng bản đồ bit: [ 0, 1] → [ -1, 1]

Tương tự như vậy, Fcũng là một bản đồ bit, với mỗi hai bit đại diện cho một giá trị duy nhất:
[ 00, 01, 10, 11] → [ -1, 0, 0, 1]

Một bảng chân lý được sử dụng để tra cứu sản phẩm, thay vì thực hiện một ứng dụng.

Bởi vì số nguyên có chữ ký 32 bit được sử dụng, ncó thể không lớn hơn 15 và n+mkhông lớn hơn 31. Có thể đạt được hỗ trợ số nguyên tùy ý với rpython.rlib.rbigintmô-đun, nếu cần.

Lặp lại đầu tiên của vòng lặp sản phẩm chấm không được kiểm soát và kết hợp với kiểm tra vô hiệu của F.

Một PRNG homebrew được sử dụng, nguồn được liệt kê. Tác giả của bài báo chứng minh khoảng thời gian 2 32 -1, và tuyên bố rằng nó vượt qua tất cả các bài kiểm tra Diehard lưu một, mặc dù cá nhân tôi chưa xác nhận điều này.

Hạt giống ngẫu nhiên thay đổi mỗi mili giây, tương đương với việc sử dụng dấu thời gian sẽ cho phép. Ngoài ra, mỗi luồng công nhân xorlà id tiến trình của họ với giá trị này, để đảm bảo rằng mỗi luồng có một hạt giống khác nhau.


Thời gian mẫu

2 chủ đề công nhân:

$ 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 chủ đề công nhân:

$ 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 chủ đề công nhân:

$ 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

Nguồn gốc của OP:

$ 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

Thời gian cho 100000 lần lặp:

$ 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

Tôi chưa bao giờ thấy một chương trình rpython trước đây. Điều đó thật tuyệt. Bây giờ có một chương trình python thuần tương đương mà pypy có thể chạy trong 1.03s không?

@Lembik Tôi muốn xem một cái. Tôi nghĩ rằng 4.7s là khá tốt, vì nỗ lực đầu tiên của tôi đối với trăn thuần là ~ 15s.
primo

Vâng, xin lỗi vì sự chậm trễ. Tôi chưa có mã chạy nhưng sẽ càng sớm càng tốt.

Bạn nên thử thêm JIT. Bây giờ THAT sẽ nhanh thôi!
kirbyfan64sos

@Lembik cảm ơn vì đã đề cập;) Vì tò mò, nó đã chạy nhanh nhất với 4 luồng công nhân, hay 8?
primo

3

Julia: 1 phút 21,4 giây (nhanh hơn 2,2 lần) (sửa đổi mã của Arman)

Mã Op trong PyPy: 3 phút 1,4 giây

Cả hai được thực hiện trong REPL, không bao gồm thời gian để tải các gói.

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 

Có một số vấn đề với mã của Arman khiến nó rất chậm: Nó sử dụng rất nhiều hàm ẩn danh và các hàm bậc cao hơn một cách không cần thiết. Để kiểm tra xem tất cả các vectơ F có bằng không, tại sao không chỉ viết tất cả (F == 0) thay vì tất cả (x-> x == 0, F)? Nó ngắn hơn, và nhanh hơn gấp ngàn lần.

Nó cũng sử dụng sum (map (*, x, y)) làm sản phẩm chấm thay vì chỉ đơn giản là dấu chấm (x, y). Phiên bản đầu tiên chậm hơn 650 lần cho vectơ 10k nhân đôi. Và chức năng sản phẩm chấm được thực hiện như một vòng lặp for trong Julia thuần túy.

Ngoài ra, hiểu mảng là chậm. Tốt hơn là viết [0,1,0, -1] [rand (1: 4, n)] thay vì [[-1 0 0 1] [rand (1: 4)] cho j = 1: n] .

Cuối cùng, các biến toàn cầu là juju xấu ở Julia. Julia chỉ nhanh nếu bạn viết mã theo cách cho phép JIT và gõ suy luận hoạt động. Một phần lớn của điều này là sự ổn định của kiểu: Trình biên dịch phải có thể chắc chắn rằng kiểu của một biến sẽ không thay đổi trong khi bên trong một vòng lặp, chẳng hạn.


Cảm ơn! Tôi thấy rằng tôi vẫn còn khá nhiều điều để tìm hiểu về Ngôn ngữ Julia trước khi tôi có thể đưa ra tuyên bố về tốc độ của nó :) Thật sự vui mừng khi thấy một vài sửa chữa nhỏ cho mã của tôi tăng thời gian thực hiện của nó lên gấp nhiều lần.
thạch nhanh nhẹn

2

Nimrod

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

Ví dụ đầu ra:

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

Nimrod biên dịch thành C, do đó, sự lựa chọn trình biên dịch C cho các vấn đề phụ trợ cũng vậy.

Sử dụng tiếng kêu, biên dịch với:

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

Sử dụng gcc, biên dịch với:

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

Bỏ qua --passc:-fltonếu bạn có trình biên dịch C cũ hơn không hỗ trợ LTO. Bỏ qua --cc=...tùy chọn nếu bạn ổn với lựa chọn mặc định cho trình biên dịch C. Mã yêu cầu Nimrod 0.9.4 hoặc 0.9.5 .

Trên iMac bốn nhân của tôi (2,66 GHz core i5), mã chạy trong khoảng 0,15 giây với gcc 4,9, 0,16 giây với tiếng kêu, so với 88 giây cho PyPy 2.2.1 (tức là tăng tốc hơn 500 lần). Thật không may, tôi không có quyền truy cập vào một máy có hơn bốn lõi cũng đã cài đặt PyPy hoặc nơi tôi có thể dễ dàng cài đặt PyPy, mặc dù tôi nhận được khoảng 1 giây (có nhiều tiếng ồn đo lường) trên AMD 64 lõi Opteron 6376 1,4 GHz (theo / Proc / cpuinfo) với gcc 4.4.6.

Việc triển khai cố gắng trung thành với bản gốc thay vì tối ưu hóa mã với chi phí dễ đọc, trong khi không đưa ra các tối ưu hóa rõ ràng. Thật thú vị, đệ quy đuôi initVecRand()nhanh hơn một chút so với vòng lặp với lệnh ngắt với cả gcc và clang. Việc tự điều khiển một convolvevòng lặp của vòng kiểm tra bên trong vòng lặp chính cũng tạo ra sự tăng tốc, có lẽ là do dự đoán nhánh tốt hơn.


Làm thế nào để bạn có được nimrod cho ubfox?

@Lembik Một tìm kiếm nhanh trên Google sẽ cung cấp cho bạn nimrod-lang.org/doad.html
ace_HongKongInependence

@ace Tôi cũng đã bao gồm liên kết trong bài đăng của mình (mặc dù bây giờ thật khó nhìn với màu xanh trên nền đen khi tôi nhìn vào nó).
Reimer Behrends

Bạn có thể tăng tốc độ này nhiều hơn nữa bằng cách tăng kích thước hạt giống lên 128 bit: xorshift.di.unimi.it
user60561

2

Java

Tôi đã dịch giải pháp C ++ ở trên sang Java:

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

Trên máy của tôi, tôi nhận được đầu ra sau cho chương trình java:

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

Chương trình OP chạy khoảng 53 giây trên máy của tôi:

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

Chương trình c ++ chỉ thực hiện khoảng 0,15 giây:

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

Đó là nhanh hơn khoảng 2,5 lần so với giải pháp java tương ứng (Tôi không loại trừ khởi động VM). Các giải pháp java này nhanh hơn khoảng 142 lần so với chương trình được thực hiện với PyPy.

Vì tôi rất quan tâm, tôi đã đặt itersthành 100_000 cho Java và C ++ nhưng hệ số 2.5 không giảm khi ủng hộ Java nếu mọi thứ trở nên lớn hơn.

EDIT: Tôi đã chạy các chương trình trên PC Arch Linux 64 bit.

EDIT2: Tôi muốn thêm rằng tôi đã bắt đầu với một bản dịch thô của mã python:

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

Chương trình này chạy khoảng 3,6 giây:

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

Đó là nhanh hơn khoảng 14 lần so với giải pháp PyPy. (Việc chọn chức năng ngẫu nhiên tiêu chuẩn so với chức năng fastRandom dẫn đến thời gian thực hiện là 5 giây)


2

Python 3.5 + numpy 1.10.1, 3.76 giây

Các bài kiểm tra đã được chạy trên Macbook Pro của tôi. Mã của OP mất ~ 6 phút trên cùng một máy.

Lý do tôi trả lời câu hỏi này thực tế là vì tôi không có 10 danh tiếng và không thể trả lời Phần I :-p

Trong vài ngày qua, tôi đã cố gắng tìm ra cách thực hiện các kết cấu lớn một cách hiệu quả với numpy (mà không cần dựa vào gói của bên thứ ba, thậm chí là scipy). Khi tôi gặp một loạt các thử thách trong quá trình nghiên cứu, tôi quyết định thử. Tôi có thể đã đến trò chơi này đến muộn, nhưng đây là nỗ lực của tôi khi sử dụng Python 3.5 và numpy 1.10.1.

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)

Tôi đã tính toán trước các mảng S và F và làm phẳng mảng S trong khi thực hiện phép chập, điều này (dựa trên các thí nghiệm của tôi) có thể tận dụng tốc độ của np.convolve. Nói cách khác, vì tôi không tìm thấy thói quen tích chập vector hóa, tôi đã giả mạo mã hóa bằng cách làm phẳng toàn bộ mảng và hy vọng np.convolve sẽ thực hiện vector hóa dưới mui xe cho tôi, dường như nó đang hoạt động. Lưu ý tôi đã sử dụng mode = 'same' và cắt bớt các phần tử hàng đầu và dấu là vô dụng.

Trên Macbook Pro của tôi, kết quả kiểm tra cho 3,76 giây . Khi tôi chạy mã của OP (được sửa đổi thành Python 3.5), tôi có khoảng 6 phút . Việc tăng tốc là khoảng 100 lần.

Một nhược điểm là vì các mảng S và F sẽ được lưu trữ, nên yêu cầu bộ nhớ có thể là một vấn đề nếu kích thước quá lớn.

Tôi đã sử dụng phương pháp tương tự cho Phần I và tôi đã tăng tốc ~ 60-100x trên máy tính xách tay của mình.

Như tôi đã làm mọi thứ trên Macbook Pro, nếu ai đó có thể kiểm tra mã của tôi và cho tôi biết nó hoạt động như thế nào trên máy của bạn, tôi sẽ đánh giá cao nó rất nhiều!


1

J, 130x ~ 50x tăng tốc?

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''

Thời gian trên một debian ngẫu nhiên:

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

Tôi nghĩ rằng có chỗ để cải thiện.


Tập lệnh Python được cho là được thực thi bằng cách sử dụng pypy, không phải vậy python, đó là lý do tại sao tập lệnh của bạn dường như tăng tốc độ 130 lần.
ace_HongKongInependence

@ace có Tôi nhận thấy nhưng tôi không thể cài đặt pypy: - / Tôi nghĩ rằng thứ tự cường độ sẽ vẫn còn mặc dù.
Eelvex


Thật vậy, không nhất thiết.
Eelvex

Vấn đề gì bạn đã cài đặt pypy?

1

C ++: x200 (i7 4 nhân, nên chia tỷ lệ thành x400 trên 8 lõi)

Cố gắng cho một giải pháp C ++ 11 đơn giản hơn (Đã thử nghiệm với VS 2012, gcc và clang) với sự song song.

Để có được điều này để biên dịch và chạy trong Linux với gcc 4.8.1:

g ++ -O3 -msse -msse2 -msse3 -march = bản địa -std = c ++ 11 -pthread -Wl, - golf.cpp không cần thiết

Trong Linux, chúng ta cũng cần std::launch::asyncbuộc nhiều luồng. Tôi đã bỏ lỡ điều đó trong một phiên bản trước đó.

Trong Visual Studio (2012+), điều này chỉ hoạt động nhưng tạo bản phát hành để định thời gian ...

Trên i3 lõi kép cũ của tôi, nó chạy trong ~ 0,9 giây. Trên lõi tứ i7 của tôi, đây là 0,336 so với pypy 66 giây.

Trên i7 8 nhân, điều này sẽ nằm trong phạm vi tăng tốc x400. Chuyển sang mảng kiểu C sẽ tăng tốc nhưng tôi thích ở lại với các thùng chứa C ++. Đối với tôi thật thú vị khi thấy tốc độ bạn có thể đạt được trong khi vẫn ở gần miền vấn đề và ở mức tương đối cao, một điều tôi nghĩ C ++ thực sự tốt. Cũng cần lưu ý là sự dễ dàng tương đối của paralleization bằng cách sử dụng các cấu trúc C ++ 11.

Giải pháp bit của @ ilmale rất tuyệt vời và hoạt động cho -1/1/0. Người ta cũng có thể ném SSE vào việc này và có thể tăng tốc đáng kể.

Ngoài việc song song hóa còn có một "mánh" khác trong đó là giảm số lượng tổng. Kết quả mẫu: 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

Pháo đài: 316x

Được rồi, Fortran: Tôi đã tăng tốc lên tới 106x 155x 160x 316x khi sử dụng Xorshift RNG và OpenMP trên CPU i7 4 nhân. Ngoài ra, không có thủ đoạn lớn. Để iterator xây dựng S, tôi chỉ sử dụng biểu diễn nhị phân của số nguyên 16 bit i. Bạn sẽ lưu ý rằng ngoài RNG nội tuyến và "iterator" / ánh xạ từ i sang S, mã này cũng ở mức cao như mã Python.

Chỉnh sửa: đã xóa "nếu" trong Xorshift, bây giờ sử dụng "r = abs (w / ...)" thay vì "r = w / ...". Đi từ 106x đến 155x.

Edit2: Điều này tạo ra số lượng ngẫu nhiên gấp 15 lần so với giải pháp C ++. Nếu ai đó có giải pháp không chi phí để chuyển đổi một int ngẫu nhiên thành một mảng 0 và 1 trong Fortran, thì tôi là tất cả. Sau đó, chúng tôi có thể đánh bại C ++ :)

Edit3: Bản chỉnh sửa đầu tiên đã giới thiệu một lỗi, như Lembik đã chỉ ra. Điều này đã được sửa chữa, với một sự cải thiện nhỏ trong việc tăng tốc. Tôi sẽ cố gắng sử dụng đề xuất của Eelvex để có thêm tốc độ.

Edit4: Profiling chỉ ra rằng việc chuyển đổi thành số thực và trở lại số nguyên với nint () là chậm. Tôi đã thay thế điều này bằng một phép chia số nguyên thực hiện cả chia tỷ lệ và làm tròn, đi từ tăng tốc 160x lên 316x.

Biên dịch với:

gfortran -O3 -march = bản địa -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

Ví dụ đầu ra:

$ thời gian ./a.out
6329624 2524831 1039787 438809 193044 6860 40486 19517
./a.out 1.45s người dùng 0,00s hệ thống 746% cpu tổng cộng 0,192

Mã của OP:

$ time pypy golf.py pypy
golf.py 60,68 giây người dùng hệ thống 0,04 giây 99% tổng số 1: 00,74


Những gì tôi đã sử dụng trong J là một danh sách dựng sẵn gồm 4 ^ n số trong cơ sở 4, sau đó được chuyển đổi thành bộ ba và loại trừ 0. RNG chỉ chọn từ danh sách này.
Eelvex

Tôi không chắc mã của bạn là chính xác. Đối với 100.000 lần lặp, tôi nhận được 63140285 271390368 118307997 52751245 23725837 10744292 4944464 2388125 nhưng tôi nghĩ rằng nó nên gần hơn với 63170604 252560981 104156146 43911426 19316309 8713324 4073378.

1
À, cảm ơn, @Lembik, bản chỉnh sửa của tôi để tăng tốc (loại bỏ câu lệnh if) thực sự là một lỗi. Tôi đã cập nhật mã của mình để nó chính xác ngay bây giờ. Tôi sẽ cố gắng đăng một phiên bản bằng cách sử dụng đề xuất của Eelvex sau.
bán ngoài

Điều đó cũng tăng tốc nó dường như!

Vâng, tôi đoán là hơi tăng tốc. Tôi nhận ra rằng tôi đã thêm 1.0 và sau đó trừ 1.0 trong một vòng lặp chặt chẽ.
bán ngoài
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.