Является ли python невероятно неэффективным для циклов for, или это просто мой код? - PullRequest
0 голосов
/ 22 сентября 2019

Мой вопрос не совсем о моем коде, а о Python в целом.У нас была гонка кодирования в моей семье, и мои родители написали свою версию этого проекта на C # и javascript соответственно.Я думал, что python должен быть настолько же эффективным, если не больше, чем другие языки.В итоге мы написали один и тот же код (очевидно, с разным синтаксисом), но их файлы работали в течение миллисекунд (221 от моего папы на Javascript и 500 от моей мамы в C #), а мои - за 5 минут.Это нелепое различие, и оно заставляет меня задуматься о том, как вообще Python применяется в реальной обработке данных и алгоритмическом решении.


Я решил проблему с помощью трифлетов числа Пифагора.Проблема в том, что если квадрат + b в квадрате = c в квадрате, есть только одна комбинация a, b и c, которая добавляет тысячу.Закончите, напечатав числа, необходимые для 1000, по времени.

for c in range(1000, 0, -1):
        for a in range(1000, 0, -1):
            for b in range(1000, 0, -1):
                if (a*a)+(b*b)==(c*c):
                    if a+b+c == 1000:
                        print("I have found it")
                        print(a*b*c)
                        quit()

Ответы [ 4 ]

4 голосов
/ 22 сентября 2019

Python обычно менее эффективен по сравнению со статически типизированными языками, поскольку динамическая природа Python приводит к множеству откатов, каждый из которых требует некоторого времени обработки.

Но я думаю, что главная проблемав том, что ваш алгоритм не очень эффективен.Здесь три петли, каждая из которых насчитывает более 999 предметов.Таким образом, это означает, что внутренние циклы выполняются не более 997'002'999 раз.Мы можем переписать алгоритм так, чтобы он максимально занимал 499-500 итераций следующим образом:

for c in range(1000, 0, -1):
    for a in range(999<b>-c</b>, 0, -1):
        <b>b =</b> 1000-a-c
        if a*a + b*b == c*c:
            print("I have found it")
            print(a*b*c)
            quit()

Действительно, мы можем вычислить действительное значение для b, получив его из 1000-переменного тока.Кроме того, мы можем ограничить диапазон, начав итерацию с 999-c в диапазоне a.

Если мы пропустим print ing и quit() ing, мы получим следующий результат, когда мызапустите функцию 1000 раз:

>>> timeit(f, number=1000)
27.56889909002348

Таким образом, это будет выполняться за 27,6 миллисекунды.

2 голосов
/ 23 сентября 2019

Мы можем сделать Python для циклов намного быстрее.

Вопрос автора был о том, почему Python "для циклов" такой медленный по сравнению с C # и JavaScript.Это не решается путем разработки другого алгоритма, который уменьшает потребность в «циклах for» (особенно с учетом того, что версии C # и JavaScript также будут быстрее с измененным алгоритмом).В общем, языки программирования сравниваются с использованием аналогичных алгоритмов с их различием, допускаемым языковыми особенностями, которые позволяют им преуспеть в задаче - https://benchmarksgame - https://benchmarksgame -team.pages.debian.net/benchmarksgame/index.html

В этом случае задача состоит в том, чтобы использовать возможности языка Python для более быстрого поиска по миллиарду чисел (размер трех вложенных циклов)?

Для ускорения кода были испробованы два подхода: Cython и Numba (отдельно).

Настройка:

  • Jupyter Notebooks
  • Python 3.5.2 OS
  • Windows 10 64-разрядная
  • Процессор i7

Результаты:

  1. Python 3.5.2 => 387.356 секунд
  2. Cython => 117,223 секунды
  3. Cython (улучшено) => 0,63 секунды (с использованием предложения @CodeSurgeon)
  4. Numba => 0,5 секунды

версия Numbaсопоставимо с JavaScript и C # раз.Numba обеспечил 774 ускорения по Pure Python, используя тот же «неоптимальный» алгоритм.Обратите внимание, что все три реализации в основном используют один и тот же код, как показано ниже.

  1. Код Python 3.5.2

    import time

    def solve():
        for c in range(1000, 0, -1):
            for a in range(1000, 0, -1):
                for b in range(1000, 0, -1):
                    if (a*a)+(b*b)==(c*c):
                        if a+b+c == 1000:
                            return a*b*c


    to = time.time()
    print('I have found it {}'.format(solve()))
    print("Elapsed Time\n", time.time()- t0)

Python 3.5.2 Вывод

I have found it 31875000
Elapsed Time
 387.3550021648407
