При использовании bruteforce с многопоточностью он работает медленнее, чем один поток

1

Я хочу перечислить все комбинации строки base36 от 0000 до zzzz.
Когда я запускаю его с одним потоком, он работает быстрее (~ 6-5 секунд), чем при многопоточности (~ 13-14 секунд).
Я читаю здесь, почему использование большего количества потоков может быть медленнее, чем использование меньших потоков.
Но у меня 4 ядра (8 логических процессоров), и я не думаю, что это проблема в моем случае.

Я что-то не так с моим кодом?
Может быть, функция join() замедляет работу?

Вот мой код:

import time
import threading

# https://codegolf.stackexchange.com/questions/169432/increment-base-36-strings?page=1&tab=votes#tab-top
def inc_base36(s):
   L,R=s[:-1],s[-1:];
   return s and[[L+chr(ord(R)+1),inc_base36(L)+'0'][R>'y'],L+'a'][R=='9']

def bruteforce(start_id, end_id):
 while start_id != end_id:
   start_id = inc_base36(start_id)

# Single thread
# --- 5.15600013733 seconds ---
start_time = time.time()
bruteforce('0000', 'zzzz')
print("--- %s seconds ---" % (time.time() - start_time))

# Two threads
# --- 13.603000164 seconds ---
t1 = threading.Thread(target=bruteforce, args = ('0000', 'hzzz')) # in decimal (0, 839807)
t2 = threading.Thread(target=bruteforce, args = ('i000', 'zzzz')) # in decimal (839808, 1679615)

start_time = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
print("--- %s seconds ---" % (time.time() - start_time))

# Three threads
# --- 14.3159999847 seconds ---
t1 = threading.Thread(target=bruteforce, args = ('0000', 'bzzz')) # in decimal (0, 559871)
t2 = threading.Thread(target=bruteforce, args = ('c000', 'nzzz')) # in decimal (559872, 1119743)
t3 = threading.Thread(target=bruteforce, args = ('o000', 'zzzz')) # in decimal (1119744, 1679615)

start_time = time.time()
t1.start()
t2.start()
t3.start()
t1.join()
t2.join()
t3.join()
print("--- %s seconds ---" % (time.time() - start_time))
  • 0
    join не может ничего замедлить. Он просто ждет, пока поток не закончится. Почему у вас count=0 ? Кажется, что это бесполезно, и я не знаю, достаточно ли умен Python для сортировки доступа к этой переменной. Это может быть потенциальная временная дыра.
  • 0
    @Poshi это не связано с count=0 , я удалил и проверил снова и получил те же результаты.
Показать ещё 1 комментарий
Теги:
multithreading

2 ответа

1

Большинство реализаций Python имеет GIL (Global Intperpreter Lock), который позволяет выполнять один поток за раз. Вы должны использовать Jython, который не имеет GIL или реализует многопроцессорность в вашем скрипте.

  • 0
    Я читал о многопроцессорности: stackoverflow.com/a/3046201/2153777, но звучит так, что потоки более легки для того, что мне нужно, а процессы spwanning медленнее. В любом случае я попробую это
  • 0
    @ E235 Если вы используете multiprocessing.Pool пул, рабочие процессы используются повторно, а не перезапускаются для каждого расчета. И насколько «легковесным» является создание нового процесса, во многом зависит от операционной системы. Для систем, использующих fork() для создания новых процессов и управления памятью при копировании, создание нового процесса не так уж и дорого.
Показать ещё 1 комментарий
0

Хотя ответ Yossi верен, более быстрым решением может быть использование инструментов из стандартной библиотеки.

В этом случае itertools.product. Если я правильно истолковал ваш вопрос, вы можете сделать следующее:

In [1]: from itertools import product

In [2]: base36 = "0123456789abcdefghijklmnopqrstuvwxyz"

In [3]: res = [''.join(p) for p in product(base36, repeat=4)]

In [4]: res[0], res[-1]
Out[4]: ('0000', 'zzzz')

Посмотрим, как быстро это происходит:

In [5]: %timeit res = [''.join(p) for p in product(base36, repeat=4)]
800 ms ± 1.24 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Как вы можете видеть, это намного быстрее. Время было выполнено с использованием CPython 3.6 на старом CPU Core2. Модуль itertools в CPython написан на C, что, вероятно, является значительной частью причины, по которой оно быстрее.

И результат кажется полным:

In [6]: len(res)
Out[6]: 1679616

In [7]: 36**4
Out[7]: 1679616
  • 0
    Спасибо, но это хорошо до 4 писем. Если я хочу запустить его из 5 букв, я получаю MemoryError . Вот почему я проверил это на темы и привел «легкий» пример из 4 букв. Но когда мне понадобится запустить его из 5 букв, это будет 36 ^ 5 = ~ 60 миллионов комбинаций, а при запуске с потоками я могу дать каждой группе потоков строку и разделить память между потоками.
  • 0
    @ E235 Неважно, сколько частей вы разделите на 36⁵. Общая длина всех списков останется 36⁵.
Показать ещё 2 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню