파이썬이 얼마나 느리게 진행됩니까 (2 부)?


52

이것은 파이썬이 얼마나 느리게 진행됩니까? (또는 언어가 얼마나 빠릅니까?) .

내 마지막 질문에 대해 x100 속도를 높이기가 너무 쉽다는 것이 밝혀졌습니다. 도전을 즐기고 있지만 저수준 기술을 실제로 사용할 수있는 더 어려운 것을 원하는 사람들을 위해 여기에 II 부가 있습니다. 문제는 내 컴퓨터에서 테스트 한 다음 파이썬 코드의 x100 속도를 높이는 것입니다.

더 어렵게하기 위해 이번에는 pypy를 사용하고 있습니다. pypy 2.2.1을 사용 하는 현재 시간은 1 분 7 초 입니다.

규칙

  1. 내가 실행할 수있는 코드를 제출 한 첫 번째 사람은 정확하며 내 컴퓨터에서 x100 배 더 빠릅니다. 현상금은 50 포인트입니다.
  2. 일주일 후에 가장 빠른 코드로 승리 할 것입니다.
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

출력은 다음과 유사해야합니다

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

코드에서 난수 시드를 사용해야하며 위와 가까운 답변을 제공 할 수있는 난수 생성기가 허용됩니다.

내 컴퓨터 타이밍이 내 컴퓨터에서 실행됩니다. 이것은 AMD FX-8350 8 코어 프로세서에 표준 우분투 설치입니다. 이것은 또한 코드를 실행할 수 있어야 함을 의미합니다.

코드 설명

이 코드는 -1과 1로 구성된 길이 n + m-1의 모든 배열 S를 반복합니다. 각 배열 S에 대해 -1,0 또는 1로 구성된 길이 n의 0이 아닌 임의의 1000 개의 배열 F를 각 값의 1/4, 1 / 2, / 14의 확률로 샘플링합니다. 그런 다음 0이 아닌 내부 제품을 찾을 때까지 F와 길이 n의 S의 각 창 사이의 내부 제품을 계산합니다. leadingzerocounts내부 위치가 0 인 각 위치에 1을 더합니다 .

지위

  • . @tobyink로 2.7 배 느려집니다. (python이 아닌 pypy와 비교)

  • J . @Eelvex로 39 배 속도 향상

  • C . @ace로 59 배 속도가 빨라집니다.
  • 줄리아 . 시작 시간을 1 분 더 단축하지 않고 197 배 더 빠릅니다. 시작 시간을 포함하여 8.5 배의 속도가 향상됩니다 (이 경우 8 개보다 4 개의 프로세서를 사용하는 것이 더 빠름).
  • 포트란 . @ semi-extrinsic으로 438 배가 빨라집니다.
  • Rpython . @primo로 258 배 속도를 높입니다.
  • C ++ . @ilmale의 508 배 속도.

(새로운 개선은 너무 빠르고 반복기가 너무 작기 때문에 타이밍을 멈췄습니다.)


1 초 미만의 타이밍은 신뢰할 수 없으며 일부 언어의 경우 시작 비용이 있습니다. C / C ++ 등의 컴파일 시간도 포함시켜야한다고 주장하는 것이 중요합니다. 반복 횟수가 10만으로 증가한 가장 빠른 코드의 타이밍은 다음과 같습니다.

  • 줄리아 . @ one-more- 분으로 42 초.
  • C ++ . @GuySirton이 14 초
  • 포트란 . @ semi-extrinsic에 의해 14s.
  • C ++ . @ilmale의 12s.
  • Rpython . @primo.
  • C ++ . @Stefan의 5 초

승자는 .. 스테판!

후속 과제가 게시되었습니다. 당신은 얼마나 높이 갈 수 있습니까? (코딩 + 알고리즘 도전) . 이것은 더 어렵다.


3
코드가 달성하고자하는 것에 대한 설명은 좋을 것이므로, 우리는 단순히 코드를 작성하지 않고 재 작성할 수 있습니다.
Einacio

