Наскільки повільний справді Python (частина II)?


52

Це подання на те, наскільки повільно Python насправді? (Або наскільки швидкою є ваша мова?) .

Виявляється, для мого останнього запитання було надто просто отримати швидкість x100. Для тих, хто сподобався виклику, але хоче щось важче, де реально використовувати свої навички низького рівня, ось частина II. Завдання полягає в тому, щоб отримати прискорення x100 для наступного коду python, перевіреного на моєму комп’ютері.

Щоб зробити це складніше, я зараз використовую pypy. Поточний час для мене становить 1 хвилину та 7 секунд за допомогою pypy 2.2.1.

Правила

  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]

Ви повинні використовувати випадковий насіння у своєму коді, і будь-який генератор випадкових чисел, який достатньо хороший, щоб дати відповіді, близькі до вище, буде прийнято.

My Machine Часи синхронізуються на моїй машині. Це стандартна установка ubuntu на восьмиядерний процесор AMD FX-8350. Це також означає, що мені потрібно мати можливість запускати ваш код.

Пояснення коду

Цей код повторює всі масиви S довжиною n + m-1, складені для -1s та 1s. Для кожного масиву S він вибирає 1000 ненульових випадкових масивів F довжиною n, що складається з -1,0 або 1, з вірогідністю 1/4, 1/2, / 14 прийому кожного значення. Потім він обчислює внутрішні добутки між F і кожним вікном S довжиною n, поки не знайде ненульовий внутрішній продукт. Він додає 1 до leadingzerocountsкожної позиції, в якій він знайшов нульовий внутрішній продукт.

Статус

  • Perl . Уповільнення у 2,7 рази від @tobyink. (У порівнянні з pypy not cpython.)

  • Дж . 39-кратне прискорення від @Eelvex.

  • C . 59 разів пришвидшити @ace.
  • Юлія . У 197 разів швидше, не враховуючи час запуску через @ ще одну хвилину. 8,5 разів пришвидшити, включаючи час запуску (у цьому випадку швидше використовувати 4 процесори, ніж 8).
  • Фортран . У 438 разів пришвидшити @ напівекзонічний.
  • Рпітон . 258 разів пришвидшити @primo.
  • C ++ . 508 разів пришвидшити @ilmale.

(Я припинив призначати нові вдосконалення, оскільки вони занадто швидкі, і вона була занадто маленькою.)


Було зазначено, що терміни, що знаходяться нижче секунди, є ненадійними, а також деякі мови мають стартову вартість. Аргумент полягає в тому, що якщо ви хочете включити, ви також повинні включати час компіляції C / C ++ тощо. Ось терміни для найшвидшого коду, кількість ітерацій збільшена до 100 000

  • Юлія . 42 секунди за хвилину @ ще одну хвилину.
  • C ++ . 14 секунд від @GuySirton.
  • Фортран . 14-х років @ напівекзонічні.
  • C ++ . 12-х років від @ilmale.
  • Рпітон . 18-х років від @primo.
  • C ++ . 5s від @Stefan.

Переможець - Стефан!

Наступні виклики розміщені. Як високо ви можете піти? (Виклик кодування + алгоритми) . Це важче.


3
пояснення того, що, на думку припущенного коду, було б непогано, тому ми можемо переписати його, а не просто
перенести

6
" Перший, хто подав код, який я можу запустити, є правильним і на 100 разів швидше на моїй машині перемагає негайно, і змагання закриваються. Яка мета закрити змагання таким? Чому б не використовувати крайній термін дати, як і більшість інших, і ми можемо бачити, що це сприяє скороченню інших мов?
grovesNL

5
@Einacio Це хороша ідея. Я змінив правила, які, сподіваюся, ніхто не заперечує.

1
@Lembik Я покращив свою версію Fortran, зробивши її в 2 рази швидшою на своїй машині. Не могли б ви знову? :)
напівнеземний

1
@ напівекзонічне Виконано.

Відповіді:


12

C ++ бітова магія

~ 16ms багатопотокове, 56ms однопоточне. ~ 4000 прискорення.

