Python到底有多慢(第二部分)?


52

这是Python到底有多慢?(或者您的语言有多快?)

事实证明,对于我的最后一个问题,获得x100加速有点太容易了。对于那些喜欢挑战但又想要更艰苦,可以真正使用其低水平技能的人,这是第二部分。挑战是要在我的计算机上测试以下python代码的速度提高100倍。

为了更加困难,我这次使用pypy。对于我来说,当前时间是使用pypy 2.2.1的1分7秒

规则

  1. 第一个提交我可以运行的代码的人是正确的,并且在我的计算机上运行的速度快100倍,将获得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八核处理器上的标准ubuntu安装。这也意味着我需要能够运行您的代码。

代码说明

此代码遍历由-1s和1s组成的长度为n + m-1的所有数组S。对于每个数组S,它对长度为n的1000个非零随机数组F进行采样,该数组由-1,0或1组成,并有1 / 4、1 / 2 // 14的概率采用每个值。然后,它计算F与长度为n的S的每个窗口之间的内积,直到找到一个非零的内积。leadingzerocounts在发现零内积的每个位置上加1 。

状态

  • Perl。@tobyink的2.7倍减速 (与pypy而非cpython相比。)

  • Ĵ。@Eelvex的39倍加速。

  • Ç。@ace可以加速59倍。
  • 朱莉娅。快197倍,不包括启动时间(多一分钟)。包括启动时间在内的速度提高了8.5倍(在这种情况下,使用4个处理器比使用8个处理器更快)。
  • Fortran。@ semi-extrinsic可以加速438倍。
  • Rpython的。@primo可以加速258倍。
  • C ++。@ilmale加快508倍。

(我停止为新的改进计时,因为它们太快且迭代次数太小。)


有人指出,不到一秒钟的时间是不可靠的,而且某些语言还会产生启动费用。论据是,如果要包括,还应该包括C / C ++等的编译时间。这是最快的代码的时间,迭代次数增加到100,000。

  • 朱莉娅。42秒一分钟。
  • C ++。@GuySirton用14秒。
  • Fortran。@ semi-extrinsic拍摄的14秒钟。
  • C ++。@ilmale 12秒。
  • Rpython的。@primo 18岁。
  • C ++。@Stefan 5s。

赢家是..斯特凡!

已发布后续挑战。你能走多高?(编码+算法挑战)。这个比较难。


3
对代码可以实现的解释很好,所以我们可以重写它而不是简单地移植它
Einacio 2014年

6
第一个提交我可以运行的代码的人是正确的,并且在我的计算机上以快100倍的速度赢得比赛并结束比赛。 ”这样结束比赛的目的是什么?为什么不像大多数其他日期一样使用日期截止日期,以便我们可以看到其他语言进一步缩短了日期截止日期?
grovesNL 2014年

5
@Einacio这是一个好主意。我更改了规则,希望没有人会介意。

1
@Lembik我改进了我的Fortran版本,使其在我的机器上又快了2倍。你能再定时吗?:)
半外在

1
@ semi-extrinsic完成。

Answers:


12

C ++魔术

〜16ms多线程,56ms单线程。〜4000加速

(加速是基于我的i7-2820QM上的多线程代码以及问题中提到的1分9秒。由于OP的系统比我的CPU的单线程性能差,但多线程性能更好,所以我希望这个数字是准确的)

由于线程的产生,多线程部分的效率很低。我可以利用我的自定义作业库来做得更好,但是在Unix系统下存在一个错误。有关解释和几乎没有线程的几乎相同的代码,请参见https://codegolf.stackexchange.com/a/26485/20965

编辑

我为每个线程提供了自己的RNG,并将位长度减少到32,这将运行时间减少了几毫秒。

#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随机生成器,可以正常工作。我将在今天晚些时候修复代码。
斯蒂芬2014年

我得到了Stefan.cpp:104:101:错误:在此范围中未声明'memcpy'memcpy(out.data()+(threadId * m),leadZeros.data(),sizeof(leadZeros [0])* m );

