Это продолжение того, насколько медленно работает Python? (Или как быстро ваш язык?) .
Оказывается, было слишком легко получить ускорение x100 для моего последнего вопроса. Для тех, кто испытал трудности, но хочет чего-то сложнее, когда они действительно могут использовать свои навыки низкого уровня, вот часть II. Задача состоит в том, чтобы получить ускорение x100 для следующего кода Python, протестированного на моем компьютере.
Чтобы сделать это более сложным, я использую Pypy на этот раз. Текущее время для меня составляет 1 минуту и 7 секунд, используя pypy 2.2.1.
правила
- Первый, кто предоставит код, который я смогу выполнить, правильный и в 100 раз быстрее на моей машине, получит награду в 50 баллов.
- Я награжу победу самым быстрым кодом через неделю.
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]
Вы должны использовать случайное начальное число в своем коде, и любой генератор случайных чисел, который достаточно хорош, чтобы дать ответы, близкие к приведенным выше, будет принят.
Моя машина Время будет запущено на моей машине. Это стандартная установка Ubuntu на восьмиъядерный процессор AMD FX-8350. Это также означает, что мне нужно иметь возможность запускать ваш код.
Объяснение кода
Этот код выполняет итерацию по всем массивам S длиной n + m-1, которые составлены на -1 с и 1 с. Для каждого массива S он выбирает 1000 ненулевых случайных массивов F длины n, составленных из -1,0 или 1 с вероятностью 1/4, 1/2, / 14 принятия каждого значения. Затем он вычисляет внутренние произведения между F и каждым окном S длины n, пока не найдет ненулевое внутреннее произведение. Он добавляет 1 к leadingzerocounts
каждому найденному положению с нулевым внутренним произведением.
Статус
Perl . Замедление в 2,7 раза от @tobyink. (По сравнению с pypy, а не cpython.)
Дж . Ускорение в 39 раз @Eelvex.
- C . Ускорение в 59 раз
- Джулия . В 197 раз быстрее, не включая время запуска на одну минуту больше. Ускорение в 8,5 раз, включая время запуска (в данном случае быстрее при использовании 4 процессоров, чем 8).
- Фортран . Ускорение в 438 раз при @ полуэкстремальном.
- Rpython . Ускорение в 258 раз благодаря @primo.
- С ++ . @Ilmale ускорил в 508 раз.
(Я перестал рассчитывать новые улучшения, потому что они слишком быстрые, а iters были слишком малы.)
Было отмечено, что время ниже секунды ненадежно, а также некоторые языки имеют начальную стоимость. Аргументом является то, что если вы хотите включить это, вы должны также включить время компиляции C / C ++ и т. Д. Вот время для самого быстрого кода с числом итераций, увеличенным до 100 000.
- Джулия . 42 секунды на @ еще одну минуту.
- С ++ . 14 секунд @GuySirton.
- Фортран . 14s от @ полу-внешности.
- С ++ . 12s @ilmale.
- Rpython . 18s @primo.
- С ++ . 5s @Stefan.
Победитель .. Стефан!
Последующий вызов опубликован. Как высоко вы можете пойти? (Задача кодирования + алгоритмы) . Этот сложнее.
источник
Ответы:
С ++ немного магии
~ 16мс многопоточный, 56мс однопоточный. ~ 4000 ускорений.
(Ускорение основано на многопоточном коде на моем i7-2820QM и 1 мин 9 секунд, упомянутых в вопросе. Поскольку у системы OP хуже однопоточная производительность, чем у моего процессора, но лучше многопоточная производительность, я ожидаю, что это число будет точным)
Многопоточная часть довольно неэффективна из-за нереста нитей. Я мог бы, вероятно, добиться большего успеха, используя свою пользовательскую библиотеку заданий, но в ней есть ошибки в системах Unix. Для объяснения и почти идентичного кода без многопоточности см. Https://codegolf.stackexchange.com/a/26485/20965 .
редактировать
Я назначил каждому потоку собственный RNG и сократил длину в битах до 32, что сократило время выполнения на несколько мс.
Пример вывода:
источник
C ++
x150x450x530Вместо массива я использовал биты (и темную магию).
Спасибо @ace за более быструю случайную функцию.
Как это работает: первые 15 бит целого числа
s
представляют массивS[15]
; нули представляют -1, единицы представляют +1. МассивF
строится аналогичным образом. Но с двумя битами для каждого символа.Потому что
S
и уF
меня другое представление, которое я должен чередоватьS
с самим собой, чтобы сопоставить егоF
.F
)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 для меня. :)
Суммируйте, что это просто игра смены и маски, ничего действительно сложного.
Вот пример вывода:
Программа была составлена с:
на Fedora 20 с gcc 4.8.2 Процессор является i7 8core.
Вероятно, я могу получить некоторые параметры настройки компилятора мс.
Пока это время решения OP на моей машине:
Редактировать:
Просто добавив openmp и изменив порядок for, я получу выигрыш в x3, что приведет к улучшению производительности x450 по сравнению с OP-кодом. : D В этом случае
leadingZero
массив должен быть атомарным. Случайные глобальные ... случайны, они будут более случайными.нужно добавить
-fopenmp
в флаг компилятораРедактировать: 2 В качестве подсказки user71404 я изменил функции sumOnes и sumArray, и теперь это очень быстро.
С openmp медленнее, потому что атомники добавляют слишком много накладных расходов.
Без атомики еще быстрее, но я получаю неправильный результат.
2137992 1147218 619297 321243 155815 70946 32919 15579
Чтобы понять sumArray, рассмотрим 16-битное представление и массив из 8 чисел.
00 не имеют 1 и представляют -1
01, а 10 имеют 1 и представляют 0
11, имеют 2 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 ++
источник
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Юлия: 0,7 с, в 120 раз быстрее
Как показал пользователь 20768, простой порт кода для Julia примерно в два раза быстрее PyPy. Но мы можем сделать намного лучше, чем это.
Вы можете запустить это с помощью
julia -p 8 -e 'require("golf.jl");main()'
(8 - это количество процессов, вы можете поиграть с ним). В последнем пререлизе Julia это занимает 0,7 с против 1 м 22 для PyPy.Если у вас достаточно ядер на вашем компьютере и, возможно, раскрутите несколько экземпляров AWS, вы сможете побриться еще немного :)
источник
С, 1,210 с
С кодом OP, работающим на моей машине 1m45.729s.
Компиляция:
Отдельное спасибо: @dyp за флаги компиляции и идеи для оптимизации.
Пример вывода:
источник
-march=native -fwhole-program -fstrict-aliasing -ftree-vectorize
кстати. Я опустился до <4 с использованием C ++ 11, включая MT19937 плюс auniform_int_distribution
.F
.n
он равен8
, вы, вероятно, можете использовать AVX (или 2 * SSE) для вычисления точечного продукта с надлежащимS
хранилищем.smmintrin.h
)Perl
Это далеко не так быстро, как решение C, но я думаю, что это довольно быстро для интерпретируемого языка высокого уровня. Это позволяет сэкономить около 40% времени работы реализации Python.
Алгоритм :: Комбинаторика доступна в Ubuntu (
sudo apt-get install libalgorithm-combinatorics-perl
). Другие используемые модули являются базовыми модулями Perl, поэтому их уже следует устанавливать как часть базовой установки Ubuntu.источник
0..N-1
диапазон в последнемmap
, верно? Вы забылиuse warnings
? :-) Хотя логика в OP сбивает с толку, скользящее окно никогда не доходит до последнего элементаS
.warnings
чтобы пропущенные элементы рассматривались как ноль.N-1
улучшает это. И на самом деле это немного улучшает скорость - теперь она примерно на 40% быстрее, чем реализация Python.any
Альтернативно можно найти в List :: MoreUtils, который, хотя и не является основным модулем, является одним из наиболее часто используемых модулей CPAN.Юлия: в 4,66 раза медленнее!
Я действительно начинаю сомневаться в статистике на их сайте ...
Обратите внимание, что следующий код Julia фактически является прямой транскрипцией кода Python OP без каких-либо оптимизаций. Я использую
time()
функцию, чтобы исключить медленное время запуска Юлии ...Юлия: 5 м 32,912 с
Код ОП в PyPy: 1 м 11.506 с
Юлия выходной:
источник
RPython 0.187s (в 258 раз быстрее)
Оригинальный источник с PyPy2.2.1: 1 м 6,718 с
Теперь с многопоточностью обратная поддержка для стандартного Python прекращена. Количество рабочих потоков может быть указано в качестве параметра командной строки, по умолчанию - два.
RPython - это ограниченное подмножество Python, которое можно преобразовать в C и затем скомпилировать с помощью RPython Toolchain . Его выраженная цель - помочь в создании языковых переводчиков, но его также можно использовать для компиляции простых программ, подобных приведенной выше. Большинство «причудливых» функций Python, таких как
itertools
или дажеmap
недоступны.Для компиляции создайте локальный клон текущего pypy-репозитория и выполните следующее:
Полученный исполняемый файл будет назван
convolution-c
или похож в текущем рабочем каталоге.Я параметризовал входные переменные, поэтому программа должна запускаться как:
чтобы соответствовать примеру кода.
Замечания по реализации
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 рабочих потока:
4 рабочих потока:
8 рабочих потоков:
Первоисточник ОП:
Сроки для 100000 итераций:
источник
Джулия: 1 мин 21,4 с (в 2,2 раза быстрее) (модификация кода Армана)
Код оп в PyPy: 3 мин. 1,4 с
Оба делаются в REPL, не считая времени на загрузку пакетов.
Есть некоторые проблемы с кодом Армана, делающим его очень медленным: он использует множество анонимных функций и функций более высокого порядка без необходимости. Чтобы проверить, является ли весь вектор F нулевым, почему бы просто не написать все (F == 0) вместо всех (x-> x == 0, F)? Это короче, и буквально в тысячу раз быстрее.
Он также использует сумму (map (*, x, y)) в качестве точечного произведения вместо простой точки (x, y). Первая версия в 650 раз медленнее для вектора 10 Кб удваивается. И функция точечного произведения реализована как цикл for в чистой Джулии.
Кроме того, понимание массива медленное. Лучше написать [0,1,0, -1] [rand (1: 4, n)] вместо [[-1 0 0 1] [rand (1: 4)] для j = 1: n] ,
И, наконец, глобальные переменные - это плохо для Джулии. Джулия работает быстро, только если вы пишете код таким образом, чтобы JIT и вывод типов работали. Большая часть этого - стабильность типов: компилятор должен быть уверен, что тип переменной не изменится, например, внутри цикла.
источник
Нимрод
Пример вывода:
Nimrod компилируется в C, поэтому выбор C-компилятора для внутреннего интерфейса также имеет значение.
Используя clang, скомпилируйте с:
Используя gcc, скомпилируйте с:
Опустите,
--passc:-flto
если у вас есть старый компилятор C, который не поддерживает LTO. Опустите--cc=...
опцию, если вы в порядке с выбором по умолчанию для компилятора C. Код требует Nimrod 0.9.4 или 0.9.5 .На моем четырехъядерном iMac (Core i5 с частотой 2,66 ГГц) код выполняется примерно за 0,15 секунды с gcc 4,9, с 0,16 секундами при Clang, по сравнению с 88 секундами для PyPy 2.2.1 (т.е. ускорение в 500 раз больше). К сожалению, у меня нет доступа к машине с более чем четырьмя ядрами, на которой также установлен PyPy или где я мог бы легко установить PyPy, хотя я получаю около 0,1 секунды (с большим количеством измерительного шума) на 64-ядерном AMD Opteron 6376 1,4 ГГц (согласно / proc / cpuinfo) с gcc 4.4.6.
Реализация старается быть верной оригиналу, а не оптимизировать код за счет читабельности, не отказываясь при этом от очевидных оптимизаций. Интересно, что хвостовая рекурсия
initVecRand()
немного быстрее, чем цикл с инструкцией break с gcc и clang. Развертывание вручную одной итерации циклаconvolve
тестирования внутри основного цикла также приводило к ускорению, вероятно, из-за лучшего предсказания ветвлений.источник
Ява
Я перевел вышеуказанное решение C ++ на Java:
На моей машине я получаю следующий вывод для программы Java:
Программа OP работает на моем компьютере около 53 секунд:
Программа на С ++ выполняется всего около 0,15 секунды:
Это примерно в 2,5 раза быстрее, чем соответствующее решение Java (я не исключал запуск виртуальной машины). Это Java-решение примерно в 142 раза быстрее, чем программа, выполняемая с PyPy.
Поскольку я лично был заинтересован, я установил
iters
100_000 для Java и C ++, но коэффициент 2,5 не уменьшился в пользу Java, если что-нибудь станет больше.РЕДАКТИРОВАТЬ: Я запустил программы на 64-битном компьютере Arch Linux.
РЕДАКТИРОВАТЬ 2: Я хочу добавить, что я начал с грубого перевода кода Python:
Эта программа работала около 3,6 секунд:
Что примерно в 14 раз быстрее, чем решение PyPy. (Выбор стандартной случайной функции вместо функции fastRandom приводит к времени выполнения 5 секунд)
источник
Python 3.5 + numpy 1.10.1, 3.76 секунды
Тесты проводились на моем Macbook Pro. Код ОП занял ~ 6 минут на той же машине.
Причина, по которой я отвечаю на этот вопрос, на самом деле в том, что у меня нет 10 репутаций и я не могу ответить на часть I :-p
Последние несколько дней я пытался понять, как эффективно выполнять массивные свертки с помощью numpy (не полагаясь на сторонний пакет, даже на scipy). Когда я столкнулся с этой серией проблем во время исследования, я решил попробовать. Возможно, я пришел в эту игру поздно, но вот моя попытка использовать Python 3.5 и numpy 1.10.1.
Я предварительно вычислил массивы S и F и сгладил массив S при выполнении свертки, которая (основываясь на моих экспериментах) могла бы использовать скорость np.convolve. Другими словами, поскольку я не нашел подпрограммы векторизованной свертки, я фальшиво векторизовал код, сгладив весь массив, и надеялся, что np.convolved сделает векторизацию под капотом для меня, что, похоже, работает. Обратите внимание, что я использовал mode = 'same' и обрезал начальные и конечные элементы, которые были бесполезны.
На моем Macbook Pro результаты теста дают 3,76 секунды . Когда я запустил код OP (модифицированный до Python 3.5), я получил около 6 минут . Ускорение примерно в 100 раз.
Один недостаток состоит в том, что, поскольку массивы S и F должны храниться, требование к памяти может быть проблемой, если размеры слишком велики.
Я использовал тот же метод для первой части, и я получил ускорение ~ 60-100x на моем ноутбуке.
Поскольку я делал все на своем Macbook Pro, если бы кто-нибудь мог проверить мой код и сообщить мне, как он работает на вашем компьютере, я был бы очень признателен!
источник
J, ускорение
130x~50x?Раз на случайном дебиане:
Я думаю, что есть место для улучшения.
источник
pypy
, а неpython
потому, что ваш сценарий ускоряется в 130 раз.C ++: x200 (4-ядерный i7, должен масштабироваться до x400 на 8-ядерном)
В поисках более простого решения C ++ 11 (протестировано с VS 2012, gcc и clang) с распараллеливанием.
Чтобы это скомпилировать и запустить под Linux с помощью gcc 4.8.1:
Под Linux нам также нужно
std::launch::async
форсировать несколько потоков. Мне не хватало этого в более ранней версии.В Visual Studio (2012+) это должно просто работать, но делать сборку релиза для синхронизации ...
На моем старом двухъядерном i3 это выполняется за ~ 0,9 секунды. На моем четырехъядерном процессоре i7 это 0.319 с против pypy 66 секунд.
На 8-ядерном i7 это должно быть в диапазоне ускорения x400. Переключение на массивы в стиле C ускорило бы это, но мне было интересно остаться с контейнерами C ++. Для меня интересно увидеть ускорение, которое вы можете получить, оставаясь относительно близко к проблемной области и на относительно высоком уровне, что я думаю, C ++ действительно хорош. Также следует отметить относительную простоту распараллеливания с использованием конструкций C ++ 11.
Битовое решение @ ilmale очень круто и работает на -1/1/0. Можно также бросить SSE на это и, возможно, получить значительное ускорение.
Помимо распараллеливания, есть еще одна «хитрость», которая заключается в уменьшении количества сумм. Пример результатов: 6332947 2525357 1041957 438353 193024 87331 40902 19649
источник
Фортран: 316x
Ладно, Фортран: Я получил ускорение до
106x155x160x316x при использовании Xorshift RNG и OpenMP на 4-ядерном процессоре i7. Кроме этого, нет больших хитростей. Для итератора для построения S я просто использую двоичное представление 16-разрядного целого числа i. Вы заметите, что помимо встроенного RNG и «итератора» / отображения от i до S, код является таким же высокоуровневым, как и код Python.Редактировать: убрал «если» в Xorshift, теперь используя «r = abs (w / ...)» вместо «r = w / ...». Идет от 106х до 155х.
Edit2: генерирует в 15 раз больше случайных чисел, чем решение C ++. Если у кого-то есть решение с нулевыми издержками для преобразования случайного целого в массив из 0 и 1 в Фортране, я весь в ушах. Тогда мы могли бы победить C ++ :)
Edit3: первое редактирование представило ошибку, как указал Лембик. Это исправлено сейчас, с небольшим улучшением в ускорении. Я постараюсь использовать предложение Eelvex, чтобы ускорить процесс.
Edit4: Профилирование показало, что преобразование в вещественное и обратно в целое число с помощью nint () было медленным. Я заменил это одним целочисленным делением, выполняющим как масштабирование, так и округление, с ускорением 160x до 316x.
Компилировать с:
Пример вывода:
Код ОП:
источник