Python ช้ามากแค่ไหน? (หรือภาษาของคุณเร็วแค่ไหน)


149

ฉันมีรหัสนี้ซึ่งฉันได้เขียนใน Python / NumPy

from __future__ import division
import numpy as np
import itertools

n = 6
iters = 1000
firstzero = 0
bothzero = 0
""" The next line iterates over arrays of length n+1 which contain only -1s and 1s """
for S in itertools.product([-1, 1], repeat=n+1):
    """For i from 0 to iters -1 """
    for i in xrange(iters):
        """ Choose a random array of length n.
            Prob 1/4 of being -1, prob 1/4 of being 1 and prob 1/2 of being 0. """
        F = np.random.choice(np.array([-1, 0, 0, 1], dtype=np.int8), size=n)
        """The next loop just makes sure that F is not all zeros."""
        while np.all(F == 0):
            F = np.random.choice(np.array([-1, 0, 0, 1], dtype=np.int8), size=n)
        """np.convolve(F, S, 'valid') computes two inner products between
        F and the two successive windows of S of length n."""
        FS = np.convolve(F, S, 'valid')
        if FS[0] == 0:
            firstzero += 1
        if np.all(FS == 0):
            bothzero += 1

print("firstzero: %i" % firstzero)
print("bothzero: %i" % bothzero)

มันกำลังนับจำนวนครั้งที่การบิดของสองอาร์เรย์สุ่มหนึ่งอันซึ่งยาวกว่าอีกอันหนึ่งโดยมีการแจกแจงความน่าจะเป็นโดยเฉพาะมี 0 ที่ตำแหน่งแรกหรือ 0 ในทั้งสองตำแหน่ง

ฉันพนันกับเพื่อนที่บอกว่า Python เป็นภาษาที่แย่มากในการเขียนโค้ดที่ต้องรวดเร็ว ใช้เวลา 9 วินาทีบนคอมพิวเตอร์ของฉัน เขาบอกว่ามันสามารถทำให้เร็วขึ้น 100 เท่าหากเขียนด้วยภาษาที่เหมาะสม

ความท้าทายคือการดูว่ารหัสนี้สามารถทำได้เร็วกว่า 100 เท่าในภาษาที่คุณเลือกหรือไม่ ฉันจะทดสอบโค้ดของคุณและเร็วที่สุดหนึ่งสัปดาห์จากนี้จะเป็นผู้ชนะ หากใครได้ต่ำกว่า 0.09 วินาทีพวกเขาจะชนะโดยอัตโนมัติและฉันเสีย

สถานะ

  • หลาม เพิ่มความเร็ว 30 ครั้งโดย Alistair Buxon! แม้ว่าจะไม่ใช่วิธีแก้ปัญหาที่เร็วที่สุด แต่ในความเป็นจริงแล้วสิ่งที่ฉันโปรดปราน
  • ระดับแปดเสียง เร็วขึ้น 100 เท่าโดย @Thethos
  • สนิม เพิ่มความเร็วได้ 500 เท่าโดย @dbaupp
  • C ++ Guy Sirton เร็วขึ้น 570 เท่า
  • . เพิ่มความเร็ว 727 เท่าโดย @ace
  • C ++ เร็วอย่างไม่น่าเชื่อโดย @tefan

การแก้ปัญหาที่เร็วที่สุดนั้นเร็วเกินไปที่จะทันเวลา ฉันเพิ่ม n เป็น 10 แล้วตั้งค่า iters = 100,000 เพื่อเปรียบเทียบสิ่งที่ดีที่สุด ภายใต้มาตรการนี้เร็วที่สุดคือ

  • . 7.5 โดย @ace
  • C ++ 1 วินาทีโดย @Stefan

My Machineเวลาของฉันจะทำงานบนเครื่องของฉัน นี่คือการติดตั้ง Ubuntu มาตรฐานบนโปรเซสเซอร์ AMD FX-8350 Eight-Core นี่ก็หมายความว่าฉันต้องสามารถเรียกใช้รหัสของคุณได้

การติดตามโพสต์เนื่องจากการแข่งขันนี้ค่อนข้างง่ายเกินไปที่จะได้รับ x100 speedup ฉันได้โพสต์ติดตามผลสำหรับผู้ที่ต้องการฝึกความเชี่ยวชาญด้านกูรูความเร็ว ดูว่า Python ช้าแค่ไหน (ตอนที่ 2) จริงหรือ

คำตอบ:


61

มายากล C ++ บิต

0.84ms พร้อม RNG แบบง่าย, 1.67ms พร้อม c ++ 11 std :: knuth

0.16ms พร้อมการปรับเปลี่ยนขั้นตอนวิธีเล็กน้อย (ดูการแก้ไขด้านล่าง)

การใช้งานไพ ธ อนทำงานใน 7.97 วินาทีบนอุปกรณ์ของฉัน ดังนั้นนี่คือ 9488 ถึง 4772 เท่าเร็วขึ้นอยู่กับสิ่งที่คุณเลือก RNG

#include <iostream>
#include <bitset>
#include <random>
#include <chrono>
#include <stdint.h>
#include <cassert>
#include <tuple>

#if 0
// C++11 random
std::random_device rd;
std::knuth_b gen(rd());

uint32_t genRandom()
{
    return gen();
}
#else
// bad, fast, random.

uint32_t genRandom()
{
    static uint32_t seed = std::random_device()();
    auto oldSeed = seed;
    seed = seed*1664525UL + 1013904223UL; // numerical recipes, 32 bit
    return oldSeed;
}
#endif

#ifdef _MSC_VER
uint32_t popcnt( uint32_t x ){ return _mm_popcnt_u32(x); }
#else
uint32_t popcnt( uint32_t x ){ return __builtin_popcount(x); }
#endif



std::pair<unsigned, unsigned> convolve()
{
    const uint32_t n = 6;
    const uint32_t iters = 1000;
    unsigned firstZero = 0;
    unsigned bothZero = 0;

    uint32_t S = (1 << (n+1));
    // generate all possible N+1 bit strings
    // 1 = +1
    // 0 = -1
    while ( S-- )
    {
        uint32_t s1 = S % ( 1 << n );
        uint32_t s2 = (S >> 1) % ( 1 << n );
        uint32_t fmask = (1 << n) -1; fmask |= fmask << 16;
        static_assert( n < 16, "packing of F fails when n > 16.");


        for( unsigned i = 0; i < iters; i++ )
        {
            // generate random bit mess
            uint32_t F;
            do {
                F = genRandom() & fmask;
            } while ( 0 == ((F % (1 << n)) ^ (F >> 16 )) );

            // Assume F is an array with interleaved elements such that F[0] || F[16] is one element
            // here MSB(F) & ~LSB(F) returns 1 for all elements that are positive
            // and  ~MSB(F) & LSB(F) returns 1 for all elements that are negative
            // this results in the distribution ( -1, 0, 0, 1 )
            // to ease calculations we generate r = LSB(F) and l = MSB(F)

            uint32_t r = F % ( 1 << n );
            // modulo is required because the behaviour of the leftmost bit is implementation defined
            uint32_t l = ( F >> 16 ) % ( 1 << n );

            uint32_t posBits = l & ~r;
            uint32_t negBits = ~l & r;
            assert( (posBits & negBits) == 0 );

            // calculate which bits in the expression S * F evaluate to +1
            unsigned firstPosBits = ((s1 & posBits) | (~s1 & negBits));
            // idem for -1
            unsigned firstNegBits = ((~s1 & posBits) | (s1 & negBits));

            if ( popcnt( firstPosBits ) == popcnt( firstNegBits ) )
            {
                firstZero++;

                unsigned secondPosBits = ((s2 & posBits) | (~s2 & negBits));
                unsigned secondNegBits = ((~s2 & posBits) | (s2 & negBits));

                if ( popcnt( secondPosBits ) == popcnt( secondNegBits ) )
                {
                    bothZero++;
                }
            }
        }
    }

    return std::make_pair(firstZero, bothZero);
}

int main()
{
    typedef std::chrono::high_resolution_clock clock;
    int rounds = 1000;
    std::vector< std::pair<unsigned, unsigned> > out(rounds);

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


    auto start = clock::now();

    for( int i = 0; i < rounds; i++ )
    {
        out[i] = convolve();
    }

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

#if 0
    for( auto pair : out )
        std::cout << pair.first << ", " << pair.second << std::endl;
#endif

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

    return 0;
}

รวบรวมใน 64- บิตสำหรับการลงทะเบียนพิเศษ เมื่อใช้ตัวสร้างแบบสุ่มอย่างง่ายลูปใน convolve () จะทำงานโดยไม่มีการเข้าถึงหน่วยความจำตัวแปรทั้งหมดจะถูกเก็บไว้ในรีจิสเตอร์

วิธีการทำงาน: แทนที่จะจัดเก็บSและFเป็นอาร์เรย์ในหน่วยความจำจะถูกจัดเก็บเป็นบิตใน uint32_t
สำหรับSที่nน้อยบิตอย่างมีนัยสำคัญที่มีการใช้บิตชุดหมายถึง 1 และบิตล้างหมายถึง -1
Fต้องการอย่างน้อย 2 บิตเพื่อสร้างการแจกแจงของ [-1, 0, 0, 1] สิ่งนี้ทำได้โดยการสร้างบิตสุ่มและตรวจสอบ 16 สำคัญน้อยที่สุด (เรียกว่าr) และ 16 บิตสำคัญที่สุด (เรียกว่าl) ถ้าl & ~rเราสมมติว่า F คือ +1 ถ้า~l & rเราสมมติว่าFเป็น -1 มิฉะนั้นFคือ 0 นี่จะเป็นการสร้างการกระจายที่เราต้องการ

ตอนนี้เรามีS, posBitsกับบิตชุดในทุกสถานที่ที่ F == ที่ 1 และnegBitsที่มีบิตชุดในทุกสถานที่ที่ F == -1

เราสามารถพิสูจน์ได้ว่าF * S(ที่ * หมายถึงการคูณ) ประเมิน 1 (S & posBits) | (~S & negBits)ภายใต้เงื่อนไขที่ นอกจากนี้เรายังสามารถสร้างตรรกะที่คล้ายกันสำหรับทุกกรณีที่F * Sประเมินเป็น -1 และสุดท้ายเราก็รู้ว่าsum(F * S)จะประเมินเป็น 0 ถ้าหากมีจำนวนเท่ากับ -1 และเท่ากับ 1 ในผลลัพธ์ สิ่งนี้ง่ายต่อการคำนวณโดยเปรียบเทียบจำนวน +1 บิตและ -1 บิต

การใช้งานนี้ใช้ 32 บิต ints และการnยอมรับสูงสุดคือ 16 มันเป็นไปได้ที่จะปรับขนาดการดำเนินการถึง 31 บิตโดยการปรับเปลี่ยนรหัสสร้างแบบสุ่มและ 63 บิตโดยใช้ uint64_t แทน uint32_t

แก้ไข