(прискорення базується на багатопотоковому коді на моєму i7-2820QM та 1 хвилині 9 секунд, зазначеному в запитанні. Оскільки система ОП має більш низьку продуктивність однопотокової, ніж мій процесор, але краще багатопотокове виконання, я очікую, що це число буде точним)

Багатопоточна частина досить неефективна через нересту ниток. Можливо, я міг би зробити це краще, використовуючи свою власну бібліотеку завдань, але в ній є помилки під 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 він працює чудово. Я виправлю код пізніше сьогодні.
Стефан

Я отримую Stefan.cpp: 104: 101: error: 'memcpy' не було оголошено в цій області memcpy (out.data () + (threadId * m) ,adingZeros.data (), sizeof (LeadZeros [0]) * m );

Я думаю, що мені потрібно включити string.h. Спробуйте ще раз.
Стефан

Ви компілюєте це з 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

Причина Sта Fмає різне уявлення , я повинен чергування Sз самими собою , щоб бути порівнянними з F.

  • 0 (-1) став 00 (-1 у поданні F)
  • 1 (+1) став 11 (+1 у представництві F)

Тепер ми можемо просто використовувати Карно для обчислення внутрішнього продукту. Пам’ятайте, що одна змінна може приймати лише значення 00 або 11

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

Схоже, це не xor для мене. :)

Підсумовуючи ці, це лише гра на зміну та маску, нічого насправді складного.

#include <array>
#include <ctime>

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

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

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

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

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

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

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

Ось зразок виводу:

6332350 2525218 1041716 438741 192917 87159 41023 19908 

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

Програма складена з:

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

на Fedora 20 з gcc 4.8.2 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 і змінюючи порядок на для мене, ви отримаєте х3 посилення, що призводить до покращення продуктивності x450 порівняно з кодом OP. : 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 Як запропонований користувачем71404, я змінив функції sumOnes та sumArray, і тепер це швидко uber.

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 на наступне (схоже, це дасть мені 2x швидкість над версією openmp). inline int32_t sumOnes(int32_t v) { /* 0xAAAA == 0b1010 1010 1010 1010 */ return !! (0xAAAA & (v ^ ~(v << 1))); } inline int32_t sumArray(int32_t v) { return __builtin_popcount(v) - 8; }це запропонував @ user71404
ace_HongKongIndependence

@ user71404: profiler сказав, що програма витрачала весь час на ці дві функції, але я вчора дуже втомився, думаю, що краще, ніж це. Я спробую сьогодні ввечері (UTC). Дякую.
ilmale

Чи не заперечуєте ви, щоб другий фрагмент коду був повним копією та проставним кодом? Я, мабуть, робив щось не так у своїх спробах змусити ваш код openmp працювати так, що це дуже допоможе.

Приємно. Я думав, що це можна зробити за допомогою бітових операцій.
Гай Сіртон

10

Юлія: 0,7s, 120x швидше

Як показав user20768, прямий порт коду до Джулії приблизно вдвічі швидший, ніж 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 проти 1м22.

Якщо у вас достатньо ядер на вашому комп’ютері і, можливо, накручується кілька екземплярів AWS, ви повинні мати можливість поголитись ще :)


Я цілком впевнений, що ви неправильно вимірюєте час. Python with Pypy також є мовою, що базується на JIT, але таймінги, зроблені ОП, включають час компіляції JIT. Ви виключаєте це. Я встановив останню версію Джулії git і перевірив ваш код, і на моїй машині ваша команда з 8 процесами займає 6,6 секунди, але вона друкує "минув час 0,588 .. секунд".
напів зовнішній

Час роботи Python включає в себе запуск PyPy та розминку JIT, але це займає максимум секунд - різниця за хвилину часу виконання незначна. Я щасливий, якщо ОП змінить спосіб приурочення Python (це не матиме ніякої різниці), але включати час запуску Julia було б нерозумно.
одна хвилина