我认为我需要包括string.h。再试一遍。
Stefan

您可以使用g ++ -O3 -std = c ++ 0x -pthread -Wl进行编译,-无需使用Stefan.cpp -o Stefan

16

C ++ x150 x450 x530

我没有使用数组(而是黑魔法)来代替数组。
感谢@ace提供更快的随机函数。

工作原理:整数的前15位s代表数组S[15];零代表-1,一个代表+1。数组F以类似的方式构建。但是每个符号有两位。

  • 00代表-1
  • 01和10代表0
  • 11代表1

因为SF我有不同的表述,我必须自己插入S才能与之比较F

  • 0(-1)变为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)

对我来说似乎不是异或。:)

总结来说,这只是一场转移和掩饰的游戏,没有什么真正复杂的。

#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 4.8.2的Fedora 20上Cpu是i7 8core。

也许我可以获得一些调整编译器参数的信息。

这是我机器上的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 = native -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;
}

现在,我等不及要测试了!不幸的是,这不会持续几个小时。

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 2014年

@ user71404:探查器说该程序将所有时间都花在了这两个函数上,但是昨天我真的很累,认为还有更好的方法。我会在今天晚上(UTC)尝试。谢谢。
ilmale 2014年

您介意将第二个代码段更改为完整副本和可粘贴代码吗?为了使您的openmp代码正常工作,我一定在做错事,因此这很有帮助。

真好 我认为可以通过位操作来完成。
Guy Sirton 2014年

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.7s与1m22s。

如果您的计算机上有足够的内核,并且可能启动了一些AWS实例,则应该可以省掉更多的东西了:)


我很确定您测量的时机有误。带有Pypy的Python也是一种基于JIT的语言,但是OP进行的计时包括 JIT编译时间。您正在排除它。我安装了最新的Julia git版本并测试了您的代码,在我的计算机上,具有8个进程的命令需要6.6秒才能完成,但它会显示“经过时间0.588 ..秒”。
半外部

Python计时确实包括PyPy启动和JIT预热,但这最多需要几秒钟的时间–一分钟的运行时间差异可以忽略不计。我很高兴,如果OP改变了Python的计时方式(不会有任何不同),但是包括Julia的启动时间将是不合理的。
多一分钟

我在对原始问题的评论中询问了OP,他说:“时间应该包括JIT语言的所有内容。” 他还表示,他将提出一个新的挑战,即解决方案将花费超过1秒的时间,这将使Julia留在比赛中。
半外部2014年

在那种情况下,最佳解决方案是使用串行算法-大约需要2秒。我会发布代码,但是这种竞争现在有点多余了,因为每个人都已经知道C ++的启动速度比其他所有启动都快。
一分钟多一点

我刚刚发布了我的Fortran解决方案,所以我不明白为什么不应该发布最快的Julia(如果已有代码)。
2014年

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-vectorize顺便说一句。通过使用一些C ++ 11(包括MT19937和a),我花了不到4秒钟的时间uniform_int_distribution
dyp 2014年

1
1.119s加速了大约59!

1
@ace是的,我只是想指出这一点。对于我来说,尝试使用C ++中的一些标准库PRNG更为简单。请注意,您可以使用PRNG的一个32位整数结果为生成8个条目F
dyp 2014年

1
由于n等于8,您可能可以使用AVX(或2 * SSE)来计算具有适当S存储量的点积。
Michael M.