ฟังก์ชั่นการโน้มน้าว folowing:

std::pair<unsigned, unsigned> convolve()
{
    const uint32_t n = 6;
    const uint32_t iters = 1000;
    unsigned firstZero = 0;
    unsigned bothZero = 0;
    uint32_t fmask = (1 << n) -1; fmask |= fmask << 16;
    static_assert( n < 16, "packing of F fails when n > 16.");


    for( unsigned i = 0; i < iters; i++ )
    {
        // generate random bit mess
        uint32_t F;
        do {
            F = genRandom() & fmask;
        } while ( 0 == ((F % (1 << n)) ^ (F >> 16 )) );

        // Assume F is an array with interleaved elements such that F[0] || F[16] is one element
        // here MSB(F) & ~LSB(F) returns 1 for all elements that are positive
        // and  ~MSB(F) & LSB(F) returns 1 for all elements that are negative
        // this results in the distribution ( -1, 0, 0, 1 )
        // to ease calculations we generate r = LSB(F) and l = MSB(F)

        uint32_t r = F % ( 1 << n );
        // modulo is required because the behaviour of the leftmost bit is implementation defined
        uint32_t l = ( F >> 16 ) % ( 1 << n );

        uint32_t posBits = l & ~r;
        uint32_t negBits = ~l & r;
        assert( (posBits & negBits) == 0 );

        uint32_t mask = posBits | negBits;
        uint32_t totalBits = popcnt( mask );
        // if the amount of -1 and +1's is uneven, sum(S*F) cannot possibly evaluate to 0
        if ( totalBits & 1 )
            continue;

        uint32_t adjF = posBits & ~negBits;
        uint32_t desiredBits = totalBits / 2;

        uint32_t S = (1 << (n+1));
        // generate all possible N+1 bit strings
        // 1 = +1
        // 0 = -1
        while ( S-- )
        {
            // calculate which bits in the expression S * F evaluate to +1
            auto firstBits = (S & mask) ^ adjF;
            auto secondBits = (S & ( mask << 1 ) ) ^ ( adjF << 1 );

            bool a = desiredBits == popcnt( firstBits );
            bool b = desiredBits == popcnt( secondBits );
            firstZero += a;
            bothZero += a & b;
        }
    }

    return std::make_pair(firstZero, bothZero);
}

ตัดรันไทม์เป็น 0.160-0.161ms การวนซ้ำแบบแมนนวล (ไม่ใช่ภาพด้านบน) ทำให้ 0.150 เรื่องเล็ก ๆ น้อย ๆ น้อย ๆ n = 10, iter = 100000 case รันน้อยกว่า 250ms ฉันแน่ใจว่าฉันสามารถทำได้ภายใต้ 50ms โดยใช้ประโยชน์จากแกนเพิ่มเติม แต่มันง่ายเกินไป

สิ่งนี้ทำได้โดยทำให้วงวนภายในเป็นอิสระและสลับลูป F และ S
หากbothZeroไม่จำเป็นฉันสามารถลดเวลาทำงานลงเหลือ 0.02 มิลลิวินาทีโดยการวนลูปมากกว่าอาร์เรย์ S ที่เป็นไปได้ทั้งหมด


3
คุณช่วยจัดหาเวอร์ชันที่เป็นมิตร gcc และบรรทัดคำสั่งของคุณจะเป็นอย่างไร ฉันไม่แน่ใจว่าฉันสามารถทดสอบได้ในขณะนี้

ฉันไม่รู้อะไรเลย แต่ google บอกฉันว่า __builtin_popcount อาจเป็นสิ่งทดแทนสำหรับ _mm_popcnt_u32 ()

3
รหัสที่ได้รับการอัพเดตใช้สวิตช์ #ifdef เพื่อเลือกคำสั่ง popcnt ที่ถูกต้อง มันคอมไพล์ด้วย-std=c++0x -mpopcnt -O2และใช้เวลา 1.01 มิลลิวินาทีในการทำงานในโหมด 32 บิต (ฉันไม่มีรุ่น GCC 64 บิตในมือ)
สเตฟาน

คุณช่วยพิมพ์ออกมาได้ไหม? ฉันไม่แน่ใจว่าจริงๆแล้วมันกำลังทำอะไรอยู่ในขณะนี้ :)

7
คุณเป็นพ่อมดที่ชัดเจน + 1
BurntPizza

76

Python2.7 + Numpy 1.8.1: 10.242 s

Fortran 90+: 0.029 วินาที 0.003 วิ 0.022 วิ 0.010 วิ

ประณามคุณสูญเสียเดิมพันของคุณ! ไม่ใช่การขนานกันของที่นี่เพียงแค่ตรง Fortran 90+

แก้ไขฉันใช้อัลกอริทึมของ Guy Sirton เพื่ออนุญาตอาร์เรย์S(good find: D) เห็นได้ชัดว่าฉันยังมี-g -tracebackธงคอมไพเลอร์ใช้งานซึ่งทำให้รหัสนี้ช้าลงจนถึงประมาณ 0.017 วินาที ขณะนี้ฉันกำลังรวบรวมสิ่งนี้เป็น

ifort -fast -o convolve convolve_random_arrays.f90

สำหรับผู้ที่ไม่มีifortคุณสามารถใช้

gfortran -O3 -ffast-math -o convolve convolve_random_arrays.f90

แก้ไข 2 : การลดลงของรันไทม์เป็นเพราะฉันทำสิ่งที่ผิดก่อนหน้านี้และได้รับคำตอบที่ไม่ถูกต้อง ทำอย่างถูกวิธีช้าลงอย่างเห็นได้ชัด ฉันยังไม่อยากจะเชื่อเลยว่า C ++ นั้นเร็วกว่าของฉันดังนั้นฉันอาจจะต้องใช้เวลาสักครู่ในสัปดาห์นี้เพื่อพยายามปรับแต่งอึออกจากสิ่งนี้เพื่อเร่งความเร็ว

แก้ไข 3 : โดยเพียงแค่การเปลี่ยนส่วน RNG โดยใช้หนึ่งอยู่บนพื้นฐานของ BSD RNG (แนะนำโดย Sampo Smolander) และกำจัดแบ่งอย่างต่อเนื่องโดยm1ผมตัดระยะเวลาที่จะเป็นเช่นเดียวกับC ++ คำตอบโดย Guy Sirton การใช้อาร์เรย์แบบคงที่ (ตามที่ Sharpie แนะนำ) จะลดระยะเวลาใช้งานลงไปในช่วง C ++ Yay Fortran! : D

EDIT 4เห็นได้ชัดว่าสิ่งนี้ไม่ได้รวบรวม (กับ gfortran) และทำงานอย่างถูกต้อง (ค่าที่ไม่ถูกต้อง) เพราะจำนวนเต็มเกินขีด จำกัด ของพวกเขา ฉันได้ทำการแก้ไขเพื่อให้แน่ใจว่ามันใช้งานได้ แต่สิ่งนี้ต้องใช้ ifort 11+ หรือ gfortran 4.7+ (หรือคอมไพเลอร์อื่นที่อนุญาตiso_fortran_envและรุ่น F2008 int64)

นี่คือรหัส:

program convolve_random_arrays
   use iso_fortran_env
   implicit none
   integer(int64), parameter :: a1 = 1103515245
   integer(int64), parameter :: c1 = 12345
   integer(int64), parameter :: m1 = 2147483648
   real, parameter ::    mi = 4.656612873e-10 ! 1/m1
   integer, parameter :: n = 6
   integer :: p, pmax, iters, i, nil(0:1), seed
   !integer, allocatable ::  F(:), S(:), FS(:)
   integer :: F(n), S(n+1), FS(2)

   !n = 6
   !allocate(F(n), S(n+1), FS(2))
   iters = 1000
   nil = 0

   !call init_random_seed()

   S = -1
   pmax = 2**(n+1)
   do p=1,pmax
      do i=1,iters
         F = rand_int_array(n)
         if(all(F==0)) then
            do while(all(F==0))
               F = rand_int_array(n)
            enddo
         endif

         FS = convolve(F,S)

         if(FS(1) == 0) then
            nil(0) = nil(0) + 1
            if(FS(2) == 0) nil(1) = nil(1) + 1
         endif

      enddo
      call permute(S)
   enddo

   print *,"first zero:",nil(0)
   print *," both zero:",nil(1)

 contains
   pure function convolve(x, h) result(y)
!x is the signal array
!h is the noise/impulse array
      integer, dimension(:), intent(in) :: x, h
      integer, dimension(abs(size(x)-size(h))+1) :: y
      integer:: i, j, r
      y(1) = dot_product(x,h(1:n-1))
      y(2) = dot_product(x,h(2:n  ))
   end function convolve

   pure subroutine permute(x)
      integer, intent(inout) :: x(:)
      integer :: i

      do i=1,size(x)
         if(x(i)==-1) then
            x(i) = 1
            return
         endif
         x(i) = -1
      enddo
   end subroutine permute

   function rand_int_array(i) result(x)
     integer, intent(in) :: i
     integer :: x(i), j
     real :: y
     do j=1,i
        y = bsd_rng()
        if(y <= 0.25) then
           x(j) = -1
        else if (y >= 0.75) then
           x(j) = +1
        else
           x(j) = 0
        endif
     enddo
   end function rand_int_array

   function bsd_rng() result(x)
      real :: x
      integer(int64) :: b=3141592653
      b = mod(a1*b + c1, m1)
      x = real(b)*mi
   end function bsd_rng
end program convolve_random_arrays

ฉันคิดว่าคำถามตอนนี้คือคุณจะหยุดใช้ Python ที่ช้าเหมือนกากน้ำตาลและใช้ Fortran ที่เร็วเป็นอิเล็กตรอนสามารถเคลื่อนย้าย Fortran;)


1
คำสั่ง case จะไม่เร็วกว่าฟังก์ชั่น generator หรือไม่? นอกจากว่าคุณคาดหวังว่าการเพิ่มความเร็วในการคาดเดาสาขา / แคช - ไลน์ / ฯลฯ
OrangeDog

17
ควรเปรียบเทียบความเร็วกับเครื่องเดียวกัน คุณใช้ runtime อะไรกับรหัสของ OP
nbubis

3
คำตอบ C ++ ใช้ตัวสร้างตัวเลขสุ่มแบบเบามาก คำตอบของคุณใช้ค่าเริ่มต้นที่มาพร้อมกับคอมไพเลอร์ซึ่งอาจช้ากว่ากัน?
Sampo Smolander

3
นอกจากนี้ตัวอย่าง C ++ ดูเหมือนจะใช้อาร์เรย์ที่จัดสรรแบบคงที่ ลองใช้อาร์เรย์ที่มีความยาวคงที่ซึ่งตั้งค่าไว้ที่เวลาคอมไพล์แล้วดูว่ามันปิดอยู่ตลอดเวลาหรือไม่
Sharpie