6
" 내가 실행할 수있는 코드를 제출 한 첫 번째 사람은 정확하고 내 컴퓨터에서 x100 배 더 빠른 속도로 승리하고 경쟁은 종료됩니다. "경쟁을 끝내는 목적은 무엇입니까? 다른 대부분의 날짜와 같이 날짜 마감일을 사용하지 않는 이유는 무엇입니까?
grovesNL

5
@ 아 이나시오 좋은 생각입니다. 나는 아무도 신경 쓰지 않기를 바라는 규칙을 바꾸었다.

1
@Lembik 나는 포트란 버전을 개선하여 내 컴퓨터에서 다시 2 배 빠르게 만들었다. 다시 시간을 내주실 수 있습니까? :)
semi-

1
@ 반-외분비 완료.

답변:


12

C ++ 비트 매직

~ 16ms 멀티 스레드, 56ms 싱글 스레드 ~ 4000 속도 향상

(속도 향상은 i7-2820QM의 멀티 스레드 코드 및 질문에 언급 된 1 분 9 초를 기반으로합니다. OP의 시스템은 CPU보다 단일 스레드 성능이 좋지 않지만 다중 스레드 성능이 우수하기 때문에이 숫자가 정확할 것으로 예상합니다)

멀티 스레드 부품은 스레드 생성으로 인해 비효율적입니다. 사용자 정의 작업 라이브러리를 활용하여 더 잘 할 수는 있지만 유닉스 시스템에서는 버그가 있습니다 . 스레딩이없는 설명과 거의 동일한 코드는 https://codegolf.stackexchange.com/a/26485/20965참조하십시오 .

편집하다

각 스레드에 자체 RNG를 부여하고 비트 길이를 32로 줄이면 런타임이 몇 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;
}

샘플 출력 :

   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,

내가 생각하는 출력이 옳지 않다. 아마도 버그가 있습니까? 질문에있는 것과 비교하십시오. 특히 마지막 열은 19180에 가까운 숫자 여야합니다.

@Lembik 나는 당신이 무슨 뜻인지 알 수 있습니다. 무작위 출력이 무작위가 아니기 때문에 때로는 펑키 출력을 생성합니다. C ++ 11 랜덤 생성기로 제대로 작동합니다. 오늘 코드를 수정하겠습니다.
Stefan

Stefan.cpp : 104 : 101 : error : 'memcpy'가이 범위에서 선언되지 않았습니다. memcpy (out.data () + (threadId * m), leadingZeros.data (), sizeof (leadingZeros [0]) * m );

string.h를 포함해야한다고 생각합니다. 다시 시도하십시오.
Stefan

당신은, g ++ -O3 -std = C + +0 -pthread -Wl으로이 컴파일하지 않습니다 - 스테판 -o 더 것과 같이 필요한 Stefan.cpp에게

16

C ++ x150 x450 x530

배열 대신 비트 (및 다크 매직)를 사용했습니다.
더 빠른 랜덤 기능을 위해 @ace에게 감사드립니다.

작동 방식 : 정수의 첫 15 번째 비트 s는 배열을 나타냅니다 S[15]. 0은 -1을 나타내고 0은 +1을 나타냅니다. 배열 F은 비슷한 방식으로 빌드됩니다. 그러나 각 심볼마다 2 비트가 있습니다.

  • 00은 -1을 나타냅니다
  • 01과 10은 0을 나타냅니다
  • 11은 1을 나타냅니다

원인 SF인터리브하기 위해 내가 가진 다른 표현이 S과 비교할 수 자체를 F.

  • (-1) 0 00되었다 (-1의 표현 F)
  • 1 (+1)은 11이되었습니다 (표시에서 +1 F).

이제 Carnot을 사용하여 내부 제품을 계산할 수 있습니다. 하나의 변수는 값 00 또는 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)

나에게 xor가 아닌 것 같습니다. :)

그것들은 단순히 교대와 마스크의 게임이며, 실제로는 아무것도 복잡하지 않습니다.

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

다음은 샘플 출력입니다.

6332350 2525218 1041716 438741 192917 87159 41023 19908 

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

이 프로그램은 다음과 같이 컴파일되었습니다 :

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

gcc가 포함 된 Fedora 20에서 4.8.2 CPU는 i7 8core입니다.

아마도 컴파일러 매개 변수를 조정하는 ms를 얻을 수 있습니다.