2
SSE版本,小加速:gist.github.com/anonymous/11394210(不要忘了包括smmintrin.h
Michael M.

5

佩尔

这远不及C解决方案快,但对于我认为的高级解释语言来说,速度却相当快。它节省了Python实现的运行时间约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允许将丢失的元素视为零的情况。N-1改善了这一点。实际上,它确实确实提高了速度-现在比Python实现快40%。
tobyink 2014年

我认为您的代码需要非常现代的List :: Util版本。在ubuntu 14.04上,我得到List :: Util模块未导出“ any”

哦,是的,这是真的-您可能需要在CPAN上安装List :: Util。any您也可以在List :: MoreUtils中找到该列表,尽管它不是核心模块,但却是最常用的CPAN模块之一。
tobyink 2014年

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.912 s

PyPy中的OP代码:1 m 11.506 s

朱莉娅输出:

6332170
2525472
1041522
438761
193119
86873
40705
19662

7
为您的<s>无耻</ s>运动精神+1。
ace_HongKong独立2014年

全局变量,导入和数组理解很慢。这不是通常为性能编写Julia程序的方式。
Alex A.

4

RPython 0.187s(快258倍)

带有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工具链进行编译。它的明确目的是帮助创建语言解释器,但是它也可以用于编译诸如上述程序之类的简单程序。Python的大多数“高级”功能(例如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为位图:[ 01]→[ -11]

同样地,F也是位图,与代表一个单一的值的每两个比特:
[ 00011011]→[ ,-1,,0 ]01

真值表用于查找产品,而不是进行多重复制。

由于使用的是32位带符号整数,因此n可以不大于15,并且n+m不大于31。rpython.rlib.rbigint如果需要,可以使用模块实现任意整数支持。

将展开点积循环的第一次迭代,并将其与的无效性测试组合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程序。这很棒。现在有一个等效的纯python程序,pypy可以在1.03s中运行吗?

@Lembik我想看看一个。考虑到我第一次尝试纯python是大约15s,我认为4.7s很好。
primo 2014年

是的,对不起您的延迟。我还没有运行代码,但会尽快。

您应该尝试添加JIT。现在那会很快!
kirbyfan64sos 2014年

@Lembik感谢您的提及;)出于好奇,它是否以4个或8个工作线程运行最快?
2014年

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的全部是否为零,为什么不写all(F == 0)而不是全部(x-> x == 0,F)?它更短,实际上快一千倍。

它还使用sum(map(*,x,y))作为点积,而不是简单的dot(x,y)。对于10k双精度向量,第一个版本要慢650倍。点积函数在纯Julia中作为for循环实现。

而且,数组理解速度很慢。对于j = 1:n,最好写[0,1,0,-1] [rand(1:4,n)]而不是[[-1 0 0 1] [rand(1:4)] 。

最后,全局变量在Julia中是不好的选择。只有当您以允许JIT和类型推断起作用的方式编写代码时,Julia才会很快。其中很大一部分是类型稳定性:例如,编译器必须能够确保变量的类型在循环内不会改变。


谢谢!我看到我仍然有很多关于Julia语言的知识,然后才能对它的速度提出要求:)非常高兴地看到对我的代码的一些琐碎修正将其执行时间提高了几倍。
灵巧的琼脂

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:-flto如果您有不支持LTO的较旧的C编译器,请省略。--cc=...如果适合C编译器的默认选项,请省略该选项。该代码要求Nimrod 0.9.4或0.9.5

在我的四核iMac(2.66 GHz核i5)上,使用gcc 4.9的代码运行时间约为0.15秒,使用clang的代码运行时间约为0.16秒,而PyPy 2.2.1的运行时间为88秒(即加速500倍以上)。不幸的是,我无法使用安装了PyPy或安装PyPy的四个以上内核的机器,尽管在64核AMD上我得到了约0.1秒(带有很多测量噪声)带有gcc 4.4.6的Opteron 6376 1.4 GHz(根据/ proc / cpuinfo)。

该实现尝试忠实于原始代码,而不是以可读性为代价来优化代码,同时不放弃明显的优化。有趣的是,尾部递归initVecRand()比带有gcc和clang的带有break指令的循环快一点。convolve可能是由于更好的分支预测,在主循环中手动展开测试循环的一次迭代也产生了加速。


您如何获得Ubuntu的Nimrod?

@Lembik快速的Google搜索将为您提供nimrod-lang.org/download.html
ace_HongKongIndependence 2014年

@ace我也将链接包含在我的帖子中(尽管现在我很难看到黑色和蓝色)。
Reimer Behrends'May

您可以通过将种子大小增加到128位来进一步提高速度:xorshift.di.unimi.it
user60561 2014年

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启动)。这个Java解决方案比PyPy执行的程序快大约142倍。