1
@KyleKanos @Lembik ปัญหาคือการกำหนดจำนวนเต็มใน fortran ไม่ได้ใช้ข้อมูลจำเพาะ int64 โดยปริยายดังนั้นตัวเลขนั้นเป็น int32 ก่อนที่จะทำการแปลงใด ๆ รหัสควรเป็น: integer(int64) :: b = 3141592653_int64สำหรับ int64 ทั้งหมด นี่เป็นส่วนหนึ่งของมาตรฐาน fortran และเป็นที่คาดหวังโดยโปรแกรมเมอร์ในภาษาการเขียนโปรแกรมประกาศชนิด (โปรดสังเกตว่าการตั้งค่าเริ่มต้นของหลักสูตรสามารถลบล้างสิ่งนี้ได้)
zeroth

69

Python 2.7 - 0.882s 0.283s

(ต้นฉบับของ OP: 6.404 วินาที)

แก้ไข:การเพิ่มประสิทธิภาพของ Steven Rumbalski โดยการคำนวณค่า F ล่วงหน้า ด้วยการเพิ่มประสิทธิภาพนี้ cpython ชนะ 0.365s ของ pypy

import itertools
import operator
import random

n=6
iters = 1000
firstzero = 0
bothzero = 0

choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))

for S in itertools.product([-1,1], repeat = n+1):
    for i in xrange(iters):
        F = random.choice(choicesF)
        if not sum(map(operator.mul, F, S[:-1])):
            firstzero += 1
            if not sum(map(operator.mul, F, S[1:])):
                bothzero += 1

print "firstzero", firstzero
print "bothzero", bothzero

รหัสดั้งเดิมของ OP ใช้อาร์เรย์ขนาดเล็กดังกล่าวไม่มีประโยชน์ในการใช้ Numpy เนื่องจากการใช้งานไพ ธ อนบริสุทธิ์นี้แสดงให้เห็น แต่ดูการใช้งาน numpy นี้ซึ่งเร็วกว่าโค้ดของฉันสามครั้ง

ฉันยังปรับให้เหมาะสมด้วยการข้ามส่วนที่เหลือของการแปลงหากผลแรกไม่เป็นศูนย์


11
ด้วย pypy จะทำงานในเวลาประมาณ 0.5 วินาที
Alistair Buxton

2
คุณจะได้รับความเร็วที่น่าเชื่อถือมากขึ้นถ้าคุณตั้งค่า n = 10 ฉันได้ 19 วินาทีกับ 4.6s สำหรับ cpython เทียบกับ pypy

3
การปรับให้เหมาะสมอีกอย่างหนึ่งก็คือการคำนวณความน่าจะเป็นFเพราะมีเพียง 4032 ของพวกเขา กำหนดchoicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))นอกลูป จากนั้นในการกำหนดF = random.choice(choicesF)วงใน ฉันได้รับการเร่งความเร็ว 3x ด้วยวิธีการดังกล่าว
Steven Rumbalski

3
วิธีการเกี่ยวกับการรวบรวมสิ่งนี้ใน Cython? ถ้าอย่างนั้นเพิ่มประเภทสถิตไหวพริบที่ไม่กี่คำ
Thane Brimhall

2
ใส่ทุกอย่างไว้ในฟังก์ชั่นแล้วเรียกมันว่าท้ายที่สุด นั่นแปลชื่อซึ่งทำให้การเพิ่มประสิทธิภาพที่เสนอโดย @riffraff ทำงาน นอกจากนี้ให้ย้ายการสร้างrange(iters)ออกจากลูป โดยรวมฉันได้รับการเร่งประมาณ 7% จากคำตอบที่ดีมากของคุณ
WolframH

44

สนิม: 0.011 วินาที

Python ดั้งเดิม: 8.3

การแปลแบบตรงของ Python ดั้งเดิม

extern crate rand;

use rand::Rng;

static N: uint = 6;
static ITERS: uint = 1000;

fn convolve<T: Num>(into: &mut [T], a: &[T], b: &[T]) {
    // we want `a` to be the longest array
    if a.len() < b.len() {
        convolve(into, b, a);
        return
    }

    assert_eq!(into.len(), a.len() - b.len() + 1);

    for (n,place) in into.mut_iter().enumerate() {
        for (x, y) in a.slice_from(n).iter().zip(b.iter()) {
            *place = *place + *x * *y
        }
    }
}

fn main() {
    let mut first_zero = 0;
    let mut both_zero = 0;
    let mut rng = rand::XorShiftRng::new().unwrap();

    for s in PlusMinus::new() {
        for _ in range(0, ITERS) {
            let mut f = [0, .. N];
            while f.iter().all(|x| *x == 0) {
                for p in f.mut_iter() {
                    match rng.gen::<u32>() % 4 {
                        0 => *p = -1,
                        1 | 2 => *p = 0,
                        _ => *p = 1
                    }
                }
            }

            let mut fs = [0, .. 2];
            convolve(fs, s, f);

            if fs[0] == 0 { first_zero += 1 }
            if fs.iter().all(|&x| x == 0) { both_zero += 1 }
        }
    }

    println!("{}\n{}", first_zero, both_zero);
}



/// An iterator over [+-]1 arrays of the appropriate length
struct PlusMinus {
    done: bool,
    current: [i32, .. N + 1]
}
impl PlusMinus {
    fn new() -> PlusMinus {
        PlusMinus { done: false, current: [-1, .. N + 1] }
    }
}

impl Iterator<[i32, .. N + 1]> for PlusMinus {
    fn next(&mut self) -> Option<[i32, .. N+1]> {
        if self.done {
            return None
        }

        let ret = self.current;

        // a binary "adder", that just adds one to a bit vector (where
        // -1 is the zero, and 1 is the one).
        for (i, place) in self.current.mut_iter().enumerate() {
            *place = -*place;
            if *place == 1 {
                break
            } else if i == N {
                // we've wrapped, so we want to stop after this one
                self.done = true
            }
        }

        Some(ret)
    }
}
  • รวบรวมด้วย --opt-level=3
  • คอมไพเลอร์สนิมของฉันคือเมื่อคืนนี้: ( rustc 0.11-pre-nightly (eea4909 2014-04-24 23:41:15 -0700)เพื่อความแม่นยำ)

ฉันได้มันมาเพื่อรวบรวมโดยใช้สนิมในยามค่ำคืน อย่างไรก็ตามฉันคิดว่ารหัสผิด ผลลัพธ์ควรใกล้เคียงกับ firstzero 27215 bothzero 12086 แทนที่จะให้ 27367 6481

@ Lembik โอ้โหเอาas และ s ของฉันbมาปะปนกัน คงที่ (ไม่เปลี่ยนรันไทม์อย่างเห็นได้ชัด)
huon

4
เป็นการสาธิตความเร็วของสนิมที่ดีมาก

39

C ++ (VS 2012) - 0.026s 0.015s

Python 2.7.6 / Numpy 1.8.1 - 12s

Speedup ~ x800

ช่องว่างจะเล็กกว่ามากถ้าอาร์เรย์ที่ได้รับการแปลงนั้นใหญ่มาก ...

#include <vector>
#include <iostream>
#include <ctime>

using namespace std;

static unsigned int seed = 35;

int my_random()
{
   seed = seed*1664525UL + 1013904223UL; // numerical recipes, 32 bit

   switch((seed>>30) & 3)
   {
   case 0: return 0;
   case 1: return -1;
   case 2: return 1;
   case 3: return 0;
   }
   return 0;
}

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

void convolve(vector<int>& out, const vector<int>& v1, const vector<int>& v2)
{
   for(size_t i = 0; i<out.size(); ++i)
   {
      int result = 0;
      for(size_t j = 0; j<v2.size(); ++j)
      {
         result += v1[i+j]*v2[j];
      }
      out[i] = result;
   }
}

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

void convolve_random_arrays(void)
{
   const size_t n = 6;
   const int two_to_n_plus_one = 128;
   const int iters = 1000;
   int bothzero = 0;
   int firstzero = 0;

   vector<int> S(n+1);
   vector<int> F(n);
   vector<int> FS(2);

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

   for(auto &x : S)
   {
      x = -1;
   }
   for(int i=0; i<two_to_n_plus_one; ++i)
   {
      for(int j=0; j<iters; ++j)
      {
         do
         {
            for(auto &x : F)
            {
               x = my_random();
            }
         } while(allzero(F));
         convolve(FS, S, F);
         if(FS[0] == 0)
         {
            firstzero++;
            if(FS[1] == 0)
            {
               bothzero++;
            }
         }
      }
      advance(S);
   }
   cout << firstzero << endl; // This output can slow things down
   cout << bothzero << endl; // comment out for timing the algorithm
}

หมายเหตุเล็กน้อย:

  • ฟังก์ชั่นแบบสุ่มนั้นถูกเรียกใช้ในลูปดังนั้นฉันจึงไปหาเครื่องกำเนิดเชิงเส้นเชิงเส้นที่มีน้ำหนักเบามาก
  • นี่เป็นเพียงจุดเริ่มต้นสำหรับโซลูชันที่ปรับให้เหมาะสมที่สุด
  • ใช้เวลาไม่นานในการเขียน ...
  • ฉันวนซ้ำทุกค่าของการ S S[0]เป็นตัวเลข "สำคัญน้อยที่สุด"

เพิ่มฟังก์ชั่นหลักนี้สำหรับตัวอย่างที่มีอยู่ในตัว:

int main(int argc, char** argv)
{
  for(int i=0; i<1000; ++i) // run 1000 times for stop-watch
  {
      convolve_random_arrays();
  }
}

1
จริง ขนาดเล็ก ๆ ของอาร์เรย์ในรหัสของ OP หมายถึงการใช้ numpy เป็นจริงขนาดของลำดับที่ช้ากว่าไพ ธ อนแบบตรง
Alistair Buxton

2
ตอนนี้ x800 คือสิ่งที่ฉันกำลังพูดถึง!

ดีมาก! ฉันได้เพิ่มความเร็วโค้ดของคุณเนื่องจากadvanceฟังก์ชั่นของคุณตอนนี้โค้ดของฉันก็เร็วกว่าของคุณ: P (แต่การแข่งขันที่ดีมาก!)
Kyle Kanos

1
@lembik ใช่แล้วตามที่ Mat พูด คุณต้องมี C ++ 11 supprt และฟังก์ชั่นหลัก แจ้งให้เราทราบหากคุณต้องการความช่วยเหลือเพิ่มเติมเพื่อให้เรื่องนี้ทำงาน ...
Guy Sirton

2
ฉันเพิ่งผ่านการทดสอบนี้และอาจโกนอีก 20% โดยใช้อาร์เรย์ธรรมดาแทนมาตรฐาน :: เวกเตอร์ ..
PlasmaHH

21

C

ใช้เวลา 0.015 วินาทีบนเครื่องของฉันโดยใช้รหัสดั้งเดิมของ OP ใช้ ~ 7.7s พยายามเพิ่มประสิทธิภาพโดยการสร้างอาเรย์แบบสุ่มและโน้มน้าวในลูปเดียวกัน แต่ดูเหมือนว่าจะไม่ได้สร้างความแตกต่างมากนัก

