Комментарии 33
Если очень нужен питон и по другому ни как, то конечно можно.. но вообще дичь, просто неиспользуйте питон там где не надо.
А то как в известной мысли про молоток и гвозди.
Вы точно читали статью?
Все вычисления далаются не на Питоне. В общем Питон вообще можно убрать из заголовка, это будет справедливо для всех языков.
С чего бы для всех?
С go пропорции рутин и тредов вообще мало кого волнуют, а тех кого волнует не редко пишут на c++/rust.
Проблем с определением количества ресурсов нет, с cgroups тоже, пример: go, jvm, c# и куча прочих.
А если вы про сишную либу под капотом.. ну давайте поговрим о трединге/асинке в php? Там тоже ведь сишные либы подкапотом...
С go пропорции рутин и тредов вообще мало кого волнуют
В Golang по тем же критериям можно выбирать значение для GOMAXPROCS.
Размер ThreadPool в джаве и С++ тоже нужно как-то устанавливать. Думаете там всё по другому? И они умеют найти нужное количество процессоров изнутри докера?
А если вы про сишную либу под капотом
Если эти либы использовать эффективно, то есть один раз положить туда дынные и один раз достать то скорость будет близка к скорости самой либы. Вне зависимости от языка который это всё оркестрирует. Так всё машинное обучение на Питоне работает.
В любой задаче, где вам нужно будет перемалывать числа, вам придётся подбирать количество процессоров под ваш кейс. Чем более эффективен язык, тем с большей погрешностью это можно делать.
GOMAXPROCS контролирует количество операционных системных потоков (OS threads), которые могут исполнять код Go одновременно. Играть с этим параметром можно и нужно и вроде как "по феншую" надо ставить num CPU == GOMAXPROCS, но можно ставить больше что приведет к тому что утилизация на ядро станет выше.
Я что хочу сказать, сама фраза "по тем же критериям можно выбирать значение для GOMAXPROCS" навеивает какую-то сложную задачу, которая совсем и не задача. Никакой там сложной магии нет, повышаешь – тестишь. А там уже крутишься от того что тебе надо: Утилизация или Скорость. И по личному опыту (на минуточку у меня приложение в жало утилизирует 40 ядер) второе начинает падать только тогда, когда первое перескакивает значение 80-85%...
Т.е если программа 10% утилизировала и производила 1000 вычислений / за момент времени, то на 80% это будет 8000. А вот дальнейшее повышение начинает немного замедлять прибавку к скорости.
В любом случае подбор GOMAXPROCS было потрачено примерно 5 минут времени, методом увеличения для достижения оптимальных значений.
//Если эти либы использовать эффективно, то есть один раз положить туда дынные и один раз достать то скорость будет близка к скорости самой либы .
Нет, не будет. Давайте сначала определим что такое "близкая скорость". Вот в моих реалиях это наносекунды и лишняя 1000 – провал. Вы про какие близкие значения говорите? Давайте я вам просто ссылку дам и вы посмотрите:
https://benchmarksgame-team.pages.debian.net/benchmarksgame/fastest/gpp-python3.html
Смотрим на графу cpu secs и больше вопросов я думаю не будет. На самом деле замечательный ресурс с тестами. Наглядно показывает какие языки и в чем проигрывают.
В любом случае подбор GOMAXPROCS было потрачено примерно 5 минут
времени, методом увеличения для достижения оптимальных значений.
Так в общем статья как раз об этом подборе :)
Просто с лирическими отступлениями про то что не все CPU одинаковые. Ну и примеры на Питоне.
Нет, не будет. Давайте сначала определим что такое "близкая скорость".
Вот в моих реалиях это наносекунды и лишняя 1000 – провал.
Я уточню, что мой ответ для контекста вычисления на CPU на больших данных.
Из серии возьми эти файлы, обучи на них нейросетку с параметрами, положи результат в этот файл. Да там старт интерпретатора и операции займут какое-то константное время, а реальную работу будут делать библиотеки на другом языке. Переписав вызов библиотек на раст, вы выиграете эту секунду и не более.
не знаю, не встречал такого чтоб к примеру на TensorFlow данные лились через Python без обработки. Нафига он (Питон) тогда там нужен? Да и вообще нафига он нужен тогда при таких вводных как язык?
Я могу ошибаться, но в этом примере(https://www.tensorflow.org/tutorials/quickstart/beginner), данные не ковенрируется в питоновские объекты, а сразу загружаются в numpy объекты (обертака над нативным С массивом).
https://github.com/keras-team/keras/blob/v2.14.0/keras/datasets/mnist.py#L25-L86
> Нафига он (Питон) тогда там нужен?
Рулить процессом. И исследовать: посмотреть на срезы, собрать статистику, проверить гипотизы.
Захотелось мне однажды найти все счастливые ip адреса. Написал на питоне программу которая перебирала 4млрд адресов и считала суммы чисел внутри адреса.
Запустил на одном ядре, понял что не дождусь.
Запустил на всех ядрах, понял что не дождусь.
Попросил чатгпт переписать на си. Сишная версия отработала на одном ядре за 5 секунд.
А что такое «счастливые ip-адреса»? ) сумма первых шести и последних шести совпадает?
Вы меня сподвигли на эксперименты.
Наивнейшая реализация
octet = list(range(1,255))
counter = 0
for i1 in octet:
print (f"{i1}", end='\r')
for i2 in octet:
for i3 in octet:
for i4 in octet:
if i1+i2 == i3+i4:
counter += 1
print (counter)
Заняла 4 минуты 13 секунд. Попробовал использовать модуль array - время совсем не изменилось. Удивился, думал хоть немного спасёт.
Переписываем, чуть-чуть включив мозги.
Вариант с мозгами
from collections import defaultdict
from timeit import default_timer as timer
start_t = timer()
octet = list(range(1,255))
variantos = defaultdict(int)
for i1 in octet:
print (f"{i1}", end='\r')
for i2 in octet:
summ = i1+ i2
variantos[summ] += 1
counter = 0
for k, v in variantos.items():
counter += v*v
print (counter)
end_t = timer()
print(end_t - start_t)
Причём первый цикл наверное не нужен - я просто не уверен, что список возможных сумм непрерывный, а проверять влом.
Получилось 0.012 секунд. Ой.
Результат, кстати, 10924794
Как насчет такой тупой реализации, обернутой в нумбу?
Тупой вариант с нумбой
import numba as nb
import numpy as np
from timeit import default_timer as timer
start_t = timer()
@nb.njit
def fast_counter():
octet = np.arange(1, 255, dtype=np.uint8)
counter = 0
for i1 in octet:
#print (f"{i1}", end='\r')
for i2 in octet:
for i3 in octet:
for i4 in octet:
if i1+i2 == i3+i4:
counter += 1
return counter
fast_counter()
end_t = timer()
print(end_t - start_t)
0.7611 секунд. И ещё ждать, пока нумба установится...
0.6482 секунд, если заменить dtype=np.uint8
на dtype=np.int32
0.5804 секунд, если не использовать numpy и вернуть list как было.
Вы считаете сумму октетов, а не сумму цифр в октетах, это заметно быстрее.
del
Не так уж и заметно, оказывается. Если рассчитать сумму цифр возможных значений октета заранее, то выходит 4:28 на моей машине.
Hidden text
sum_of_digits = [sum(int(x) for x in str(number)) for number in range(256)]
counter = 0
octet = tuple(range(0, 256))
for i1 in range(1, 256):
print (f"{i1}", end='\n')
for i2 in octet:
for i3 in octet:
for i4 in octet:
if sum_of_digits[i1] + sum_of_digits[i2] == sum_of_digits[i3] + sum_of_digits[i4]:
counter += 1
print(counter)
Если переписать на C, то получается (на более слабой машине)
real 0m9.139s
user 0m9.133s
sys 0m0.005s
Ну если еще чуть подумать, то variations
можно еще быстрее собрать (О(n) вместо О(n^2)).
Хотя если еще подумать, то и собирать variations
не зачем там вообще один цикл на самом деле нужен:
l = 256
t = 0
for s in range(1, l):
t += s*s * 2
t += l*l
print(t)
Да, и ответ у вас неверный. Вы почему-то считаете, что октет может иметь значения от 1 до 254 (включительно) хотя он от 0 до 255. И хотя некоторые счастливые IP v4 и зарезервированы как служебные и никто их выдать не может, но абстрагируюясь от этого ответ будет 11184896. Зарезервированные счастливые можно при желании вычесть.
Варианты: ваш "умный", мой с O(n) при построении variations и тот что выше:
11184896
0.010685138004191685
11184896
0.00014450500020757318
11184896
4.1819999751169235e-05
Наивнейшую реализацию можно оптимизировать спрятав итерацию в generator_expression.
Для вложенных циклов уже есть сахар https://docs.python.org/3/library/itertools.html#itertools.product
# v1
counter = sum(
1 for i1 in octet
for i2 in octet
for i3 in octet
for i4 in octet
if i1+i2 == i3+i4
)
# v2
counter = sum(
1 for i in product(octet, octet, octet, octet) if i[0] + i[1] == i[2] + i[3]
)
Вам интересно, почему быстрая функция намного быстрее? Возможно, стоит прочитать книгу об оптимизации низкоуровневого кода, над которой я работаю.
Два раза заполнить массив и один раз заполнить массив. Что здесь низкоуровневого?
Может сначала надо научиться пользоваться оптимизациями numpy?
Зачем вам умный контейнер для быстрых вычислений над элементами, если вы делаете всё снаружи?
Если вам надо сделать один массив из другого, да еще и одинаковой размерности, то надо все делать внутри.
from timeit import timeit
import numpy as np
def f_bad(img, noise_threshold=5):
result = np.empty(arr.shape, arr.dtype)
for i in range(result.shape[0]):
result[i] = 0 if img[i] < noise_threshold else img[i]
return result
def f_good(img, noise_threshold=5):
return np.where(img < noise_threshold, 0, arr)
if __name__ == "__main__":
arr = np.array(list(range(100)))
print(timeit("f_bad(arr)", globals=globals())) # 15.4994
print(timeit("f_good(arr)", globals=globals())) # 1.2924
При выполнении параллельной программы, активно задействующей CPU, нам часто необходимо, чтобы пул потоков или процессов имел размер, сопоставимый с количеством ядер CPU на машине
Распараллеливание CPU-bound задач сводится к их непосредственному запуску процессов на разных ядрах CPU. Потоки, в данном случае, не подходят в принципе, т.к. процесс исполняется на одном ядре, и сколько потоков не выделяй -- они все будут исполняться в рамках одного.
Конкретно Python располагает не малым арсеналом встроенных механизмов распараллеливания задач на разных ядрах. Так, например, могу порекомендовать посмотреть в сторону ProcessPoolExecutor, т.к. он предоставляет весьма удобный интерфейс для управления пулом процессов на высоком уровне
Количество ядер, которое можно эффективно использовать, зависит от того кода, который напишете вы!
Интересно, а как интерпретатор должен догадаться за вас, что, когда и как ему нужно распараллеливать. Это уже какое-то чтение мыслей получается
Прибавив ещё и то, что автор берет numpy
и не знает, что это обёртка над C-библиотекой, которая спрашивает о выделении процессов у ОС, а не у CPython, и выходит, что статья написана в стиле "Я тут зашел в ваш Python и чет он ничего не может"
P.s.: считаю, что на хабре кроме редактуры на грамматику необходимо также ввести цензор на ошибочные или глупые статьи, а то пока мы имеем вот такие статьи, где автор искренне негодует по поводу того, что интерпретатор не может сам догадаться и исполнить его код так, как он хочет, а потоки почему-то не исполняются в рамках разных ядер
Потоки, в данном случае, не подходят в принципе, т.к. процесс исполняется на одном ядре, и сколько потоков не выделяй -- они все будут исполняться в рамках одного.
Эммм... Нет. В питоне многопоточность убита GIL'ом, но во-первых в данном примере его выключили, а во-вторых, в других языках GIL'а может и не быть вовсе. При этом использование потоков вместо процессов позволяет создавать более быстрые программы, за счёт отсутствия необходимости пересылать данные между адресными пространствами.
Замеры были на 12700k /thread
ну и вообще, переводите ещё и комменты к оригинальной статье, там автору уже напихали в панамку за бенчмарк непонятно чего непонятно как
А вот это вот отключения ГИЛ, насколько это безопасно? Насколько оправдано в реальных проектах? Не приведёт ли к разрушению стеков в случайный момент?
Вам интересно, почему быстрая функция намного быстрее? Возможно, стоит прочитать книгу об оптимизации низкоуровневого кода, над которой я работаю.
Оптимизация низкоуровнего кода на Python - звучит как оксюморон.
>>И наоборот, более быстрая функция может использовать преимущества не более чем девяти ядер; при увеличении их количества начинается замедление. Возможно, она упирается в какое-то другое узкое место, не в вычислительные ресурсы, например, в пропускную способность памяти.
Всё очень просто: полезли в регистры fpu - Hyper-threading полетел, и вот у вас не 2 логических ядра а 1
Сколько ядер CPU можно использовать параллельно в Python?