由于我个人感兴趣,因此我将itersJava和C ++的值设置为100_000,但如果有任何变化,2.5的因数并不会因Java而降低。

编辑:我在64位Arch Linux PC上运行了程序。

EDIT2:我想补充一点,我是从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));
    }
}

该程序运行了大约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

Python 3.5 + numpy 1.10.1,3.76秒

测试在我的Macbook Pro上运行。OP的代码在同一台机器上花费了大约6分钟。

我之所以回答这个问题,实际上是因为我没有10个声誉,也无法回答第I部分:-p

在过去的几天中,我一直在尝试找出如何使用numpy有效执行大规模卷积(无需依赖第三方程序包,甚至不需要scipy)。在研究过程中遇到了一系列挑战时,我决定尝试一下。我可能来这游戏很晚了,但是这是我尝试使用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数组,因此如果大小太大,则内存需求可能会成为问题。

对于第一部分,我使用了相同的方法,笔记本电脑的速度提高了60-100倍。

当我在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''

随机Debian上的时间:

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

我认为还有改进的空间。


Python脚本应该使用pypy而不是来执行python,这就是为什么您的脚本似乎可以提高130倍的速度。
ace_HongKongIndependence

@ace是的,我注意到了,但是我无法安装pypy:-/虽然我认为数量级将保持不变。
Eelvex 2014年


确实,不一定。
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 = native -std = c ++ 11 -pthread -Wl,-不需要的golf.cpp

在Linux下,我们还需要std::launch::async强制多个线程。我在较早的版本中缺少该功能。

在Visual Studio(2012+)中,这应该可以正常工作,但要发布一个用于计时的版本。

在我较老的双核i3上,运行时间约为0.9秒。在我的i7四核上,这是0.319s对pypy 66秒。

在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

Fortran:316倍

好的,Fortran:在4核i7 CPU上使用Xorshift RNG和OpenMP时,我的速度提高了106x 155x 160x 316x。除此之外,没有什么大把戏。对于构造S的迭代器,我只使用16位整数i的二进制表示形式。您会注意到,除了内联RNG和从i到S的“迭代器” /映射之外,该代码与Python代码一样高级。

编辑:现在使用“ r = abs(w / ...)”代替“ r = w / ...”,删除了Xorshift中的“ if”。从106倍变为155倍。

Edit2:生成的随机数是C ++解决方案的15倍。如果有人有零开销的解决方案在Fortran中将随机int转换为0和1的数组,那么我无所不能。然后我们可以击败C ++ :)

Edit3:正如Lembik指出的那样,第一次编辑引入了一个错误。现在,此问题已得到解决,但加速方面的改进很小。我将尝试使用Eelvex的建议来提高速度。

Edit4:分析表明,使用nint()转换为实数并返回整数的速度很慢。我用一个整数除法来代替它,同时进行缩放和舍入,从160倍加速到316倍加速。

编译:

gfortran -O3 -march = native -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

输出示例:

$ time ./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数字列表,然后转换为三元组并排除0。RNG只是从该列表中选择。
Eelvex 2014年

我不确定您的代码是否正确。对于100,000次迭代,我得到633140285 271390368 118307997 52751245 23725837 10744292 4944464 2388125,但我认为它应该更接近633170604 252560981 104156146 43911426 19316309 8713324 40733781959440。这是两次运行之间的一致差异。

1
啊,谢谢@Lembik,我对加速的编辑(删除了if语句)确实是一个错误。我已经更新了代码,因此现在应该是正确的。稍后,我将尝试使用Eelvex的建议发布版本。
2014年

这似乎也加快了速度!

是的,我想会稍微加快速度。我意识到我先加了1.0,然后在一个紧密的循环中减去了1.0。
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.