Я запитав ОП у коментарях до оригінального запитання, і він сказав: "Тимінг повинен містити все для мов JIT". Він також заявив, що створить новий виклик, коли рішення займуть набагато більше часу, ніж 1 секунда, залишивши Джулію у змаганні.
напівкрединне

У цьому випадку оптимальним рішенням є використання послідовного алгоритму, який займає близько 2 с. Я б опублікував код, але ця конкуренція зараз дещо зайва, оскільки всі вже знають, що C ++ завантажується швидше за все.
одна хвилина

Я щойно опублікував своє рішення Fortran, тому не знаю, чому ви не повинні розміщувати найшвидший варіант Джулії (якщо у вас вже є код).
напів зовнішній

5

С, 1.210с

З кодом OP на моїй машині працює 1м45,729.

Збірка:

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

Особлива подяка: @dyp за компіляційні прапори та ідеї для оптимізацій.

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

#define n (8)
#define m (8)
#define iters (1000)
int leadingzerocounts[m]; // declared as global so initialised to 0
unsigned int x,y=34353,z=57768,w=1564; //PRNG seeds

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

int dotproduct(int*F, int*S) {
    unsigned int i;
    int sum=0;
    for(i=0; i<n; i++) {
        sum+=F[i]*S[i];
    }
    return sum;
}

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

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

Вибірка зразка:

6334411 2527506 1042239 439328 192914 87005 40847 19728

1
Дійсно, що я можу зробити подібні спостереження, коли скидаю всі ці прапори з оптимізації. Спробуйте -march=native -fwhole-program -fstrict-aliasing -ftree-vectorizeBtw. Я перейшов до <4 с, використовуючи деякі C ++ 11, включаючи MT19937 плюс a uniform_int_distribution.
dyp

1
1.119 робить швидкість приблизно 59!

1
@ace Так, я просто хотів це зазначити. Для мене було простіше просто спробувати деякі зі стандартних бібліотечних PRNG в C ++. Зауважте, що ви можете використовувати один 32-розрядний цілий результат з PRNG для створення 8 записів для F.
dyp

1
Оскільки nдорівнює 8, ви можете, ймовірно, використовувати AVX (або 2 * SSE) для обчислення дотпродукту з належним Sсховищем.
Майкл М.

2
Версія SSE, невелика швидкість: gist.github.com/anonymous/11394210 (не забудьте включити smmintrin.h)
Майкл М.

5

Perl

Це ніде не так швидко, як рішення C, але я думаю, що це досить швидко для мови, що інтерпретується на високому рівні. Це голить приблизно 40% від часу роботи програми Python.

#!/usr/bin/env perl

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

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

my @leadingzerocounts;

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

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

say join ", ", @leadingzerocounts;

Алгоритм :: Комбінаторика доступна в Ubuntu ( sudo apt-get install libalgorithm-combinatorics-perl). Інші модулі, що використовуються, є основними модулями Perl, тому їх уже слід встановити як частину базової установки Ubuntu.


1
Це не вплине на швидкість, але це 0..N-1діапазон в останньому map, правда? Ви забули use warnings? :-) Хоча логіка в ОП заплутана, розсувне вікно ніколи не потрапляє до останнього елемента S.
користувач2846289

Ага. Я просто зрозумів, що масиви мають невідповідні розміри, тому я відключив, warningsщо дозволяє відсутній елемент трактуватись як нуль. N-1покращує це. І це насправді покращує швидкість дуже незначно - зараз це приблизно на 40% швидше, ніж реалізація Python.
tobyink

Я думаю, що для вашого коду потрібна дуже сучасна версія List :: Util. У ubuntu 14.04 я отримую "будь-яке", не експортується модулем List :: Util

О так, це правда - вам, ймовірно, потрібно буде встановити List :: Util off CPAN. anyАльтернативно можна знайти в Список :: MoreUtils, який хоча і не є основним модулем, є одним з найбільш часто використовуваних модулів CPAN.
tobyink

4

Юлія: 4.66x повільніше!

Я справді починаю сумніватися у статистиці на їхньому веб-сайті ...