내 컴퓨터의 OP 솔루션 시간이지만 :

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

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

편집하다:

openmp를 추가하고 x3 이득을 얻기 위해 순서를 변경하면 OP 코드에 비해 x450 성능이 향상됩니다. : D이 경우 leadingZero배열은 원자 적이어야합니다. 무작위 전역은 무작위입니다. 더 무작위 적입니다.

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

-fopenmp컴파일러 플래그 에 추가해야합니다


편집 : 2 user71404의 제안으로 sumOnes 및 sumArray 함수를 변경했으며 이제는 빠릅니다.

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

openmp가 느리면 원자가 너무 많은 오버 헤드를 발생시킵니다.

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

원자가 없으면 더 빠르지 만 잘못된 결과를 얻습니다.

2137992 1147218 619297 321243 155815 70946 32919 15579

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

sumArray를 이해하려면 16 비트가 8 개의 숫자를 나타내는 것으로 간주하십시오.
00에는 1이없고 나타내는 -1
01 및도 10는 하나의 1을 나타내는 0과
11 개의 1 초를 1을 나타내는
그래서 기본이 1로 설정된 비트의 수를 카운트 http://en.wikipedia.org/wiki/을 Hamming_weight] 와 각 그룹에 대해 1. 제거합니다.

sumOnes는 검은 마법입니다.

여기에 최신 컴파일 플래그와 코드가 있습니다.

gcc -std = c ++ 11 -mfpmath = sse -O3 -flto -march = 네이티브 -funroll-loops-벽 -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;
}

이제 이것을 테스트하기 위해 기다릴 수 없습니다! 슬프게도 이것은 몇 시간 동안 지속되지 않을 것입니다.

1
다음은 제안 된 수정 사항이지만 의견으로 더 적합하다고 생각합니다. sumOnes, sumArray를 다음과 같이 바꿀 수 있습니다 (openmp 버전보다 2 배 빠른 속도로 나타납니다). 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; }이것은 @ user71404에 의해 제안되었습니다
ace_HongKongIndependence

@ user71404 : 프로파일 러는 프로그램이 그 두 시간 동안 모든 시간을 보냈다고 말했지만 어제 피곤하다고 생각했습니다. 오늘 저녁에 시도 할 것입니다 (UTC). 감사.
ilmale

두 번째 코드 스 니펫을 전체 사본 및 붙여 넣기 가능한 코드로 변경 하시겠습니까? openmp 코드를 작동시키려는 시도에서 잘못된 일이 있어야 도움이 될 것입니다.

좋은. 비트 작업 으로이 작업을 수행 할 수 있다고 생각했습니다.
Guy Sirton

10

줄리아 : 0.7 초, 120 배 빠름

user20768에서 알 수 있듯이 Julia의 코드 포트는 PyPy보다 두 배 빠릅니다. 그러나 우리는 그보다 훨씬 더 잘할 수 있습니다.

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

julia -p 8 -e 'require("golf.jl");main()'8을 사용하여 프로세스를 실행할 수 있습니다. 최신 Julia 시험판에서는 PyPy의 경우 0.7m 대 1m22s가 필요합니다.

컴퓨터에 충분한 코어가 있고 아마도 몇 개의 AWS 인스턴스를 가동 시키면 더 많은 것을 제거 할 수 있습니다. :)


타이밍을 잘못 측정하고 있다고 확신합니다. Python with Pypy도 JIT 기반 언어이지만 OP에 의해 만들어진 타이밍 에는 JIT 컴파일 시간이 포함 됩니다. 당신은 그것을 제외하고 있습니다. 최신 Julia git 버전을 설치하고 코드를 테스트했으며 내 컴퓨터에서 8 개의 프로세스로 명령을 완료하는 데 6.6 초가 걸리지 만 "경과 시간 0.588 .. 초"가 표시됩니다.
세미 외부

Python 타이밍에는 PyPy 시작 및 JIT 예열이 포함되지만 최대 몇 초가 걸립니다. 1 분 동안의 런타임 차이는 무시할 수 있습니다. OP가 파이썬이 시간을 정하는 방식을 바꾸면 기쁘지만 (줄 바꿈이 없지만) Julia의 시작 시간을 포함시키는 것이 합리적이지 않습니다.
1 분 더 많은

