С ++ немного магии
0,84 мс с простым RNG, 1,67 мс с c ++ 11 std :: knuth
0,16 мс с небольшой алгоритмической модификацией (см. Редактирование ниже)
Реализация Python выполняется на моей установке за 7,97 секунды. Так что это в 9488–4772 раза быстрее, в зависимости от того, какую ГСЧ вы выберете.
#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-битные целые, а максимальная n
допустимое значение равно 16. Можно масштабировать реализацию до 31 бита путем изменения кода случайной генерации и до 63 битов, используя uint64_t вместо uint32_t.
редактировать
Следующая сверточная функция:
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,161мс. Ручное развертывание петли (не показано выше) составляет 0,150. Менее тривиальный n = 10, iter = 100000 кейс работает менее 250 мс. Я уверен, что смогу получить его за 50 мс, используя дополнительные ядра, но это слишком просто.
Это делается путем освобождения ветви внутреннего цикла и замены F и S цикла.
Если bothZero
не требуется, я могу сократить время выполнения до 0,02 мс, разреживая циклы по всем возможным S-массивам.
-std=c++0x -mpopcnt -O2
и требует 1,01 мс для запуска в 32-битном режиме (у меня под рукой нет 64-битной версии GCC).Python2.7 + Numpy 1.8.1: 10.242 с
Фортран 90+:
0,029 с0,003 с0,022 с0,010 сЧерт побери, ты проиграл! Здесь тоже нет ни капли распараллеливания, просто прямой Fortran 90+.
РЕДАКТИРОВАТЬ Я взял алгоритм Гая Сиртона для перестановки массива
S
(хорошая находка: D). Очевидно, у меня также были-g -traceback
активны флаги компилятора, которые замедляли этот код примерно до 0,017 с. В настоящее время я собираю это какДля тех, у кого нет
ifort
, вы можете использоватьРЕДАКТИРОВАТЬ 2 : Уменьшение времени выполнения, потому что я делал что-то не так ранее и получил неправильный ответ. Делать это правильно, по-видимому, медленнее. Я до сих пор не могу поверить, что C ++ работает быстрее, чем мой, поэтому я, вероятно, собираюсь потратить некоторое время на этой неделе, пытаясь исправить это дерьмо, чтобы ускорить его.
РЕДАКТИРОВАТЬ 3 : Просто изменив секцию RNG, используя секцию, основанную на RNG BSD (как предложено Sampo Smolander) и исключив постоянное деление на
m1
, я сократил время выполнения до того же, что и ответ C ++ Гая Сиртона . Использование статических массивов (как предложено Sharpie) понижает время выполнения до уровня времени выполнения C ++! Yay Фортран! : DРЕДАКТИРОВАТЬ 4 Очевидно, что это не компилируется (с gfortran) и работает правильно (неправильные значения), потому что целые числа выходят за свои пределы. Я внес исправления, чтобы убедиться, что он работает, но для этого требуется, чтобы у него был либо ifort 11+, либо gfortran 4.7+ (или другой компилятор, который позволяет
iso_fortran_env
иint64
тип F2008 ).Вот код:
Я полагаю, что теперь вопрос заключается в том, перестанете ли вы использовать Python с медленной скоростью, как меласса, и использовать Fortran с быстрым движением электронов;).
источник
integer(int64) :: b = 3141592653_int64
для всех int64. Это является частью стандарта Fortran и ожидается программистом на языке программирования с объявленным типом. (обратите внимание, что настройки по умолчанию, конечно, могут переопределить это)Python 2,7 -
0,882 с0,283 с(Оригинал ОП: 6.404с)
Редактировать: оптимизация Стивена Румбальски путем предварительного вычисления значений F. С этой оптимизацией cpython превосходит 0,365 Pypy.
Оригинальный код OP использует такие крошечные массивы, что использование Numpy бесполезно, как демонстрирует эта чистая реализация на python. Но посмотрите также на эту клочковатую реализацию, которая в три раза быстрее моего кода.
Я также оптимизирую, пропуская остальную часть свертки, если первый результат не равен нулю.
источник
F
рассчитать возможности, потому что их всего 4032. ОпределитеchoicesF = filter(any, itertools.product([-1, 0, 0, 1], repeat=n))
снаружи петли. Затем во внутреннем цикле определитеF = random.choice(choicesF)
. Я получаю ускорение в 3 раза при таком подходе.range(iters)
из цикла. В целом, я получаю ускорение примерно на 7% по сравнению с вашим очень хорошим ответом.Ржавчина: 0,011 с
Оригинальный Python: 8,3
Прямой перевод оригинального Python.
--opt-level=3
rustc 0.11-pre-nightly (eea4909 2014-04-24 23:41:15 -0700)
чтобы быть точным)источник
a
s иb
s в свертке; исправлено (заметно не меняет время выполнения).C ++ (VS 2012) -
0,026 с0,015 сPython 2.7.6 / Numpy 1.8.1 - 12 с
Ускорение ~ x800.
Разрыв был бы намного меньше, если бы свернутые массивы были очень большими ...
Несколько заметок:
S[0]
«наименее значимую» цифру.Добавьте эту основную функцию для отдельного примера:
источник
advance
функции, поэтому мой код теперь быстрее, чем ваш: P (но очень хорошая конкуренция!)С
Занимает 0,015 с на моей машине, а исходный код OP занимает ~ 7,7 с. Попытка оптимизации путем генерации случайного массива и свертки в одном и том же цикле, но, похоже, не имеет большого значения.
Первый массив генерируется путем взятия целого числа, записи его в двоичном виде и изменения всех 1 на -1 и всех 0 на 1. Остальные должны быть очень простыми.
Редактировать: вместо того, чтобы иметь
n
какint
, теперь у насn
есть макроопределенная константа, поэтому мы можем использоватьint arr[n];
вместоmalloc
.Edit2: вместо встроенной
rand()
функции теперь реализован PRNG ксоршифта. Кроме того, многие условные операторы удаляются при генерации случайного массива.Составьте инструкции:
Код:
источник
do{}while(!flag)
или что-то в этом роде. Я не ожидаю, что это сильно изменит время выполнения (может сделать его быстрее).continue;
утверждением я назначил-1
кk
, такk
будет цикл от 0 раз.-=
скорее, чем=-
:-) Цикл while был бы более читабельным.J
Я не рассчитываю выбивать какие-либо скомпилированные языки, и что-то подсказывает мне, что потребуется чудесная машина, чтобы получить менее 0,09 с, но я все равно хотел бы представить этот J, потому что он довольно гладкий.
Это займет около 0,5 с на ноутбуке предыдущего десятилетия, всего в 20 раз быстрее, чем Python в ответе. Большая часть времени уходит,
conv
потому что мы пишем это лениво (мы вычисляем всю свертку) и в полной общности.Поскольку мы знаем что-то о
S
иF
, мы можем ускорить процесс, сделав определенные оптимизации для этой программы. Лучшее, что я смог придумать, - этоconv =: ((num, num+1) { +//.)@:(*/)"1
выбрать конкретно два числа, которые соответствуют от диагональных сумм самым длинным элементам свертки, что примерно вдвое меньше времени.источник
Perl - в 9,3 раза быстрее ... улучшение на 830%
На моем древнем нетбуке запуск кода OP занимает 53 секунды; Версия Алистера Бакстона занимает около 6,5 секунд, а следующая версия Perl - около 5,7 секунд.
источник
Python 2.7 - numpy 1.8.1 с привязками mkl - 0.086 с
(Оригинал ОП: 6,404 с) (чистый питон Бакстона: 0,270 с)
Как указывает Бакстон, в исходном коде OP используются такие крошечные массивы, что использование Numpy бесполезно. Эта реализация использует numpy, выполняя все случаи F и S одновременно с ориентацией на массив. Это в сочетании с привязками mkl для python приводит к очень быстрой реализации.
Также обратите внимание, что простая загрузка библиотек и запуск интерпретатора занимает 0,076 секунды, поэтому фактические вычисления занимают ~ 0,01 секунды, что аналогично решению C ++.
источник
python -c "import numpy; numpy.show_config()"
покажет вам, скомпилирована ли ваша версия numpy с использованием blas / atlas / mkl и т. Д. ATLAS - это бесплатный ускоренный математический пакет, с которым можно связать numpy , за Intel MKL, за который вам обычно приходится платить (если вы не академик) и может быть связано с NumPy / Scipy .MATLAB 0.024s
Компьютер 1
Компьютер 2
Я решил дать о, так медленно, Matlab попробовать. Если вы знаете, как, вы можете избавиться от большинства циклов (в Matlab), что делает его довольно быстрым. Однако требования к памяти выше, чем для зацикленных решений, но это не будет проблемой, если у вас нет очень больших массивов ...
Вот что я делаю:
Я предполагаю, что у вас нет matlab, что очень плохо, так как мне бы очень хотелось посмотреть, как он сравнивается ...
(Функция может быть медленнее при первом запуске.)
источник
Юлия: 0,30 с
Op's Python: 21,36 с (Core2 Duo)
71-кратное ускорение
Я сделал несколько модификаций ответа Джулии от Армана: во-первых, я обернул его в функцию, так как глобальные переменные затрудняют вывод типа Джулии и JIT: глобальная переменная может изменить свой тип в любое время и должна проверяться при каждой операции , Затем я избавился от анонимных функций и массивов. Они на самом деле не нужны, и все еще довольно медленные. Джулия быстрее с низкоуровневыми абстракциями прямо сейчас.
Есть намного больше способов сделать это быстрее, но это делает достойную работу.
источник
Хорошо, я публикую это только потому, что я чувствую, что Java должна быть представлена здесь. Я ужасно разбираюсь с другими языками и признаюсь, что не совсем понял проблему, поэтому мне понадобится некоторая помощь, чтобы исправить этот код. Я украл большую часть примера кода туза C, а затем позаимствовал некоторые фрагменты у других. Я надеюсь, что это не подделка ...
Одна вещь, на которую я хотел бы обратить внимание, состоит в том, что языки, которые оптимизируются во время выполнения, должны запускаться несколько / много раз, чтобы достичь полной скорости. Я думаю, что вполне оправданно брать полностью оптимизированную скорость (или, по крайней мере, среднюю скорость), потому что большинство вещей, которые вам нужны для быстрой работы, будут выполняться несколько раз.
Код все еще нужно исправить, но я все равно запустил его, чтобы посмотреть, сколько раз я получу.
Вот результаты на процессоре Intel (R) Xeon (R) E3-1270 V2 @ 3,50 ГГц на Ubuntu, работающем 1000 раз:
сервер: / tmp # time java8 -cp. тестер
firstzero 40000
оба ноль 20000
Время первого запуска: 41 мс Время последнего запуска: 4 мс
реальный 0m5.014s пользователь 0m4.664s sys 0m0.268s
Вот мой дерьмовый код:
И я попытался запустить код Python после обновления Python и установки Python-Numpy, но я получаю это:
источник
currentTimeMillis
для бенчмаркинга (используйте nano-версию в System), и 1k запусков может быть недостаточно для вовлечения JIT (1.5k для клиента и 10k для сервера будут значениями по умолчанию, хотя вы вызываете myRand достаточно часто, чтобы это было JITed, который должен вызывать компиляцию некоторых функций из стека вызовов, что может сработать здесь). Последнее, но не в последнюю очередь, обманывает слабый PNRG, но также и решение C ++ и другие, так что я думаю, что это не слишком несправедливо.gettimeofday(&time, NULL)
миллисекунды, которые не являются монотонными и не дают каких-либо гарантий точности (поэтому на некоторых платформах / ядрах точно так же проблемы, связанные с реализацией currentTimeMillis для Windows - так что либо все в порядке, либо нет). nanoTime, с другой стороны, использует,clock_gettime(CLOCK_MONOTONIC, &tp)
что, безусловно, также правильно использовать при тестировании производительности в Linux.Golang версия 45X python на моей машине ниже кодов Golang:
и приведенные ниже коды Python, скопированные сверху:
и время ниже:
источник
"github.com/yanatan16/itertools"
? также вы бы сказали, что это будет хорошо работать в нескольких goroutines?C # 0.135s
C # на основе простого питона Алистера Бакстона : 0,278 с
Параллелизированный C #: 0,135 с
Python из вопроса: 5.907
с простого питона Алистера: 0,853 с
Я на самом деле не уверен, что эта реализация верна - ее вывод отличается, если вы посмотрите на результаты внизу.
Там, безусловно, более оптимальные алгоритмы. Я просто решил использовать алгоритм, очень похожий на алгоритм Python.
Однопоточный C
Параллельный C #:
Тестовый вывод:
Windows (.NET)
C # намного быстрее в Windows. Вероятно, потому что .NET быстрее, чем моно.
Время пользователя и системы не работает (используется
git bash
для синхронизации).Linux (моно)
источник
Haskell: ~ 2000x ускорение на ядро
Скомпилируйте с 'ghc -O3 -funbox-strict-fields -threaded -fllvm' и запустите с '+ RTS -Nk', где k - количество ядер на вашем компьютере.
источник
Рубин
Ruby (2.1.0) 0.277s
Ruby (2.1.1) 0.281s
Python (Алистер Бакстон) 0.330s
Python (alemi) 0.097s
источник
поток не будет полным без PHP
В 6,6 раза быстрее
PHP v5.5.9 -
1.2230.646 сек;против
Python v2.7.6 - 8.072 сек
convolve
Функция немного упростилась, чтобы быть быстрее$F
И$FS
проверки).Выходы:
Редактировать. Вторая версия скрипта работает только для
0.646 sec
:источник
Решение F #
Время выполнения составляет 0,030 с при компиляции в x86 на CLR Core i7 4 (8) при 3,4 ГГц
Я понятия не имею, если код правильный.
источник
Q, 0,296 сегмента
Q - ориентированный на коллекцию язык (kx.com)
Код переписан для использования идиоматического Q, но без других умных оптимизаций
Языки сценариев оптимизируют время программиста, а не время выполнения
Первая попытка кодирования = не победитель, а разумное время (примерно в 30 раз быстрее)
ПРИМЕЧАНИЯ.-
\S seed
\t sentence
время, затраченное на это предложениеисточник
Юлия:
12,1496,929 сНесмотря на их претензии на скорость , начальное время компиляции JIT сдерживает нас!
Обратите внимание, что следующий код Julia фактически является прямым переводом исходного кода Python (без оптимизации) как демонстрация того, что вы можете легко перенести свой опыт программирования на более быстрый язык;)
редактировать
Бег с
n = 8
занимает 32,935 с. Учитывая, что сложность этого алгоритмаO(2^n)
, то4 * (12.149 - C) = (32.935 - C)
, гдеC
является константой, представляющей время компиляции JIT. Решив,C
мы находим этоC = 5.2203
, предполагая, что фактическое время выполненияn = 6
составляет 6,929 с.источник
Rust, 6,6 мс, ускорение 1950x
Практически прямой перевод кода Алистера Бакстона на Rust. Я подумал об использовании нескольких ядер с rayon (бесстрашный параллелизм!), Но это не улучшило производительность, возможно, потому что она уже очень быстрая.
И Cargo.toml, так как я использую внешние зависимости:
Сравнение скорости:
6625608 нс - около 6,6 мс. Это означает ускорение в 1950 раз. Здесь возможно много оптимизаций, но я стремился к удобству чтения, а не производительности. Одной из возможных оптимизаций будет использование массивов вместо векторов для хранения вариантов, поскольку они всегда будут иметь
n
элементы. Также возможно использовать RNG, отличный от XorShift, поскольку Xorshift работает быстрее, чем CSPRNG HC-128 по умолчанию, но медленнее, чем наивный алгоритм PRNG.источник