Зауважте, що наступний код Julia - це фактично пряма транскрипція коду Python OP без будь-яких оптимізацій. Я використовую time()функцію, щоб виключити повільний час запуску Джулії ...

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 м 32.912 с

Код OP в PyPy: 1 м 11.506 с

Вихід Джулії:

6332170
2525472
1041522
438761
193119
86873
40705
19662

7
+1 за вашу <s> безсоромність </s> спортивність.
ace_HongKongIndependence

Глобальні змінні, імпорт та масив розуміються повільно. Як правило, не можна писати програму Julia для виконання.
Олексій А.

4

RPython 0.187s (258x швидше)

Першоджерело w / PyPy2.2.1: 1m 6.718s

Тепер із нанизуванням, підтримка спинки для стандартного Python відпала. Кількість робочих ниток можна вказати як параметр командного рядка, за замовчуванням - два.

from time import time, sleep
from math import fmod

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

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

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

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

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

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

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

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

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

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

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

  env.running += 1

  # use the main process as a worker
  run()

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

  print env.counts
  return 0

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

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

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

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

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

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

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

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

  env.lock.release()

def target(*args):
  return main, None

RPython - обмежена підмножина Python, яку можна перекласти на C, а потім компілювати за допомогою RPython Toolchain . Її виражене призначення - допомогти у створенні перекладачів мови, але воно також може бути використане для складання простих програм, таких як вище. Більшість "фантазійніших" функцій 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як біт-карту: [ 0, 1] → [ -1, 1]

Крім того, Fтакож трохи карту, з кожних двох бітів , що представляють одне значення:
[ 00, 01, 10, 11] → [ -1, 0, 0, 1]

Таблиця істинності використовується для пошуку продукту, а не виконання мультиплікації.

Оскільки використовуються 32-бітні цілі числа, вони nможуть бути не більше 15 і n+mне більше 31. Довільна ціла підтримка може бути досягнута за допомогою rpython.rlib.rbigintмодуля, якщо це необхідно.

Перша ітерація циклу крапки-продукту розгортається та поєднується з тестом на нікчемність F.

Використовується домашній PRNG, вказане джерело. Автор статті демонструє період 2 32 -1, і стверджує, що він пройшов усі тести Дієхара, окрім одного, хоча я особисто цього не підтвердив.

Випадкове насіння змінюється кожні мілісекунди, що настільки ж добре, як і використання часової позначки дозволить. Крім того, кожен працівник розшифровує xorсвій ідентифікатор процесу з цим значенням, щоб гарантувати, що кожен з них має інше насіння.


Зразок часу

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

Оригінальне джерело ОП:

$ 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,03 секунди?

@Lembik Я хотів би його побачити. Я подумав, що 4.7s був досить гарним, враховуючи, що моя перша спроба чистого пітона була ~ 15s.
прим

Так, вибачте за затримку. У мене ще не запущений код, але якнайшвидше.

Спробуйте додати JIT. Тепер це було б швидко!
kirbyfan64sos

@ Lembik дякую за згадку;) З цікавості, чи швидше це було з 4-х робочих ниток, або 8?
примо

3

Джулія: 1 хв 21,4 с (2,2 рази швидше) (модифікація коду Армана)

Код Op в PyPy: 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 

Є деякі проблеми з кодом Армана, що робить його дуже повільним: він використовує безліч анонімних функцій та функцій вищого порядку, без потреби. Щоб перевірити, чи весь вектор F дорівнює нулю, чому б просто не написати всі (F == 0) замість усіх (x-> x == 0, F)? Це коротше, і буквально в тисячу разів швидше.

Він також використовує sum (map (*, x, y)) як крапковий продукт замість просто крапки (x, y). Перша версія в 650 разів повільніше для вектора в 10 кб. А функція крапкового продукту реалізована як цикл для чистої Юлії.

Крім того, розуміння масиву відбувається повільно. Краще написати [0,1,0, -1] [rand (1: 4, n)] замість [[-1 0 0 1] [rand (1: 4)] для j = 1: n] .