나는 OP에게 원래 질문에 대한 의견을 물었고, 그는 "JIT 언어에 대한 모든 것을 타이밍에 포함시켜야한다"고 말했다. 그는 또한 솔루션이 1 초 이상 걸리는 Julia에게 경쟁에서 새로운 도전을 만들 것이라고 말했다.
semi-exrinsic

이 경우 최적의 솔루션은 직렬 알고리즘을 사용하는 것입니다. 약 2 초가 걸립니다. 코드를 게시했지만 C ++이 다른 모든 것보다 빠르게 부팅된다는 것을 모두가 이미 알고 있기 때문에이 경쟁은 다소 중복됩니다.

방금 Fortran 솔루션을 게시 했으므로 가장 빠른 Julia를 게시하지 않아야하는 이유를 알 수 없습니다 (이미 코드가있는 경우).
semi-exrinsic

5

C, 1.210 초

내 컴퓨터에서 OP 코드가 1m45.729s로 실행 중입니다.

편집:

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

특별 감사 : 컴파일 플래그에 대한 @dyp 및 최적화에 대한 아이디어.

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

샘플 출력 :

6334411 2527506 1042239 439328 192914 87005 40847 19728

1
실제로 흥미롭게도 모든 최적화 플래그를 삭제할 때 비슷한 관찰을 할 수 있습니다. -march=native -fwhole-program -fstrict-aliasing -ftree-vectorizeBtw를 보십시오 . MT19937 plus a를 포함한 일부 C ++ 11을 사용하여 <4 초로 떨어졌습니다 uniform_int_distribution.
dyp

1
대략 59의 속도를 올리는 1.119s!

1
@ace 예, 나는 이것을 지적하고 싶었습니다. C ++에서 표준 라이브러리 PRNG 중 일부를 시도하는 것이 더 간단했습니다. PRNG에서 하나의 32 비트 정수 결과를 사용하여에 대한 8 개의 항목을 생성 할 수 있습니다 F.
dyp

1
n이 같으 므로 8AVX (또는 2 * SSE)를 사용하여 적절한 S스토리지가 있는 내적을 계산할 수 있습니다.
Michael M.

2
SSE 버전, 작은 속도 향상 : gist.github.com/anonymous/11394210 (포함하는 것을 잊지 마십시오smmintrin.h )
Michael M.

5

이것은 C 솔루션만큼 빠르지는 않지만 고급 통역 언어에 대해서는 매우 빠릅니다. 파이썬 구현의 실행 시간을 약 40 % 줄입니다.

#!/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 ( sudo apt-get install libalgorithm-combinatorics-perl) 에서 사용할 수 있습니다 . 사용되는 다른 모듈은 코어 Perl 모듈이므로 기본 Ubuntu 설치의 일부로 이미 설치되어 있어야합니다.


1
속도에는 영향을 미치지 않지만 0..N-1마지막 범위에 map있습니다. 잊었습니까 use warnings? :-) OP의 논리가 혼란 스럽지만 슬라이딩 창은의 마지막 요소에 도달하지 않습니다 S.
user2846289

아 방금 배열의 크기가 일치하지 warnings않는다고 생각했기 때문에 누락 된 요소를 0으로 처리 하지 못하게했습니다. N-1이것을 향상시킵니다. 그리고 실제로 속도를 약간 향상시킵니다. 이제는 Python 구현보다 약 40 % 빠릅니다.
tobyink

코드에는 최신 버전의 List :: Util이 필요하다고 생각합니다. 우분투 14.04에서 "any"는 List :: Util 모듈에 의해 내보내지지 않습니다.

네, 맞습니다. CPAN에 List :: Util을 설치해야 할 것입니다. any코어 모듈은 아니지만 가장 일반적으로 사용되는 CPAN 모듈 중 하나이지만 List :: MoreUtils에서 찾을 수 있습니다.
tobyink

4

줄리아 : 4.66 배 느리다!

나는 그들의 웹 사이트에 대한 통계 를 의심 하기 시작했습니다 ...