อาร์เรย์แรกถูกสร้างขึ้นโดยจดจำนวนเต็มเขียนมันเป็นเลขฐานสองและเปลี่ยนทั้ง 1 เป็น -1 และทั้งหมด 0 เป็น 1 ส่วนที่เหลือควรตรงไปตรงมามาก

แก้ไข:แทนที่จะต้องnเป็นintตอนนี้เรามีnเป็นแมโครที่กำหนดไว้อย่างต่อเนื่องดังนั้นเราจึงสามารถใช้แทนint arr[n];malloc

แก้ไข 2:แทนที่จะใช้rand()ฟังก์ชันในตัวตอนนี้ใช้ xorshift PRNG นอกจากนี้งบเงื่อนไขจำนวนมากจะถูกลบออกเมื่อสร้างอาร์เรย์แบบสุ่ม

รวบรวมคำแนะนำ:

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

รหัส:

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

#define n (6)
#define iters (1000)
unsigned int x,y=34353,z=57768,w=1564; //PRNG seeds

/* xorshift PRNG
 * Taken from https://en.wikipedia.org/wiki/Xorshift#Example_implementation
 * Used under CC-By-SA */
int myRand() {
    unsigned int t;
    t = x ^ (x << 11);
    x = y; y = z; z = w;
    return w = w ^ (w >> 19) ^ t ^ (t >> 8);
}

int main() {
    int firstzero=0, bothzero=0;
    int arr[n+1];
    unsigned int i, j;
    x=(int)time(NULL);

    for(i=0; i< 1<<(n+1) ; i++) {
        unsigned int tmp=i;
        for(j=0; j<n+1; j++) {
            arr[j]=(tmp&1)*(-2)+1;
            tmp>>=1;
        }
        for(j=0; j<iters; j++) {
            int randArr[n];
            unsigned int k, flag=0;
            int first=0, second=0;
            do {
                for(k=0; k<n; k++) {
                    randArr[k]=(1-(myRand()&3))%2;
                    flag+=(randArr[k]&1);
                    first+=arr[k]*randArr[k];
                    second+=arr[k+1]*randArr[k];
                }
            } while(!flag);
            firstzero+=(!first);
            bothzero+=(!first&&!second);
        }
    }
    printf("firstzero %d\nbothzero %d\n", firstzero, bothzero);
    return 0;
}

1
ฉันทดสอบสิ่งนี้ มันเร็วมาก (ลอง n = 10) และให้ผลลัพธ์การค้นหาที่ถูกต้อง ขอขอบคุณ.

การใช้งานนี้ไม่เป็นไปตามต้นฉบับเพราะหากเวกเตอร์แบบสุ่มเป็นศูนย์ทั้งหมดองค์ประกอบสุดท้ายเท่านั้นที่จะถูกสร้างขึ้นใหม่ ในต้นฉบับเวกเตอร์ทั้งหมดจะเป็น คุณต้องล้อมวงนั้นไว้do{}while(!flag)หรืออะไรก็ตามที่มีผลกระทบนั้น ฉันไม่คาดหวังว่ามันจะเปลี่ยนเวลาทำงานมาก (อาจทำให้เร็วขึ้น)
Guy Sirton

@Guy Sirton สังเกตว่าก่อนที่จะมีcontinue;คำสั่งที่ผมได้รับมอบหมายให้-1ไปkดังนั้นkจะห่วงจาก 0 อีกครั้ง
ace_HongKongIndependence

1
@ace ah! คุณถูก. ฉันสแกนเร็วเกินไปและดูเหมือนว่า-=จะเป็น=-:-) A ขณะที่ลูปจะอ่านได้มากกว่า
Guy Sirton

17

J

ฉันไม่ได้คาดหวังว่าจะเอาชนะภาษาที่คอมไพล์ได้และมีบางอย่างบอกฉันว่าจะต้องใช้เครื่องจักรที่น่าอัศจรรย์เพื่อให้ได้น้อยกว่า 0.09 วินาทีด้วยสิ่งนี้ แต่ฉันต้องการส่ง J ต่อไปนี้เพราะมันค่อนข้างเรียบ

NB. constants
num =: 6
iters =: 1000