Нарешті, глобальні змінні є поганим дзюджу у Джулії. Джулія швидко, тільки якщо ви пишете код таким чином, що дозволяє JIT і типу висновку працювати. Велика частина цього полягає в стабільності типу: компілятор повинен бути в змозі бути впевненим, що тип змінної не зміниться, наприклад, всередині циклу.


Дякую! Я бачу, що в мене ще досить небагато дізнатися про мову Джулії, перш ніж я можу подати претензії щодо її швидкості :) Дуже радий бачити, що кілька дрібницьких виправлень мого коду збільшують час його виконання у кілька разів.
спритний агар

2

Німрод

import times, locks, strutils, unsigned

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

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

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

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

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

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

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

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

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

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

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

main()

Приклад виводу:

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

Nimrod компілюється в C, тому вибір компілятора C також є важливим.

Використовуючи кланг, компілюйте з:

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якщо у вас є старший компілятор C, який не підтримує LTO. Пропустіть --cc=...опцію, якщо ви добре обрали за замовчуванням компілятор C. Для коду потрібен Nimrod 0.9.4 або 0.9.5 .

У моєму чотирикоротному iMac (ядро i5 2,66 ГГц) код працює приблизно за .15 секунд з gcc 4.9, .16 секунд з клаксом, порівняно з 88 секундами для PyPy 2.2.1 (тобто 500+ разів прискорення). На жаль, у мене немає доступу до машини з більш ніж чотирма ядрами, на якій також встановлений PyPy, або де я міг би легко встановити PyPy, хоча я отримую приблизно 1 секунду (з великим шумом вимірювання) на 64-ядерному AMD Opteron 6376 1,4 ГГц (згідно / proc / cpuinfo) з gcc 4.4.6.

Реалізація намагається бути вірною оригіналу, а не оптимізувати код за рахунок читабельності, не відмовляючись від очевидних оптимізацій. Цікаво, що рекурсія в хвості initVecRand()трохи швидша, ніж цикл з інструкцією на розрив як з gcc, так і з клангом. Ручне розгортання однієї ітерації convolveтестового циклу всередині основного циклу також призвело до прискорення, імовірно, завдяки кращому прогнозуванню гілок.


Як отримати nimrod для ubuntu?

@Lembik Швидкий пошук Google дасть вам nimrod-lang.org/download.html
ace_HongKongIndependence

@ace Я також включив посилання у свій пост (хоча важко бачити синій на чорному зараз, коли я дивлюся на нього).
Реймер Берендс

Ви можете ще більше прискорити це, збільшивши розмір насіння до 128 біт: xorshift.di.unimi.it
user60561

2

Java

Я переклав вищевказане рішення 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

Це приблизно в 2,5 рази швидше, ніж відповідне рішення Java (я не виключав запуск VM). Це рішення Java приблизно в 142 рази швидше, ніж програма, виконана з PyPy.

Оскільки мене особисто зацікавило, я встановив iters100_000 для Java та C ++, але коефіцієнт 2,5 не зменшився на користь Java, якщо щось стало більше.

EDIT: Я запускав програми на 64-бітному ПК Linux Linux.

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

Це приблизно в 14 разів швидше, ніж рішення PyPy. (Вибір стандартної випадкової функції через функцію fastRandom призводить до часу виконання 5 секунд)


2

Python 3.5 + numpy 1.10.1, 3.76 секунди

Тести проводилися на моєму Macbook Pro. Код ОП займав ~ 6 хв на одній машині.

Причина, що я відповідаю на це питання насправді, полягає в тому, що я не маю 10 репутацій і не можу відповісти на частину I :-p

Останні кілька днів я намагаюся зрозуміти, як ефективно виконувати масові згортки з нумером (не покладаючись на сторонній пакет, навіть на наупі). Коли я зіткнувся з цією низкою проблем під час свого дослідження, я вирішив спробувати. Можливо, я пішов до цієї гри пізно, але ось моя спроба використання 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 = 'той же самий' та обробляв провідні та кінцеві елементи, які були марними.