다음 Julia 코드는 사실상 최적화없이 OP의 Python 코드를 직접 복사 한 것입니다. time()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")

줄리아 : 5 m 32.912s

PyPy의 OP 코드 : 1m 11.506s

줄리아 출력 :

6332170
2525472
1041522
438761
193119
86873
40705
19662

7
<s> shamelessness </ s> 스포츠맨십 +1
ace_HongKongIndependence

전역 변수, 가져 오기 및 배열 이해가 느립니다. 이것은 일반적으로 성능을 위해 Julia 프로그램을 작성하는 방법이 아닙니다.
Alex A.

4

RPython 0.187s (258x 빠름)

PyPy2.2.1이 포함 된 원본 소스 : 1m 6.718s

이제 스레딩을 통해 표준 Python에 대한 지원이 중단되었습니다. 작업자 스레드 수는 명령 줄 매개 변수로 지정할 수 있으며 기본값은 2입니다.

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 은 제한된 Python 하위 집합으로, C로 변환 한 다음 RPython Toolchain을 사용하여 컴파일 할 수 있습니다 . 표현 된 목적은 언어 통역사의 작성을 돕기위한 것이지만, 위와 같은 간단한 프로그램을 컴파일하는 데 사용될 수도 있습니다. 같은 파이썬의 '애호가'기능의 대부분 itertools도 또는 map사용할 수 없습니다.

컴파일하려면 현재 pypy 저장소 의 로컬 복제본을 만들고 다음을 실행하십시오.

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

결과 실행 파일은 convolution-c현재 작업 디렉토리에서 이름이 지정 되거나 유사합니다.

입력 변수를 매개 변수화 했으므로 프로그램을 다음과 같이 실행해야합니다.

convolution-c 8 8 1000

샘플 코드와 일치합니다.


구현 노트

S in itertools.product([-1,1], repeat = n+m-1)는 비트 맵으로 S in xrange(1<<n+m-1)해석 S하여 다음과 같이됩니다. [ 0, 1] → [ -1, 1]

이와 같이, F비트 맵이 단일 값을 각각 나타내는 두 비트와 함께,도 :
[ 00, 01, 10, 11] → [ -1, 0, 0, 1]

진리표는 다중화를 수행하지 않고 제품을 조회하는 데 사용됩니다.

32 비트 부호있는 정수가 사용 n되므로 15 n+m보다 크지 않고 31보다 크지 않을 수 있습니다 rpython.rlib.rbigint. 필요한 경우 모듈에서 임의의 정수 지원을 수행 할 수 있습니다 .

내적 루프의 첫 번째 반복이 풀리고의 nullity 테스트와 결합됩니다 F.

홈 브루 잉 PRNG가 사용되며 소스가 나열됩니다. 이 논문의 저자는 2 32 -1 의 기간을 보여 주었으며, 개인적으로 이것을 확인하지는 않았지만 모든 Diehard 테스트를 통과했다고 주장합니다.

임의의 시드는 밀리 초마다 변경되는데, 타임 스탬프를 사용하는 것만 큼 좋습니다. 또한 각 작업자 스레드 xor는이 값을 가진 프로세스 ID를 가지므로 서로 다른 시드를 갖습니다.


샘플 타이밍

2 개의 작업자 스레드 :

$ 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 개 :

$ 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 개 :

$ 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의 원본 출처 :

$ 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 회 반복 타이밍 :

$ 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

나는 전에 rpython 프로그램을 본 적이 없다. 대단하다. 이제 파이썬이 1.03에서 실행할 수있는 동등한 순수 파이썬 프로그램이 있습니까?

@Lembik 하나보고 싶습니다. 순수한 파이썬에 대한 첫 번째 시도는 ~ 15 초라는 점을 감안할 때 4.7 초가 꽤 좋다고 생각했습니다.
primo

예, 늦어서 죄송합니다. 아직 코드를 실행하지 않았지만 가능한 한 빨리 드리겠습니다.

JIT를 추가해야합니다. 이제는 빠를 것입니다!
kirbyfan64sos

@Lembik은 언급에 감사드립니다.
primo

3

줄리아 : 1 분 21.4 초 (2.2 배 빠름) (아르 만 코드 수정)