NB. convolve
NB. take the multiplication table                */
NB. then sum along the NE-SW diagonals           +//.
NB. and keep the longest ones                    #~ [: (= >./) #/.
NB. operate on rows of higher dimensional lists  " 1
conv =: (+//. #~ [: (= >./) #/.) @: (*/) " 1

NB. main program
S  =: > , { (num+1) # < _1 1                NB. all {-1,1}^(num+1)
F  =: (3&= - 0&=) (iters , num) ?@$ 4       NB. iters random arrays of length num
FS =: ,/ S conv/ F                          NB. make a convolution table
FB =: +/ ({. , *./)"1 ] 0 = FS              NB. first and both zero
('first zero ',:'both zero ') ,. ":"0 FB    NB. output results

สิ่งนี้ใช้เวลาประมาณ 0.5 วินาทีบนแล็ปท็อปจากทศวรรษก่อนหน้าเพียง 20x เร็วเท่า Python ในคำตอบ ส่วนใหญ่ใช้เวลาconvเพราะเราเขียนมันอย่างเกียจคร้าน (เราคำนวณการบิดทั้งหมด) และโดยทั่วไป

เนื่องจากเรารู้เกี่ยวกับSและFเราสามารถเร่งดำเนินการโดยการเพิ่มประสิทธิภาพเฉพาะสำหรับโปรแกรมนี้ สิ่งที่ดีที่สุดที่ฉันสามารถทำได้คือ - conv =: ((num, num+1) { +//.)@:(*/)"1เลือกตัวเลขสองตัวที่สอดคล้องกันจากผลบวกในแนวทแยงไปยังองค์ประกอบที่ยาวที่สุดของการโน้มน้าว - ซึ่งประมาณครึ่งหนึ่ง


6
J เป็นคนที่ควรค่าแก่การส่งเสมอ :)
Vitaly Dyatlov

17

Perl - 9.3X เร็วขึ้น ... เพิ่มขึ้น 830%

ในเน็ตบุ๊กโบราณของฉันรหัสของ OP ใช้เวลา 53 วินาทีในการทำงาน เวอร์ชันของ Alistair Buxton ใช้เวลาประมาณ 6.5 วินาทีและรุ่น Perl ต่อไปนี้ใช้เวลาประมาณ 5.7 วินาที

use v5.10;
use strict;
use warnings;

use Algorithm::Combinatorics qw( variations_with_repetition );
use List::Util qw( any sum );
use List::MoreUtils qw( pairwise );

my $n         = 6;
my $iters     = 1000;
my $firstzero = 0;
my $bothzero  = 0;

my $variations = variations_with_repetition([-1, 1], $n+1);
while (my $S = $variations->next)
{
  for my $i (1 .. $iters)
  {
    my @F;
    until (@F and any { $_ } @F)
    {
      @F = map +((-1,0,0,1)[rand 4]), 1..$n;
    }

    # The pairwise function doesn't accept array slices,
    # so need to copy into a temp array @S0
    my @S0 = @$S[0..$n-1];

    unless (sum pairwise { $a * $b } @F, @S0)
    {
      $firstzero++;
      my @S1 = @$S[1..$n];  # copy again :-(
      $bothzero++ unless sum pairwise { $a * $b } @F, @S1;
    }
  }
}

say "firstzero ", $firstzero;
say "bothzero ", $bothzero;

12

Python 2.7 - numpy 1.8.1 พร้อม mkl bindings - 0.086s

(ต้นฉบับของ OP: 6.404 วินาที) (Python บริสุทธิ์ของ Buxton: 0.270s)

import numpy as np
import itertools

n=6
iters = 1000

#Pack all of the Ses into a single array
S = np.array( list(itertools.product([-1,1], repeat=n+1)) )

# Create a whole array of test arrays, oversample a bit to ensure we 
# have at least (iters) of them
F = np.random.rand(int(iters*1.1),n)
F = ( F < 0.25 )*-1 + ( F > 0.75 )*1
goodrows = (np.abs(F).sum(1)!=0)
assert goodrows.sum() > iters, "Got very unlucky"
# get 1000 cases that aren't all zero
F = F[goodrows][:iters]

# Do the convolution explicitly for the two 
# slots, but on all of the Ses and Fes at the 
# same time
firstzeros = (F[:,None,:]*S[None,:,:-1]).sum(-1)==0
secondzeros = (F[:,None,:]*S[None,:,1:]).sum(-1)==0

firstzero_count = firstzeros.sum()
bothzero_count = (firstzeros * secondzeros).sum()
print "firstzero", firstzero_count
print "bothzero", bothzero_count

ดังที่บักซ์ตันชี้ให้เห็นรหัสดั้งเดิมของ OP ใช้อาร์เรย์ขนาดเล็กดังกล่าวไม่มีประโยชน์ในการใช้ Numpy การใช้งานนี้ยกระดับ numpy โดยทำทุกกรณี F และ S ในครั้งเดียวในลักษณะเชิงอาร์เรย์ สิ่งนี้รวมกับการเชื่อม mkl สำหรับ python ทำให้การใช้งานรวดเร็วมาก

โปรดทราบว่าเพียงแค่โหลดไลบรารีและการเริ่มใช้ล่ามใช้เวลา 0.076 วินาทีดังนั้นการคำนวณจริงจะใช้เวลา ~ 0.01 วินาทีคล้ายกับโซลูชัน C ++


mkl bindings คืออะไรและฉันจะนำไปใช้กับ Ubuntu ได้อย่างไร

การทำงานpython -c "import numpy; numpy.show_config()"จะแสดงให้คุณเห็นว่าเวอร์ชั่นของคุณถูกรวบรวมกับ blas / atlas / mkl หรือไม่ ฯลฯATLASเป็นแพ็คเกจทางคณิตศาสตร์แบบเร่งความเร็วฟรีที่ผู้ใช้สามารถเชื่อมโยงกับก้อน นั้นIntel MKL ที่คุณต้องจ่าย (ยกเว้นว่าคุณเป็นนักวิชาการ) และสามารถเชื่อมโยงกับ numpy
alemi

สำหรับวิธีง่าย ๆ ให้ใช้การแจกจ่ายงูใหญ่และใช้แพ็คเกจเร่งความเร็ว หรือใช้การกระจายความกระตือรือร้น
alemi

หากคุณอยู่ในหน้าต่างเพียงดาวน์โหลด numpy จากที่นี่ ตัวติดตั้งแบบรวบรวมล่วงหน้าเชื่อมโยงกับ MKL
ชื่อปลอม

9

MATLAB 0.024 วินาที

คอมพิวเตอร์ 1

  • รหัสดั้งเดิม: ~ 3.3 วิ
  • รหัสของ Alistar Buxton: ~ 0.51 s
  • รหัสใหม่ของ Alistar Buxton: ~ 0.25 s
  • รหัส Matlab: ~ 0.024 s (Matlab ทำงานอยู่แล้ว)

คอมพิวเตอร์ 2

  • รหัสดั้งเดิม: ~ 6.66 วิ
  • รหัสของ Alistar Buxton: ~ 0.64 s
  • รหัสใหม่ของ Alistar Buxton:
  • Matlab: ~ 0.07 s (Matlab ทำงานอยู่แล้ว)
  • อ็อกเทฟ: ~ 0.07 วิ

ฉันตัดสินใจลองใช้ Matlab ช้ามาก ถ้าคุณรู้วิธีคุณสามารถกำจัดลูปส่วนใหญ่ (ใน Matlab) ซึ่งทำให้มันค่อนข้างเร็ว อย่างไรก็ตามความต้องการของหน่วยความจำจะสูงกว่าสำหรับวิธีการวนลูป แต่จะไม่เป็นปัญหาหากคุณไม่มีอาร์เรย์ที่มีขนาดใหญ่มาก ...

function call_convolve_random_arrays
tic
convolve_random_arrays
toc
end

function convolve_random_arrays

n = 6;
iters = 1000;
firstzero = 0;
bothzero = 0;

rnd = [-1, 0, 0, 1];

S = -1 *ones(1, n + 1);

IDX1 = 1:n;
IDX2 = IDX1 + 1;

for i = 1:2^(n + 1)
    F = rnd(randi(4, [iters, n]));
    sel = ~any(F,2);
    while any(sel)
        F(sel, :) = rnd(randi(4, [sum(sel), n]));
        sel = ~any(F,2);
    end

    sum1 = F * S(IDX1)';
    sel = sum1 == 0;
    firstzero = firstzero + sum(sel);

    sum2 = F(sel, :) * S(IDX2)';
    sel = sum2 == 0;
    bothzero = bothzero + sum(sel);

    S = permute(S); 
end

fprintf('firstzero %i \nbothzero %i \n', firstzero, bothzero);

end

function x = permute(x)

for i=1:length(x)
    if(x(i)==-1)
        x(i) = 1;
            return
    end
        x(i) = -1;
end

end

นี่คือสิ่งที่ฉันทำ:

  • ใช้ฟังก์ชัน Kyle Kanos เพื่อเปลี่ยนรูปแบบผ่าน S
  • คำนวณทั้งหมด n * ซ้ำตัวเลขสุ่มในครั้งเดียว
  • แผนที่ 1 ถึง 4 ถึง [-1 0 0 1]
  • ใช้การคูณเมทริกซ์ (ผลรวมตามองค์ประกอบ (F * S (1: 5)) เท่ากับการคูณเมทริกซ์ของ F * S (1: 5) '
  • สำหรับทั้งศูนย์: คำนวณเฉพาะสมาชิกที่เต็มเงื่อนไขแรก

ฉันคิดว่าคุณไม่มี matlab ซึ่งแย่เกินไปเพราะฉันชอบที่จะดูว่ามันเปรียบเทียบ ...

(ฟังก์ชั่นอาจช้าลงในครั้งแรกที่คุณเรียกใช้)


ฉันมีอ็อกเทฟถ้าคุณสามารถทำให้มันใช้ได้สำหรับ ...

ฉันสามารถลองได้ - ฉันไม่เคยทำงานกับอ็อกเทฟ
คณิตศาสตร์

ตกลงฉันสามารถเรียกใช้ได้เหมือนอยู่ในระดับแปดเสียงถ้าฉันใส่รหัสในไฟล์ชื่อ call_convolve_random_arrays.m แล้วเรียกมันจากระดับแปดเสียง
คณิตศาสตร์

จำเป็นต้องมีรหัสเพิ่มเติมหรือไม่เพื่อให้สามารถทำอะไรได้จริง เมื่อฉันทำ "octave call_convolve_random_arrays.m" มันจะไม่แสดงผลอะไรเลย ดูbpaste.net/show/JPtLOCeI3aP3wc3F3aGf

ขออภัยลองเปิดอ็อกเทฟและเรียกใช้จากนั้น มันควรแสดง firstzero ทั้งศูนย์และเวลาดำเนินการ
คณิตศาสตร์

7

จูเลีย: 0.30 วิ

Op's Python: 21.36 s (Core2 duo)

การเร่งความเร็ว 71x

function countconv()                                                                                                                                                           
    n = 6                                                                                                                                                                      
    iters = 1000                                                                                                                                                               
    firstzero = 0                                                                                                                                                              
    bothzero = 0                                                                                                                                                               
    cprod= Iterators.product(fill([-1,1], n+1)...)                                                                                                                             
    F=Array(Float64,n);                                                                                                                                                        
    P=[-1. 0. 0. 1.]                                                                                                                                                                                                                                                                                                             

    for S in cprod                                                                                                                                                             
        Sm=[S...]                                                                                                                                                              
        for i = 1:iters                                                                                                                                                        
            F=P[rand(1:4,n)]                                                                                                                                                  
            while all(F==0)                                                                                                                                                   
                F=P[rand(1:4,n)]                                                                                                                                              
            end                                                                                                                                                               
            if  dot(reverse!(F),Sm[1:end-1]) == 0                                                                                                                           
                firstzero += 1                                                                                                                                                 
                if dot(F,Sm[2:end]) == 0                                                                                                                              
                    bothzero += 1                                                                                                                                              
                end                                                                                                                                                            
            end                                                                                                                                                                
        end                                                                                                                                                                    
    end
    return firstzero,bothzero
end

ฉันทำการแก้ไขคำตอบ Julia ของ Arman: ก่อนอื่นฉันห่อมันไว้ในฟังก์ชั่นเนื่องจากตัวแปรทั่วโลกทำให้ยากสำหรับการอนุมานแบบของ Julia และ JIT: ตัวแปรทั่วโลกสามารถเปลี่ยนชนิดได้ตลอดเวลาและต้องตรวจสอบทุกการทำงาน . จากนั้นฉันก็กำจัดฟังก์ชั่นนิรนามและอาเรย์ความเข้าใจ พวกเขาไม่จำเป็นจริงๆและยังค่อนข้างช้า Julia เร็วขึ้นด้วย abstractions ระดับต่ำกว่าตอนนี้

มีหลายวิธีที่จะทำให้มันเร็วขึ้น แต่มันก็ทำงานได้ดี


คุณวัดเวลาใน REPL หรือรันไฟล์ทั้งหมดจากบรรทัดคำสั่งหรือไม่
Aditya

ทั้งจาก REPL
user20768

6

ตกลงฉันโพสต์สิ่งนี้เพียงเพราะฉันรู้สึกว่า Java จำเป็นต้องแสดงที่นี่ ฉันแย่มากกับภาษาอื่นและฉันยอมรับที่จะไม่เข้าใจปัญหาอย่างแน่นอนดังนั้นฉันจึงต้องการความช่วยเหลือในการแก้ไขรหัสนี้ ฉันขโมยตัวอย่าง C ของรหัส ace ส่วนใหญ่แล้วยืมตัวอย่างจากคนอื่น ฉันหวังว่ามันจะไม่ใช่มารยาท ...

สิ่งหนึ่งที่ฉันต้องการชี้ให้เห็นก็คือภาษาที่ปรับให้เหมาะสมในเวลาทำงานต้องมีการรันหลายครั้ง / หลายครั้งเพื่อให้ได้ความเร็วสูงสุด ฉันคิดว่ามันสมเหตุสมผลที่จะใช้ความเร็วที่ปรับให้เหมาะสมเต็มที่ (หรืออย่างน้อยก็ความเร็วเฉลี่ย) เพราะสิ่งที่คุณกังวลเกี่ยวกับการวิ่งเร็วจะวิ่งหลายครั้ง

รหัสยังคงต้องได้รับการแก้ไข แต่ฉันวิ่งต่อไปเพื่อดูว่าฉันจะได้รับกี่ครั้ง

นี่คือผลลัพธ์ของ CPU Intel (R) Xeon (R) E3-1270 V2 @ 3.50GHz บน Ubuntu ที่ใช้งาน 1,000 ครั้ง:

เซิร์ฟเวอร์: / tmp # time java8 -cp Tester

เลขศูนย์ก่อน 40000

bothzero 20000

เวลาทำงานครั้งแรก: 41 มิลลิวินาทีเวลาทำงานล่าสุด: 4 มิลลิวินาที

จริง 0m5.014s ผู้ใช้ 0m4.664s sys 0m0.268s

นี่คือรหัสเส็งเคร็งของฉัน:

public class Tester 
{
    public static void main( String[] args )
    {
        long firstRunTime = 0;
        long lastRunTime = 0;
        String testResults = null;
        for( int i=0 ; i<1000 ; i++ )
        {
            long timer = System.currentTimeMillis();
            testResults = new Tester().runtest();
            lastRunTime = System.currentTimeMillis() - timer;
            if( i ==0 )
            {
                firstRunTime = lastRunTime;
            }
        }
        System.err.println( testResults );
        System.err.println( "first run time: " + firstRunTime + " ms" );
        System.err.println( "last run time: " + lastRunTime + " ms" );
    }

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

    public String runtest()
    {
        int n = 6;
        int iters = 1000;
        //#define iters (1000)
        //PRNG seeds

        /* xorshift PRNG
         * Taken from https://en.wikipedia.org/wiki/Xorshift#Example_implementation
         * Used under CC-By-SA */

            int firstzero=0, bothzero=0;
            int[] arr = new int[n+1];
            int i=0, j=0;
            x=(int)(System.currentTimeMillis()/1000l);

            for(i=0; i< 1<<(n+1) ; i++) {
                int tmp=i;
                for(j=0; j<n+1; j++) {
                    arr[j]=(tmp&1)*(-2)+1;
                    tmp>>=1;
                }
                for(j=0; j<iters; j++) {
                    int[] randArr = new int[n];
                    int k=0;
                    long flag = 0;
                    int first=0, second=0;
                    do {
                        for(k=0; k<n; k++) {
                            randArr[k]=(1-(myRand()&3))%2;
                            flag+=(randArr[k]&1);
                            first+=arr[k]*randArr[k];
                            second+=arr[k+1]*randArr[k];
                        }
                    } while(allzero(randArr));
                    if( first == 0 )
                    {
                        firstzero+=1;
                        if( second == 0 )
                        {
                            bothzero++;
                        }
                    }
                }
            }
         return ( "firstzero " + firstzero + "\nbothzero " + bothzero + "\n" );
    }

    private boolean allzero(int[] arr)
    {
       for(int x : arr)
       {
          if(x!=0)
          {
             return false;
          }
       }
       return true;
    }

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

และฉันพยายามเรียกใช้รหัสหลามหลังจากอัปเกรด python และติดตั้ง python-numpy แต่ฉันได้รับสิ่งนี้:

server:/tmp# python tester.py
Traceback (most recent call last):
  File "peepee.py", line 15, in <module>
    F = np.random.choice(np.array([-1,0,0,1], dtype=np.int8), size = n)
AttributeError: 'module' object has no attribute 'choice'

ความคิดเห็น: อย่าใช้currentTimeMillisสำหรับการเปรียบเทียบ (ใช้รุ่นนาโนในระบบ) และการรัน 1k อาจไม่เพียงพอที่จะทำให้ JIT เกี่ยวข้อง (1.5k สำหรับไคลเอนต์และ 10k สำหรับเซิร์ฟเวอร์จะเป็นค่าเริ่มต้นแม้ว่าคุณจะเรียก myRand บ่อยเพียงพอที่จะ JITed ซึ่งน่าจะทำให้ฟังก์ชั่นบางอย่างเพิ่มขึ้น callstack ซึ่งอาจทำงานได้ที่นี่) สุดท้าย แต่ไม่น้อย PNRG ที่อ่อนแอคือการโกง แต่วิธีการแก้ปัญหา C ++ และอื่น ๆ ทำให้ฉันเดาว่ามันไม่ยุติธรรมเกินไป
Voo

บน windows คุณต้องหลีกเลี่ยง currentTimeMillis แต่สำหรับ linux สำหรับการวัดละเอียดทั้งหมดที่ละเอียดมากคุณไม่จำเป็นต้องใช้เวลา nano และการเรียกเพื่อรับ nano time นั้นแพงกว่ามิลลิวินาทีมาก ดังนั้นฉันไม่เห็นด้วยอย่างยิ่งที่คุณไม่ควรใช้
Chris Seline

ดังนั้นคุณกำลังเขียนโค้ด Java สำหรับระบบปฏิบัติการและ JVM หนึ่งระบบ อันที่จริงผมไม่แน่ใจว่าที่ OS ที่คุณกำลังใช้เพราะผมเพียงแค่การตรวจสอบในต้นไม้ dev ของฉัน HotSpot และ Linux ใช้gettimeofday(&time, NULL)สำหรับมิลลิวินาทีซึ่งไม่ monotonical และไม่ให้การค้ำประกันความถูกต้องใด ๆ (ดังนั้นในบางแพลตฟอร์ม / เมล็ดตรงเดียวกัน ปัญหาที่เกิดจากการใช้งาน Windows ปัจจุบัน currentmillis - เพื่อให้เป็นหนึ่งในนั้นที่ดีเกินไปหรือไม่เป็น) ในทางกลับกัน nanoTime ใช้clock_gettime(CLOCK_MONOTONIC, &tp)ซึ่งเห็นได้ชัดว่าเป็นสิ่งที่ถูกต้องที่จะใช้เมื่อเปรียบเทียบกับ Linux
Voo

มันไม่เคยทำให้เกิดปัญหาสำหรับฉันตั้งแต่ฉันได้รับการเข้ารหัสจาวาบนลินุกซ์ distro หรือเคอร์เนลใด ๆ
Chris Seline

6

งูหลามรุ่น 45X ของ Golang บนเครื่องของฉันด้านล่างรหัส Golang:

package main

import (
"fmt"
"time"
)

const (
n     = 6
iters = 1000
)

var (
x, y, z, w = 34353, 34353, 57768, 1564 //PRNG seeds
)

/* xorshift PRNG
 * Taken from https://en.wikipedia.org/wiki/Xorshift#Example_implementation
 * Used under CC-By-SA */
func myRand() int {
var t uint
t = uint(x ^ (x << 11))
x, y, z = y, z, w
w = int(uint(w^w>>19) ^ t ^ (t >> 8))
return w
}

func main() {
var firstzero, bothzero int
var arr [n + 1]int
var i, j int
x = int(time.Now().Unix())

for i = 0; i < 1<<(n+1); i = i + 1 {
    tmp := i
    for j = 0; j < n+1; j = j + 1 {
        arr[j] = (tmp&1)*(-2) + 1
        tmp >>= 1
    }
    for j = 0; j < iters; j = j + 1 {
        var randArr [n]int
        var flag uint
        var k, first, second int
        for {
            for k = 0; k < n; k = k + 1 {
                randArr[k] = (1 - (myRand() & 3)) % 2
                flag += uint(randArr[k] & 1)
                first += arr[k] * randArr[k]
                second += arr[k+1] * randArr[k]
            }
            if flag != 0 {
                break
            }
        }
        if first == 0 {
            firstzero += 1
            if second == 0 {
                bothzero += 1
            }
        }
    }
}
println("firstzero", firstzero, "bothzero", bothzero)
}

และรหัสหลามด้านล่างคัดลอกมาจากด้านบน:

import itertools
import operator
import random

n=6
iters = 1000
firstzero = 0
bothzero = 0

choicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))

for S in itertools.product([-1,1], repeat = n+1):
    for i in xrange(iters):
        F = random.choice(choicesF)
        if not sum(map(operator.mul, F, S[:-1])):
            firstzero += 1
            if not sum(map(operator.mul, F, S[1:])):
                bothzero += 1

print "firstzero", firstzero
print "bothzero", bothzero

และเวลาด้านล่าง:

$time python test.py
firstzero 27349
bothzero 12125

real    0m0.477s
user    0m0.461s
sys 0m0.014s

$time ./hf
firstzero 27253 bothzero 12142

real    0m0.011s
user    0m0.008s
sys 0m0.002s

1
คุณคิดจะใช้"github.com/yanatan16/itertools"ไหม คุณจะบอกว่าสิ่งนี้จะทำงานได้ดีในหลาย goroutines?
ymg

5

C # 0.135 วินาที

C # อ้างอิงจากงูหลามธรรมดาของ Alistair Buxton : 0.278s
Parallelised C #: 0.135s
Python จากคำถาม: 5.907s
งูหลามธรรมดาของ Alistair: 0.853s

ฉันไม่แน่ใจว่าการใช้งานนี้ถูกต้องหรือไม่ผลลัพธ์ของมันแตกต่างกันหากคุณดูผลลัพธ์ที่ด้านล่าง

มีอัลกอริธึมที่เหมาะสมกว่าอย่างแน่นอน ฉันเพิ่งตัดสินใจใช้อัลกอริทึมที่คล้ายกันมากกับ Python

C เธรดเดียว

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConvolvingArrays
{
    static class Program
    {
        static void Main(string[] args)
        {
            int n=6;
            int iters = 1000;
            int firstzero = 0;
            int bothzero = 0;

            int[] arraySeed = new int[] {-1, 1};
            int[] randomSource = new int[] {-1, 0, 0, 1};
            Random rand = new Random();

            foreach (var S in Enumerable.Repeat(arraySeed, n+1).CartesianProduct())
            {
                for (int i = 0; i < iters; i++)
                {
                    var F = Enumerable.Range(0, n).Select(_ => randomSource[rand.Next(randomSource.Length)]);
                    while (!F.Any(f => f != 0))
                    {
                        F = Enumerable.Range(0, n).Select(_ => randomSource[rand.Next(randomSource.Length)]);
                    }
                    if (Enumerable.Zip(F, S.Take(n), (f, s) => f * s).Sum() == 0)
                    {
                        firstzero++;
                        if (Enumerable.Zip(F, S.Skip(1), (f, s) => f * s).Sum() == 0)
                        {
                            bothzero++;
                        }
                    }
                }
            }

            Console.WriteLine("firstzero {0}", firstzero);
            Console.WriteLine("bothzero {0}", bothzero);
        }

        // itertools.product?
        // http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
        static IEnumerable<IEnumerable<T>> CartesianProduct<T>
            (this IEnumerable<IEnumerable<T>> sequences)
        {
            IEnumerable<IEnumerable<T>> emptyProduct =
              new[] { Enumerable.Empty<T>() };
            return sequences.Aggregate(
              emptyProduct,
              (accumulator, sequence) =>
                from accseq in accumulator
                from item in sequence
                select accseq.Concat(new[] { item }));
        }
    }
}

Parallel C #:

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConvolvingArrays
{
    static class Program
    {
        static void Main(string[] args)
        {
            int n=6;
            int iters = 1000;
            int firstzero = 0;
            int bothzero = 0;

            int[] arraySeed = new int[] {-1, 1};
            int[] randomSource = new int[] {-1, 0, 0, 1};

            ConcurrentBag<int[]> results = new ConcurrentBag<int[]>();

            // The next line iterates over arrays of length n+1 which contain only -1s and 1s
            Parallel.ForEach(Enumerable.Repeat(arraySeed, n + 1).CartesianProduct(), (S) =>
            {
                int fz = 0;
                int bz = 0;
                ThreadSafeRandom rand = new ThreadSafeRandom();
                for (int i = 0; i < iters; i++)
                {
                    var F = Enumerable.Range(0, n).Select(_ => randomSource[rand.Next(randomSource.Length)]);
                    while (!F.Any(f => f != 0))
                    {
                        F = Enumerable.Range(0, n).Select(_ => randomSource[rand.Next(randomSource.Length)]);
                    }
                    if (Enumerable.Zip(F, S.Take(n), (f, s) => f * s).Sum() == 0)
                    {
                        fz++;
                        if (Enumerable.Zip(F, S.Skip(1), (f, s) => f * s).Sum() == 0)
                        {
                            bz++;
                        }
                    }
                }

                results.Add(new int[] { fz, bz });
            });

            foreach (int[] res in results)
            {
                firstzero += res[0];
                bothzero += res[1];
            }

            Console.WriteLine("firstzero {0}", firstzero);
            Console.WriteLine("bothzero {0}", bothzero);
        }

        // itertools.product?
        // http://ericlippert.com/2010/06/28/computing-a-cartesian-product-with-linq/
        static IEnumerable<IEnumerable<T>> CartesianProduct<T>
            (this IEnumerable<IEnumerable<T>> sequences)
        {
            IEnumerable<IEnumerable<T>> emptyProduct =
              new[] { Enumerable.Empty<T>() };
            return sequences.Aggregate(
              emptyProduct,
              (accumulator, sequence) =>
                from accseq in accumulator
                from item in sequence
                select accseq.Concat(new[] { item }));
        }
    }

    // http://stackoverflow.com/a/11109361/1030702
    public class ThreadSafeRandom
    {
        private static readonly Random _global = new Random();
        [ThreadStatic]
        private static Random _local;

        public ThreadSafeRandom()
        {
            if (_local == null)
            {
                int seed;
                lock (_global)
                {
                    seed = _global.Next();
                }
                _local = new Random(seed);
            }
        }
        public int Next()
        {
            return _local.Next();
        }
        public int Next(int maxValue)
        {
            return _local.Next(maxValue);
        }
    }
}

ผลการทดสอบ:

Windows (. NET)

C # นั้นเร็วกว่ามากใน Windows อาจเป็นเพราะ. NET นั้นเร็วกว่าโมโน

ดูเหมือนว่าเวลาของผู้ใช้และระบบจะไม่ทำงาน (ใช้git bashสำหรับการกำหนดเวลา)

$ time /c/Python27/python.exe numpypython.py
firstzero 27413
bothzero 12073

real    0m5.907s
user    0m0.000s
sys     0m0.000s
$ time /c/Python27/python.exe plainpython.py
firstzero 26983
bothzero 12033

real    0m0.853s
user    0m0.000s
sys     0m0.000s
$ time ConvolvingArrays.exe
firstzero 28526
bothzero 6453

real    0m0.278s
user    0m0.000s
sys     0m0.000s
$ time ConvolvingArraysParallel.exe
firstzero 28857
bothzero 6485

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

Linux (ขาวดำ)

bob@phoebe:~/convolvingarrays$ time python program.py
firstzero 27059
bothzero 12131

real    0m11.932s
user    0m11.912s
sys     0m0.012s
bob@phoebe:~/convolvingarrays$ mcs -optimize+ -debug- program.cs
bob@phoebe:~/convolvingarrays$ time mono program.exe
firstzero 28982
bothzero 6512

real    0m1.360s
user    0m1.532s
sys     0m0.872s
bob@phoebe:~/convolvingarrays$ mcs -optimize+ -debug- parallelprogram.cs
bob@phoebe:~/convolvingarrays$ time mono parallelprogram.exe
firstzero 28857
bothzero 6496

real    0m0.851s
user    0m2.708s
sys     0m3.028s

1
ฉันไม่คิดว่ารหัสถูกต้องตามที่คุณพูด เอาต์พุตไม่ถูกต้อง

@ Lembik ใช่ ฉันจะขอบคุณถ้ามีคนบอกฉันว่ามันผิดตรงไหน - ฉันไม่สามารถเข้าใจได้ (มีเพียงความเข้าใจเพียงเล็กน้อยเกี่ยวกับสิ่งที่ควรจะทำไม่ได้ช่วย)
บ๊อบ

จะน่าสนใจที่จะดูว่าสิ่งนี้เกิดขึ้นกับ. NET Native blogs.msdn.com/b/dotnet/archive/2014/04/02/ อย่างไร
Rick Minerich

@ Lembik ฉันได้ไปทั้งหมดแล้วเท่าที่ฉันสามารถบอกได้ว่ามันควรจะเหมือนกับโซลูชัน Python อื่น ๆ ... ตอนนี้ฉันสับสนจริงๆ
บ๊อบ

4

Haskell: ~ 2000x speedup ต่อหนึ่งคอร์

คอมไพล์ด้วย 'ghc -O3 -funbox-เคร่งครัด-fields -threaded -fllvm' และเรียกใช้ด้วย '+ RTS -Nk' โดยที่ k คือจำนวนแกนประมวลผลในเครื่องของคุณ

import Control.Parallel.Strategies
import Data.Bits
import Data.List
import Data.Word
import System.Random

n = 6 :: Int
iters = 1000 :: Int

data G = G !Word !Word !Word !Word deriving (Eq, Show)

gen :: G -> (Word, G)
gen (G x y z w) = let t  = x `xor` (x `shiftL` 11)
                      w' = w `xor` (w `shiftR` 19) `xor` t `xor` (t `shiftR` 8)
                  in (w', G y z w w')  

mask :: Word -> Word
mask = (.&.) $ (2 ^ n) - 1

gen_nonzero :: G -> (Word, G)
gen_nonzero g = let (x, g') = gen g 
                    a = mask x
                in if a == 0 then gen_nonzero g' else (a, g')


data F = F {zeros  :: !Word, 
            posneg :: !Word} deriving (Eq, Show)

gen_f :: G -> (F, G)       
gen_f g = let (a, g')  = gen_nonzero g
              (b, g'') = gen g'
          in  (F a $ mask b, g'')

inner :: Word -> F -> Int
inner s (F zs pn) = let s' = complement $ s `xor` pn
                        ones = s' .&. zs
                        negs = (complement s') .&. zs
                    in popCount ones - popCount negs

specialised_convolve :: Word -> F -> (Int, Int)
specialised_convolve s f@(F zs pn) = (inner s f', inner s f) 
    where f' = F (zs `shiftL` 1) (pn `shiftL` 1)

ss :: [Word]
ss = [0..2 ^ (n + 1) - 1]

main_loop :: [G] -> (Int, Int)
main_loop gs = foldl1' (\(fz, bz) (fz', bz') -> (fz + fz', bz + bz')) . parMap rdeepseq helper $ zip ss gs
    where helper (s, g) = go 0 (0, 0) g
                where go k u@(fz, bz) g = if k == iters 
                                              then u 
                                              else let (f, g') = gen_f g
                                                       v = case specialised_convolve s f
                                                               of (0, 0) -> (fz + 1, bz + 1)
                                                                  (0, _) -> (fz + 1, bz)
                                                                  _      -> (fz, bz)
                                                   in go (k + 1) v g'

seed :: IO G                                        
seed = do std_g <- newStdGen
          let [x, y, z, w] = map fromIntegral $ take 4 (randoms std_g :: [Int])
          return $ G x y z w

main :: IO ()
main = (sequence $ map (const seed) ss) >>= print . main_loop

2
ดังนั้นด้วย 4 แกนมันมากกว่า 9000 ?! ไม่มีทางที่จะถูก
Cees Timmerman

กฎหมายของ Amdahlระบุว่าการเร่งความเร็วในการขนานนั้นไม่ตรงกับจำนวนหน่วยการประมวลผลแบบขนาน แทนที่จะให้ผลตอบแทนที่ลดลงเท่านั้น
xaedes

@xaedes การเร่งความเร็วดูเหมือนเป็นเส้นตรงสำหรับจำนวนแกนที่น้อย
user1502040

3

ทับทิม

Ruby (2.1.0) 0.277s
Ruby (2.1.1) 0.281s
Python (Alistair Buxton) 0.330s
Python (alemi) 0.097s

n = 6
iters = 1000
first_zero = 0
both_zero = 0

choices = [-1, 0, 0, 1].repeated_permutation(n).select{|v| [0] != v.uniq}

def convolve(v1, v2)
  [0, 1].map do |i|
    r = 0
    6.times do |j|
      r += v1[i+j] * v2[j]
    end
    r
  end
end

[-1, 1].repeated_permutation(n+1) do |s|
  iters.times do
    f = choices.sample
    fs = convolve s, f
    if 0 == fs[0]
      first_zero += 1
      if 0 == fs[1]
        both_zero += 1
      end
    end
  end
end

puts 'firstzero %i' % first_zero
puts 'bothzero %i' % both_zero

3

ด้ายคงจะไม่สมบูรณ์ถ้าไม่มี PHP

6.6x เร็วขึ้น

PHP v5.5.9 - 1.223 0.646 วินาที

VS

Python v2.7.6 - 8.072 วินาที

<?php

$n = 6;
$iters = 1000;
$firstzero = 0;
$bothzero = 0;

$x=time();
$y=34353;
$z=57768;
$w=1564; //PRNG seeds

function myRand() {
    global $x;
    global $y;
    global $z;
    global $w;
    $t = $x ^ ($x << 11);
    $x = $y; $y = $z; $z = $w;
    return $w = $w ^ ($w >> 19) ^ $t ^ ($t >> 8);
}

function array_cartesian() {
    $_ = func_get_args();
    if (count($_) == 0)
        return array();
    $a = array_shift($_);
    if (count($_) == 0)
        $c = array(array());
    else
        $c = call_user_func_array(__FUNCTION__, $_);
    $r = array();
    foreach($a as $v)
        foreach($c as $p)
            $r[] = array_merge(array($v), $p);
    return $r;
}

function rand_array($a, $n)
{
    $r = array();
    for($i = 0; $i < $n; $i++)
        $r[] = $a[myRand()%count($a)];
    return $r;
}

function convolve($a, $b)
{
    // slows down
    /*if(count($a) < count($b))
        return convolve($b,$a);*/
    $result = array();
    $w = count($a) - count($b) + 1;
    for($i = 0; $i < $w; $i++){
        $r = 0;
        for($k = 0; $k < count($b); $k++)
            $r += $b[$k] * $a[$i + $k];
        $result[] = $r;
    }
    return $result;
}

$cross = call_user_func_array('array_cartesian',array_fill(0,$n+1,array(-1,1)));

foreach($cross as $S)
    for($i = 0; $i < $iters; $i++){
        while(true)
        {
            $F = rand_array(array(-1,0,0,1), $n);
            if(in_array(-1, $F) || in_array(1, $F))
                break;
        }
        $FS = convolve($S, $F);
        if(0==$FS[0]) $firstzero += 1;
        if(0==$FS[0] && 0==$FS[1]) $bothzero += 1;
    }

echo "firstzero $firstzero\n";
echo "bothzero $bothzero\n";
  • ใช้ตัวสร้างแบบสุ่มที่กำหนดเอง (ถูกขโมยจากคำตอบ C), PHP หนึ่ง sucks และตัวเลขไม่ตรงกัน
  • convolve ฟังก์ชั่นง่ายขึ้นเล็กน้อยเพื่อให้เร็วขึ้น
  • การตรวจสอบอาเรย์แบบมีศูนย์มีเพียงอย่างเดียวก็ถูกปรับให้เหมาะสมเช่นกัน (ดู$Fและ$FSตรวจสอบ)

ขาออก:

$ time python num.py 
firstzero 27050
bothzero 11990

real    0m8.072s
user    0m8.037s
sys 0m0.024s
$ time php num.php
firstzero 27407
bothzero 12216

real    0m1.223s
user    0m1.210s
sys 0m0.012s

แก้ไข สคริปต์รุ่นที่สองใช้งานได้เพื่อ0.646 sec:

<?php

$n = 6;
$iters = 1000;
$firstzero = 0;
$bothzero = 0;

$x=time();
$y=34353;
$z=57768;
$w=1564; //PRNG seeds

function myRand() {
    global $x;
    global $y;
    global $z;
    global $w;
    $t = $x ^ ($x << 11);
    $x = $y; $y = $z; $z = $w;
    return $w = $w ^ ($w >> 19) ^ $t ^ ($t >> 8);
}

function array_cartesian() {
    $_ = func_get_args();
    if (count($_) == 0)
        return array();
    $a = array_shift($_);
    if (count($_) == 0)
        $c = array(array());
    else
        $c = call_user_func_array(__FUNCTION__, $_);
    $r = array();
    foreach($a as $v)
        foreach($c as $p)
            $r[] = array_merge(array($v), $p);
    return $r;
}

function convolve($a, $b)
{
    // slows down
    /*if(count($a) < count($b))
        return convolve($b,$a);*/
    $result = array();
    $w = count($a) - count($b) + 1;
    for($i = 0; $i < $w; $i++){
        $r = 0;
        for($k = 0; $k < count($b); $k++)
            $r += $b[$k] * $a[$i + $k];
        $result[] = $r;
    }
    return $result;
}

$cross = call_user_func_array('array_cartesian',array_fill(0,$n+1,array(-1,1)));

$choices = call_user_func_array('array_cartesian',array_fill(0,$n,array(-1,0,0,1)));

foreach($cross as $S)
    for($i = 0; $i < $iters; $i++){
        while(true)
        {
            $F = $choices[myRand()%count($choices)];
            if(in_array(-1, $F) || in_array(1, $F))
                break;
        }
        $FS = convolve($S, $F);
        if(0==$FS[0]){
            $firstzero += 1;
            if(0==$FS[1])
                $bothzero += 1;
        }
    }

echo "firstzero $firstzero\n";
echo "bothzero $bothzero\n";

3

วิธีแก้ปัญหา F #

รันไทม์เป็น 0.030s เมื่อคอมไพล์เป็น x86 บน CLR Core i7 4 (8) @ 3.4 Ghz

ฉันไม่รู้ว่ารหัสถูกต้องหรือไม่

  • การเพิ่มประสิทธิภาพการใช้งาน (พับแบบอินไลน์) -> 0.026 วินาที
  • การสร้างผ่านโครงการคอนโซล -> 0.022 วินาที
  • เพิ่มอัลกอริธึมที่ดีกว่าสำหรับการสร้างอาร์เรย์การเปลี่ยนแปลง -> 0.018s
  • Mono สำหรับ Windows -> 0.089s
  • เรียกใช้สคริปต์ Python ของ Alistair -> 0.259 วินาที
let inline ffoldi n f state =
    let mutable state = state
    for i = 0 to n - 1 do
        state <- f state i
    state

let product values n =
    let p = Array.length values
    Array.init (pown p n) (fun i ->
        (Array.zeroCreate n, i)
        |> ffoldi n (fun (result, i') j ->
            result.[j] <- values.[i' % p]
            result, i' / p
        )
        |> fst
    )

let convolute signals filter =
    let m = Array.length signals
    let n = Array.length filter
    let len = max m n - min m n + 1

    Array.init len (fun offset ->
        ffoldi n (fun acc i ->
            acc + filter.[i] * signals.[m - 1 - offset - i]
        ) 0
    )

let n = 6
let iters = 1000

let next =
    let arrays =
        product [|-1; 0; 0; 1|] n
        |> Array.filter (Array.forall ((=) 0) >> not)
    let rnd = System.Random()
    fun () -> arrays.[rnd.Next arrays.Length]

let signals = product [|-1; 1|] (n + 1)

let firstzero, bothzero =
    ffoldi signals.Length (fun (firstzero, bothzero) i ->
        let s = signals.[i]
        ffoldi iters (fun (first, both) _ ->
            let f = next()
            match convolute s f with
            | [|0; 0|] -> first + 1, both + 1
            | [|0; _|] -> first + 1, both
            | _ -> first, both
        ) (firstzero, bothzero)
    ) (0, 0)

printfn "firstzero %i" firstzero
printfn "bothzero %i" bothzero

2

Q, 0.296 seg

n:6; iter:1000  /parametrization (constants)
c:n#0           /auxiliar constant (sequence 0 0.. 0 (n))
A:B:();         /A and B accumulates results of inner product (firstresult, secondresult)

/S=sequence with all arrays of length n+1 with values -1 and 1
S:+(2**m)#/:{,/x#/:-1 1}'m:|n(2*)\1 

f:{do[iter; F:c; while[F~c; F:n?-1 0 0 1]; A,:+/F*-1_x; B,:+/F*1_x];} /hard work
f'S               /map(S,f)
N:~A; +/'(N;N&~B) / ~A is not A (or A=0) ->bitmap.  +/ is sum (population over a bitmap)
                  / +/'(N;N&~B) = count firstResult=0, count firstResult=0 and secondResult=0

Q เป็นภาษาที่เน้นการรวบรวม (kx.com)

โค้ดเขียนใหม่เพื่อสำรวจสำนวน Q แต่ไม่มีการเพิ่มประสิทธิภาพที่ฉลาดอื่น ๆ

ภาษาสคริปต์เพิ่มประสิทธิภาพเวลาโปรแกรมเมอร์ไม่ใช่เวลาดำเนินการ

  • Q ไม่ใช่เครื่องมือที่ดีที่สุดสำหรับปัญหานี้

ความพยายามในการเข้ารหัสครั้งแรก = ไม่ใช่ผู้ชนะ แต่เป็นเวลาที่เหมาะสม (ความเร็วประมาณ 30x)

  • ค่อนข้างมีการแข่งขันกันระหว่างล่าม
  • หยุดและเลือกปัญหาอื่น

NOTES.-

  • โปรแกรมใช้เมล็ดเริ่มต้น (execs repeteable) เพื่อเลือกเมล็ดพันธุ์อื่นสำหรับการใช้เครื่องกำเนิดไฟฟ้าแบบสุ่ม \S seed
  • ผลลัพธ์ถูกกำหนดเป็นกำลังสองของ int ดังนั้นจึงมี i-suffix สุดท้ายที่ค่าที่สอง 27421 12133i -> อ่านเป็น (27241, 12133)
  • เวลาไม่นับการเริ่มต้นล่าม \t sentence เวลา mesures ที่ใช้โดยประโยคนั้น

ขอบคุณที่น่าสนใจมาก

1

Julia: 12.149 6.929 วิ

แม้พวกเขาจะอ้างว่าเร่งความเร็วแต่เวลารวบรวม JIT เริ่มต้นก็ทำให้เราย้อนกลับไป!

โปรดทราบว่ารหัส Julia ต่อไปนี้เป็นการแปลโดยตรงของรหัส Python ต้นฉบับ (ไม่มีการเพิ่มประสิทธิภาพ) เป็นการสาธิตที่คุณสามารถถ่ายโอนประสบการณ์การเขียนโปรแกรมของคุณเป็นภาษาที่เร็วขึ้นได้อย่างง่ายดาย;)

require("Iterators")

n = 6
iters = 1000
firstzero = 0
bothzero = 0

for S in Iterators.product(fill([-1,1], n+1)...)
    for i = 1:iters
        F = [[-1 0 0 1][rand(1:4)] for _ = 1:n]
        while all((x) -> round(x,8) == 0, F)
            F = [[-1 0 0 1][rand(1:4)] for _ = 1:n]
        end
        FS = conv(F, [S...])
        if round(FS[1],8) == 0
            firstzero += 1
        end
        if all((x) -> round(x,8) == 0, FS)
            bothzero += 1
        end
    end
end

println("firstzero ", firstzero)
println("bothzero ", bothzero)

แก้ไข

วิ่งด้วยn = 8ใช้เวลา 32.935 วินาที พิจารณาว่าความซับซ้อนของขั้นตอนวิธีนี้เป็นO(2^n)แล้ว4 * (12.149 - C) = (32.935 - C)ที่Cเป็นค่าคงที่เป็นตัวแทนของเวลา JIT รวบรวม การแก้ปัญหาสำหรับCเราพบว่าC = 5.2203แนะนำเวลาการดำเนินการจริงสำหรับn = 66.929 วินาที


แล้วการเพิ่ม n เป็น 8 เพื่อดูว่า Julia มาเป็นของตัวเองหรือไม่?

นี้จะไม่สนใจหลายเคล็ดลับประสิทธิภาพที่นี่: julia.readthedocs.org/en/latest/manual/performance-tips ดูรายการ Julia อื่น ๆ ซึ่งทำได้ดีกว่ามาก ส่งเป็นที่นิยมแม้ว่า :-)
StefanKarpinski

0

สนิม, 6.6 ms, 1950x speedup

การแปลโดยตรงจากโค้ดของAlistair Buxton เป็น Rust ฉันคิดว่าการใช้หลายแกนกับเรยอน (ไม่เห็นพ้องด้วยความกลัว!) แต่นี่ไม่ได้ปรับปรุงประสิทธิภาพการทำงานอาจเป็นเพราะมันเร็วมากแล้ว

extern crate itertools;
extern crate rand;
extern crate time;

use itertools::Itertools;
use rand::{prelude::*, prng::XorShiftRng};
use std::iter;
use time::precise_time_ns;

fn main() {
    let start = precise_time_ns();

    let n = 6;
    let iters = 1000;
    let mut first_zero = 0;
    let mut both_zero = 0;
    let choices_f: Vec<Vec<i8>> = iter::repeat([-1, 0, 0, 1].iter().cloned())
        .take(n)
        .multi_cartesian_product()
        .filter(|i| i.iter().any(|&x| x != 0))
        .collect();
    // xorshift RNG is faster than default algorithm designed for security
    // rather than performance.
    let mut rng = XorShiftRng::from_entropy(); 
    for s in iter::repeat(&[-1, 1]).take(n + 1).multi_cartesian_product() {
        for _ in 0..iters {
            let f = rng.choose(&choices_f).unwrap();
            if f.iter()
                .zip(&s[..s.len() - 1])
                .map(|(a, &b)| a * b)
                .sum::<i8>() == 0
            {
                first_zero += 1;
                if f.iter().zip(&s[1..]).map(|(a, &b)| a * b).sum::<i8>() == 0 {
                    both_zero += 1;
                }
            }
        }
    }
    println!("first_zero = {}\nboth_zero = {}", first_zero, both_zero);

    println!("runtime {} ns", precise_time_ns() - start);
}

และ Cargo.toml เนื่องจากฉันใช้การอ้างอิงภายนอก:

[package]
name = "how_slow_is_python"
version = "0.1.0"

[dependencies]
itertools = "0.7.8"
rand = "0.5.3"
time = "0.1.40"

เปรียบเทียบความเร็ว:

$ time python2 py.py
firstzero: 27478
bothzero: 12246
12.80user 0.02system 0:12.90elapsed 99%CPU (0avgtext+0avgdata 23328maxresident)k
0inputs+0outputs (0major+3544minor)pagefaults 0swaps
$ time target/release/how_slow_is_python
first_zero = 27359
both_zero = 12162
runtime 6625608 ns
0.00user 0.00system 0:00.00elapsed 100%CPU (0avgtext+0avgdata 2784maxresident)k
0inputs+0outputs (0major+189minor)pagefaults 0swaps

6625608 ns ประมาณ 6.6 ms นี่หมายถึงการเพิ่มความเร็วขึ้น 1950 เท่า มีการเพิ่มประสิทธิภาพหลายอย่างที่เป็นไปได้ที่นี่ แต่ฉันจะอ่านง่ายกว่าประสิทธิภาพ การปรับให้เหมาะสมที่เป็นไปได้อย่างหนึ่งคือการใช้อาร์เรย์แทนเวกเตอร์สำหรับการจัดเก็บตัวเลือกเนื่องจากจะมีnองค์ประกอบอยู่เสมอ นอกจากนี้ยังเป็นไปได้ที่จะใช้ RNG นอกเหนือจาก XorShift ในขณะที่ Xorshift เร็วกว่า HC-128 CSPRNG ที่เป็นค่าเริ่มต้นมันช้าที่สุดในการอัลกอริทึม PRNG

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.