На моєму Macbook Pro результати тестування дають 3,76 секунди . Коли я запустив код OP (модифікований на Python 3.5), у мене було близько 6 хвилин . Швидкість роботи приблизно в 100 разів.

Одним із недоліків є те, що оскільки масиви S та F мають бути збережені, потреба в пам'яті може бути проблемою, якщо розміри занадто великі.

Я використовував той самий метод для I частини, і я отримав на своєму ноутбуці швидкість ~ 60-100x.

Як я робив усе на своєму Macbook Pro, якби хтось міг перевірити мій код і повідомити мені, як це відбувається на вашій машині, я дуже вдячний!


1

J, 130x ~ 50x швидкість?

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

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

Часи на випадковому 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: - / Я думаю, що порядок збережеться.
Ельвакс

Не обов’язково ... i.imgur.com/n566hzw.png
ace_HongKongIndependence

Дійсно, не обов’язково.
Ельвакс

З якою проблемою у вас встановлення pypy?

1

C ++: x200 (4-ядерний i7, має масштабуватись до x400 на 8-ядерному)

Спроба більш простого рішення C ++ 11 (випробувана з VS 2012, gcc та clang) з паралелізацією.

Щоб зробити це для компіляції та запуску під Linux з gcc 4.8.1:

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

Фортран: 316х

Гаразд, Fortran: у мене це швидкість 106x 155x 160x 316x при використанні RNG Xorshift та OpenMP на 4-ядерному процесорі i7. Крім цього, великих хитрощів немає. Щоб ітератор побудував S, я просто використовую двійкове представлення 16-бітного цілого числа i. Ви зауважите, що крім вбудованого RNG та "ітератора" / відображення від i до S, код настільки ж високий, як і код Python.

Редагувати: видалено "якщо" в Xorshift, тепер використовуючи "r = abs (w / ...)" замість "r = w / ...". Переходить від 106x до 155x.

Edit2: Це генерує в 15 разів більше випадкових чисел, ніж рішення C ++. Якщо хтось має нульове накладне рішення для перетворення випадкового int у масив 0s та 1s у Fortran, я все вухо. Тоді ми могли би перемогти C ++ :)

Edit3: Перша редакція внесла помилку, як вказував Лембік. Це виправлено зараз, з невеликим покращенням швидкості. Я спробую використати пропозицію Eelvex, щоб отримати більшу швидкість.

Edit4: Профілювання вказувало на те, що перетворення в дійсне і назад до цілого числа з nint () відбувається повільно. Я замінив це на одне ціле ділення, яке робить масштабування та округлення, проходячи від 160x до 316x прискорення.

Зібрати:

gfortran -O3 -march = рідний -fopenmp golf.f90

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

leadingzerocounts=0

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

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

print *, leadingzerocounts

end

Приклад виводу:

$ time ./a.out
6329624 2524831 1039787 438809 193044 6860 40486 19517
./a.out 1,45s користувальницькі 0,00s система 746% процесор 0,192 всього

Код ОП:

$ час pypy golf.py
pypy golf.py 60.68s користувач 0.04s система 99% процесор 1: 00.74 всього


Що я використав у J, це попередній перелік 4 ^ n чисел у базовій-4, потім перетворений на триадичний і виключаючи 0. RNG просто вибирає із цього списку.
Вісімнадцять

Я не впевнений, що ваш код правильний. За 100 000 ітерацій я отримую 633140285 271390368 118307997 52751245 23725837 10744292 4944464 2388125, але я думаю, що це повинно бути ближче до 633170604 252560981 104156146 43911426 19316309 8713324 4073378 1959440. Це різна різниця.

1
А, дякую, @Lembik, моє редагування на пришвидшення (видалення if-заяви) справді було помилкою. Я оновив свій код, тому він повинен бути правильним зараз. Я спробую опублікувати версію, використовуючи пропозицію Eelvex пізніше.
напівкрединне

Це також прискорило це, здається!

Так, певне прискорення, я думаю. Я зрозумів, що додаю 1,0, а потім віднімаю 1,0 всередині тісного циклу.
напів зовнішній
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.