PyPy의 Op 코드 : 3 분 1.4 초

패키지를로드하는 시간을 포함하지 않고 REPL에서 수행됩니다.

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의 코드에는 속도를 느리게 만드는 몇 가지 문제가 있습니다. 불필요하게 많은 익명 함수와 고차 함수를 사용합니다. 모든 벡터 F가 0인지 테스트하려면 왜 all (x-> x == 0, F) 대신 all (F == 0)을 쓰지 않겠습니까? 더 짧고 문자 그대로 천 배 더 빠릅니다.

또한 단순히 dot (x, y) 대신 sum (map (*, x, y))를 내적으로 사용합니다. 첫 번째 버전 은 10k 벡터가 650 배 더 느립니다 . 그리고 도트 제품 함수는 순수 Julia에서 for 루프로 구현됩니다.

또한 배열 이해가 느립니다. j = 1 : n의 경우 [[-1 0 1] [rand (1 : 4)] 대신 [0,1,0, -1] [rand (1 : 4, n)]을 쓰는 것이 좋습니다. .

마지막으로 전역 변수는 Julia에서 나쁜 juju입니다. Julia는 JIT 및 형식 유추가 작동하는 방식으로 코드를 작성하는 경우에만 빠릅니다. 이것의 큰 부분은 타입 안정성입니다. 컴파일러는 예를 들어 루프 안에서 변수의 타입이 변경되지 않도록해야합니다.


감사! 나는 속도에 대한 주장을하기 전에 줄리아 언어에 대해 배울 점이 여전히 꽤 많다는 것을 알았습니다.
민첩 한천

2

님로드

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

출력 예 :

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

Nimrod는 C로 컴파일하므로 백엔드를위한 C 컴파일러의 선택도 중요합니다.

clang을 사용하여 다음과 같이 컴파일하십시오.

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

gcc를 사용하여 다음과 같이 컴파일하십시오.

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

--passc:-fltoLTO를 지원하지 않는 구형 C 컴파일러가있는 경우 생략 하십시오. --cc=...C 컴파일러의 기본 선택에 문제가 없으면 옵션을 생략하십시오 . 이 코드에는 Nimrod 0.9.4 또는 0.9.5 가 필요합니다 .

내 quadcore iMac (2.66GHz 코어 i5)에서 코드는 gcc 4.9에서 약 .15 초, clang에서 .16 초로 PyPy 2.2.1의 경우 88 초 (즉 500 배 이상의 속도 향상)와 비교됩니다. 불행히도, PyPy가 설치되어 있거나 PyPy를 쉽게 설치할 수있는 4 개 이상의 코어가있는 시스템에 액세스 할 수는 없지만 64 코어 AMD에서 약 0.1 초 (많은 측정 노이즈가 있음) gcc 4.4.6에서 Opteron 6376 1.4GHz (/ proc / cpuinfo에 따름).

구현은 가독성을 희생시키면서 코드를 최적화하는 것이 아니라 명확한 최적화를 포기하지 않고 원본에 충실하려고합니다. 흥미롭게도 꼬리 재귀는 initVecRand()gcc와 clang 모두에서 브레이크 명령이있는 루프보다 약간 빠릅니다. convolve메인 루프 내 에서 테스트 루프의 반복을 수동으로 풀면 분기 예측이 향상되어 속도가 향상 될 수 있습니다.


우분투에 대한 nimrod를 어떻게 얻습니까?


@ace 나는 또한 내 게시물에 링크를 포함시켰다.
Reimer Behrends

시드 크기를 128 비트로 늘림으로써이
user60561

2

자바

위의 C ++ 솔루션을 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));
  }
}

내 컴퓨터에서 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

OPs 프로그램은 내 컴퓨터에서 약 53 초 동안 실행됩니다.

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 ++ 프로그램은 약 0.15 초만 실행했습니다.

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

이는 해당 Java 솔루션보다 약 2.5 배 빠릅니다 (VM 시작을 제외하지 않았습니다). 이 자바 솔루션은 PyPy로 실행되는 프로그램보다 약 142 배 빠릅니다.

개인적으로 관심이 있었기 때문에 itersJava 및 C ++에 대해 100_000으로 설정 했지만 더 큰 것이 있으면 2.5가 Java에 유리하게 감소하지 않았습니다.

