Чому зациклення над діапазоном () у Python швидше, ніж використання циклу while?


81

Днями я робив тестування Python і натрапив на щось цікаве. Внизу є дві петлі, які роблять більш-менш одне і те ж. Цикл 1 виконується приблизно вдвічі довше, ніж цикл 2.

Петля 1:

int i = 0
while i < 100000000:
  i += 1

Петля 2:

for n in range(0,100000000):
  pass

Чому перший цикл набагато повільніший? Я знаю, що це тривіальний приклад, але це викликало мій інтерес. Чи є щось особливе у функції range (), що робить її ефективнішою, ніж збільшення змінної таким же чином?

Відповіді:


159

дивіться розбирання байтового коду python, ви можете отримати більш конкретну ідею

використовувати цикл while:

1           0 LOAD_CONST               0 (0)
            3 STORE_NAME               0 (i)

2           6 SETUP_LOOP              28 (to 37)
      >>    9 LOAD_NAME                0 (i)              # <-
           12 LOAD_CONST               1 (100000000)      # <-
           15 COMPARE_OP               0 (<)              # <-
           18 JUMP_IF_FALSE           14 (to 35)          # <-
           21 POP_TOP                                     # <-

3          22 LOAD_NAME                0 (i)              # <-
           25 LOAD_CONST               2 (1)              # <-
           28 INPLACE_ADD                                 # <-
           29 STORE_NAME               0 (i)              # <-
           32 JUMP_ABSOLUTE            9                  # <-
      >>   35 POP_TOP
           36 POP_BLOCK

Тіло петлі має 10 ор

діапазон використання:

1           0 SETUP_LOOP              23 (to 26)
            3 LOAD_NAME                0 (range)
            6 LOAD_CONST               0 (0)
            9 LOAD_CONST               1 (100000000)
           12 CALL_FUNCTION            2
           15 GET_ITER
      >>   16 FOR_ITER                 6 (to 25)        # <-
           19 STORE_NAME               1 (n)            # <-

2          22 JUMP_ABSOLUTE           16                # <-
      >>   25 POP_BLOCK
      >>   26 LOAD_CONST               2 (None)
           29 RETURN_VALUE

Тіло петлі має 3 ор

Час запуску коду С значно менший, ніж інтерпретатор, і його можна ігнорувати.


2
Насправді тіло петлі при першій розбірці має 10 операцій (стрибок з положення 32 на 9). У поточній реалізації CPython інтерпретація кожного байт-коду призводить до досить високої ймовірності дорогої непрямої гілки в CPU (перехід до реалізації наступного байт-коду). Це наслідок поточної реалізації CPython, JIT, реалізовані незавантаженою ластівкою, PyPy та іншими, швидше за все, втратять накладні витрати. Найкращі з них також зможуть пройти спеціалізацію типу на порядок прискорення.
Ants Aasma

5
використовувати модуль "dis". Визначте свій код у функції, а потім зателефонуйте dis.disco (func .__ code__)
kcwu

Точніше було б сказати, що на вищому рівні whileцикл повинен робити порівняння на кожній ітерації?
davidhood2,

35

range()реалізується на мові C, тоді i += 1як інтерпретується.

Використання xrange()може зробити це ще швидшим для великих номерів. Починаючи з Python 3.0 range()- це те саме, що і раніше xrange().


15

Слід сказати, що з циклом while відбувається багато створення та знищення об'єктів.

i += 1

те саме, що:

i = i + 1

Але оскільки вставки Python незмінні, це не змінює існуючий об'єкт; швидше він створює абсолютно новий об'єкт з новою вартістю. Це в основному:

i = new int(i + 1)   # Using C++ or Java-ish syntax

Сміттєзбірнику також доведеться провести велику кількість прибирання. Msgstr "Створення об'єкта дороге".


4

Тому що ви частіше працюєте в коді, написаному на інтерпретаторі. тобто i + = 1 знаходиться в Python, настільки повільно (порівняно), тоді як діапазон (0, ...) - це один виклик C, який цикл for буде виконуватися в основному також на C.


1

Більшість вбудованих викликів методів Python виконуються як код C. Код, який потрібно інтерпретувати, набагато повільніший. Що стосується ефективності пам'яті та швидкості виконання, різниця гігантська. Внутрішні елементи python оптимізовані до крайності, і найкраще скористатися цими оптимізаціями.


0

Я думаю, що відповідь тут трохи тонша, ніж пропонують інші відповіді, хоча суть її правильна: цикл for швидший, оскільки більша кількість операцій відбувається на C, а менше на Python .

Більш конкретно, у випадку циклу for, в C трапляються дві речі, які в циклі while обробляються в Python:

  1. У циклі while порівняння i < 100000000виконується на Python, тоді як у циклі for завдання передається ітератору range(100000000), який внутрішньо виконує ітерацію (і, отже, перевірку меж) у C.

  2. У циклі while оновлення циклу i += 1відбувається в Python, тоді як у циклі for знову ітератор range(100000000), написаний на мові C, робить i+=1(або ++i).

Ми бачимо, що саме комбінація обох цих речей пришвидшує цикл for, додаючи їх назад, щоб побачити різницю.

import timeit

N = 100000000


def while_loop():
    i = 0
    while i < N:
        i += 1


def for_loop_pure():
    for i in range(N):
        pass


def for_loop_with_increment():
    for i in range(N):
        i += 1


def for_loop_with_test():
    for i in range(N):
        if i < N: pass


def for_loop_with_increment_and_test():
    for i in range(N):
        if i < N: pass
        i += 1


def main():
    print('while loop\t\t', timeit.timeit(while_loop, number=1))
    print('for pure\t\t', timeit.timeit(for_loop_pure, number=1))
    print('for inc\t\t\t', timeit.timeit(for_loop_with_increment, number=1))
    print('for test\t\t', timeit.timeit(for_loop_with_test, number=1))
    print('for inc+test\t', timeit.timeit(for_loop_with_increment_and_test, number=1))


if __name__ == '__main__':
    main()

Я спробував це як із числовою константою 100000000, так і як змінною N, що було б більш типовим.

# inline constant N
while loop      3.5131139
for pure        1.3211338000000001
for inc         3.5477727000000003
for test        2.5209639
for inc+test    4.697028999999999

# variable N
while loop      4.1298240999999996
for pure        1.3526357999999998
for inc         3.6060175
for test        3.1093069
for inc+test    5.4753364

Як бачите, в обох випадках whileчас дуже близький до різниці for inc+testі for pure. Зауважте також, що у випадку, коли ми використовуємо Nзмінну, параметр whileдодаткового уповільнення багаторазово шукає значення N, але forні.

Це справді божевільно, що такі тривіальні модифікації можуть призвести до 3-кратного прискорення коду , але це Python для вас. І навіть не заводи мене, коли ти взагалі можеш використовувати вбудований цикл ....

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.