Cython Code (см. https://ipython -books.github.io / 55-ускоряющийся-python-код-с-cython / )

    %load_ext cython
    %%cython -a

    import time

    def solve():
        for c in range(1000, 0, -1):
            for a in range(1000, 0, -1):
                for b in range(1000, 0, -1):
                    if (a*a)+(b*b)==(c*c):
                        if a+b+c == 1000:
                            return a*b*c


    t0 = time.time()
    print('I have found it {}'.format(solve()))
    print("Elapsed Time\n", time.time()- t0)

Cython Output

I have found it 31875000
('Elapsed Time\n', 117.22299981117249)

Cython (с Cdef https://buildmedia.readthedocs.org/media/pdf/notes-on-cython/latest/notes-on-cython.pdf)

%% cython -a

время импорта

cdef int solve (): cdef int a, b,c

for c in range(1000, 0, -1):
    for a in range(1000, 0, -1):
        for b in range(1000, 0, -1):
            if (a*a)+(b*b)==(c*c):
                if a+b+c == 1000:
                    return a*b*c

t0 = time.time () print ('Я нашел его {}'. format (solve ())) print ("Истекшее время \ n", time.time () -t0)

Вывод (Cython с использованием Cdef)

I have found it 31875000
('Elapsed Time\n', 0.6340005397796631)
Код Numba (см. https://numba.pydata.org/)

    import time
    from numba import jit

    @jit(nopython=True)    # only need to add Numba decorator to get speed up
    def solve():
        for c in range(1000, 0, -1):
            for a in range(1000, 0, -1):
                for b in range(1000, 0, -1):
                    if (a*a)+(b*b)==(c*c):
                        if a+b+c == 1000:
                            return a*b*c


    t0 = time.time()
    print('I have found it {}'.format(solve()))
    print("Elapsed Time\n", time.time()- t0)

Выход Numba

I have found it 31875000
Elapsed Time
 0.5209996700286865
2 голосов
/ 23 сентября 2019

Вы можете еще больше ускорить процесс, используя отношение квадратов к сумме пифагорейских триплетов.В этой реализации https://www.geeksforgeeks.org/generate-pythagorean-triplets/

Я сам попробовал его с помощью следующего кода и обнаружил, что он выполняется за две десятых миллисекунды.

import math
import time

def pythagoreanTriplets(limits) : 
    c, m = 0, 2

    # Limiting c would limit  
    # all a, b and c 
    while c < limits : 

        # Now loop on n from 1 to m-1 
        for n in range(1, m) : 
            a = m * m - n * n 
            b = 2 * m * n 
            c = m * m + n * n 

            # if c is greater than 
            # limit then break it 
            if limits%(a+b+c) ==0: 
              #print(1000/(a+b+c)*a,1000/(a+b+c)*b,1000/(a+b+c)*c, "Found")
              return

            print(a, b, c) 

        m = m + 1

start = time.process_time() 
pythagoreanTriplets(1000)
elapsed = time.process_time() 
elapsed = elapsed - start
print("Time spent in (function name) is: ", elapsed)

Эффективный код, такой как Виллемпредполагает, является самым большим фактором здесь.enter image description here

Обновил приведенный выше код с пониманием Пита.

Я запускал код один раз с оператором печати, чтобы убедиться в его правильности, и один раз, чтобы не видеть скорость,и это показывает уменьшение примерно в 20 раз!

enter image description here

0 голосов
/ 24 сентября 2019

Кажется, что много циклов вычислений можно было бы извлечь из циклов, поэтому я решил предварительно вычислить квадраты.sqrs в качестве диктанта выполняет три задачи:

  1. Позволяет зацикливать x и x ** 2, просто перебирая sqrs.items().
  2. Позволяет искать, если a ** 2 + b ** 2 само по себе является квадратом.
  3. Это позволяет искать c, если предыдущая задача верна.

Вот код с таймингами:

def original():
    for c in range(1000, 0, -1):
        for a in range(1000, 0, -1):
            for b in range(1000, 0, -1):
                if (a*a)+(b*b)==(c*c):
                    if a+b+c == 1000:
                        print("I have found it")
                        print(a*b*c)
                        return

%time original()
I have found it
31875000
Wall time: 2min 27s

def pre_compute_sqrs():
    sqrs = {x**2:x for x in range(1, 1001)}
    for aa, a in sqrs.items():
        for bb, b in sqrs.items():
            ab, aabb = a + b, aa + bb
            if ab >= 1000 or aabb >= 1_000_000:
                break
            if aabb not in sqrs:
                continue
            c = sqrs[aabb]
            if a+b+c == 1000:
                print("I have found it")
                print(a*b*c)
                return

%time pre_compute_sqrs()
I have found it
31875000
Wall time: 46.9 ms

Является ли Python медленным?Это может быть.

Обычно это слишком медленно?Нет, не обычно.

...