편집 : 64 비트 아치 리눅스 PC에서 프로그램을 실행했습니다.

EDIT2 : 파이썬 코드의 대략적인 번역으로 시작했다고 덧붙이고 싶습니다.

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

이 프로그램은 약 3.6 초 동안 실행되었습니다.

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 솔루션보다 약 14 배 빠릅니다. (fastRandom 기능을 통해 표준 랜덤 기능을 선택하면 실행 시간이 5 초가됩니다)


2

파이썬 3.5 + numpy 1.10.1, 3.76 초

테스트는 Macbook Pro에서 실행되었습니다. OP의 코드는 동일한 머신에서 ~ 6 분이 걸렸습니다.

내가이 질문에 실제로 대답하는 이유는 내가 10 평판을 얻지 못하고 파트 I에 대답 할 수 없기 때문입니다.

지난 며칠 동안, 나는 제 3 자 패키지에 의존하지 않고 numpy로 대규모 컨볼 루션을 효율적으로 수행하는 방법을 알아 내려고 노력했습니다. 연구하는 동안이 일련의 과제를 겪으면서 시도해보기로했습니다. 나는이 게임에 늦게 올지 모르지만 여기에 Python 3.5와 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)

나는 S와 F 배열을 미리 계산하고 컨볼 루션을 수행하면서 S 배열을 평평하게 만들었습니다.이 실험은 np.convolve의 속도를 활용할 수 있습니다. 즉, 벡터화 된 컨볼 루션 루틴을 찾지 못했기 때문에 전체 배열을 평면화하여 코드를 가짜 벡터화했으며 np.convolved가 후드를 통해 벡터화를 수행하기를 희망했습니다. 참고 mode = 'same'을 사용하고 쓸모없는 선행 및 후행 요소를 자릅니다.

내 Macbook Pro에서 테스트 결과는 3.76 초 입니다. OP의 코드 (Python 3.5로 수정)를 실행했을 때 약 6 분이 걸렸습니다 . 속도는 약 100 배입니다.

한 가지 단점은 S 및 F 어레이를 저장해야하기 때문에 크기가 너무 크면 메모리 요구 사항이 문제가 될 수 있다는 것입니다.

나는 파트 I에 대해 동일한 방법을 사용했으며 랩톱에서 ~ 60-100x 속도가 향상되었습니다.

Macbook Pro에서 모든 작업을 수행했을 때 누군가 내 코드를 테스트하여 컴퓨터에서 어떻게 작동하는지 알려면 대단히 감사하겠습니다!


1

J, 130x ~ 50x 속도 향상?

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

임의의 데비안 시간 :

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

개선의 여지가 있다고 생각합니다.


파이썬 스크립트는 pypy아닌 not을 사용하여 실행되어야하므로 스크립트가 python130 배 속도를 높이는 것처럼 보입니다.
ace_HongKongIndependence

@ace 예 나는 알았지 만 pypy를 설치할 수는 없습니다 :-/ 비록 크기의 순서가 남아 있다고 생각합니다.
Eelvex

반드시 그런 것은 아닙니다 ... i.imgur.com/n566hzw.png
ace_HongKongIndependence

실제로, 반드시 그런 것은 아닙니다.
Eelvex

pypy를 설치하면 어떤 문제가 있습니까?

1

C ++ : x200 (4 코어 i7, 8 코어에서 x400으로 확장해야 함)

병렬화 가있는보다 간단한 C ++ 11 (VS 2012, gcc 및 clang으로 테스트) 솔루션을 시도합니다 .

gcc 4.8.1을 사용하여 Linux에서 컴파일하고 실행하려면 :

g ++ -O3 -msse -msse2 -msse3 -march = 네이티브 -std = c ++ 11 -pthread -Wl,-필요하지 않은 golf.cpp

Linux에서는 std::launch::async여러 스레드를 강제 실행 해야 합니다. 이전 버전에서는 누락되었습니다.

Visual Studio (2012+)에서는 이것이 작동하지만 타이밍을위한 릴리스 빌드를 만들어야합니다 ...

내 오래된 듀얼 코어 i3에서는 ~ 0.9 초 안에 실행됩니다 . 내 i7 쿼드 코어에서 이것은 pypy 66 초에 대해 0.319 초입니다.

8 코어 i7에서는 x400 속도 향상 범위에 있어야합니다. C 스타일 배열로 전환하면 속도가 빨라지지만 C ++ 컨테이너를 유지하는 데 관심이있었습니다. 나에게 C ++이 정말 좋은 것으로 생각되는 문제 영역에 비교적 가깝고 비교적 높은 수준을 유지하면서 얻을 수있는 속도 향상을 보는 것이 흥미 롭습니다. 또한 C ++ 11 구문을 사용하는 비교적 쉬운 마비가 중요합니다.

@ilmale의 비트 솔루션은 매우 시원하며 -1/1/0에서 작동합니다. 이것에 SSE를 던질 수 있고 상당한 속도 향상을 얻을 수 있습니다.

병렬화를 넘어서서 거기에 또 다른 "속임수"가있어 합산 횟수를 줄입니다. 샘플 결과 : 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

포트란 : 316x

좋아, 포트란 : 나는 그것을를 가지고 106x 155x 160X 316x 4 코어 i7 CPU 상에 Xorshift RNG와의 OpenMP를 사용할 때 속도 향상을. 그 외에는 큰 트릭이 없습니다. 반복자가 S를 구성하기 위해 16 비트 정수 i의 이진 표현을 사용합니다. 인라인 RNG와 i에서 S 로의 "반복자"/ 매핑과는 별도로 코드는 Python 코드와 마찬가지로 수준이 높습니다.

편집 : Xorshift에서 "if"를 제거하여 "r = w / ..."대신 "r = abs (w / ...)"를 사용합니다. 106 배에서 155 배로 증가합니다.

Edit2 : C ++ 솔루션보다 15 배 많은 난수를 생성합니다. 누군가 Fortran에서 임의의 int를 0과 1의 배열로 변환하기위한 오버 헤드 제로 솔루션이 있다면, 나는 모두 귀입니다. 그런 다음 C ++을 이길 수 있습니다. :)

Edit3 : Lembik이 지적한 것처럼 첫 번째 편집에서 버그가 발생했습니다. 속도가 약간 향상되어 이제 수정되었습니다. 속도를 높이기 위해 Eelvex의 제안을 사용하려고합니다.

Edit4 : 프로파일 링은 nint ()를 사용하여 실수로 변환하고 다시 정수로 변환하는 것이 느리다는 것을 나타 냈습니다. 나는 이것을 160x에서 316x 속도 향상으로 스케일링과 라운딩을 수행하는 하나의 정수 나누기로 대체했습니다.

다음과 같이 컴파일하십시오.

gfortran -O3 -march = 네이티브 -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

출력 예 :

$ 시간 ./a.out
6329624 2524831 1039787 438809 193044 6860 40486 19517
./a.out 1.45s 사용자 0.00s 시스템 746 % CPU 0.192 총

OP의 코드 :

$ time pypy golf.py
pypy golf.py 60.68s 사용자 0.04s 시스템 99 % CPU 1 : 00.74 총


J에서 사용한 것은 base-4의 4 ^ n 숫자로 구성된 사전 빌드 목록이었고 3을 3으로 변환하고 0을 제외했습니다. RNG는이 목록에서 선택합니다.
Eelvex

코드가 올바른지 확실하지 않습니다. 100,000 회 반복하면 633140285 271390368 118307997 52751245 23725837 10744292 4944464 2388125이지만 633170604 252560981 104156146 43911426 19316309 8713324 4073378 1959440에 더 가깝습니다.

1
아, 고맙습니다, @Lembik, 속도 향상 (if 문 제거)에 대한 편집은 실제로 버그였습니다. 코드가 업데이트되었으므로 지금은 정확해야합니다. 나중에 Eelvex의 제안을 사용하여 버전을 게시하려고합니다.
세미 외부

그것은 또한 그것을 속도 올렸다!

예, 약간의 속도 향상입니다. 1.0을 더한 다음 타이트한 루프에서 1.0을 빼고 있다는 것을 깨달았습니다.
세미 외부
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.