Почему f-строки быстрее, чем str (), анализируют значения? - PullRequest
8 голосов
/ 13 июня 2019

Я играл с f-строками (см. PEP 498 ), и я решил проверить скорость синтаксического анализа (например, f "{1}") по сравнению собычный str-анализ (например, str (1)).Но к моему удивлению, когда я проверил скорость обоих методов с помощью функции timeit , я обнаружил, что

>>> from timeit import timeit
>>> timeit("f'{1}'")
0.1678762999999961

, тогда как

>>> timeit("str(1)")
0.3216999999999999

или дажеrepr func, который в большинстве случаев быстрее, чем str cast

>>> timeit("repr(1)")
0.2528296999999995

Интересно, почему это так?Я думал, что f-струны называются str внутренне, но сейчас я немного запутался, есть идеи?Заранее спасибо!

PD: Просто если кому-то интересно:

assert f"{1}" == str(1) == repr(1)

1 Ответ

12 голосов
/ 13 июня 2019

Простой ответ заключается в том, что f-строки являются частью грамматики и синтаксиса языка. С другой стороны, вызов str() требует поиска в таблице символов, за которой следуетвызов функции.

Вот аналогичный пример, который интерполирует целочисленную переменную, сопоставьте это с интерполяцией константного значения.

x = 1                                                                                                                                

%timeit f'{1}'                                                                                                                       
%timeit f'{x}'   
%timeit str(1)                                                                                                                                                                                                                                       
%timeit str(x)                                                                                                                       

113 ns ± 2.25 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
166 ns ± 4.71 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
342 ns ± 23.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
375 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Разница в поведении очевидна, когда вы смотрите надизассемблированный байт-код с dis.

import dis

dis.dis("f'{x}'")                                                                                                                    
  1           0 LOAD_NAME                0 (x)
              2 FORMAT_VALUE             0
              4 RETURN_VALUE

dis.dis("str(x)")                                                                                                                     
  1           0 LOAD_NAME                0 (str)
              2 LOAD_NAME                1 (x)
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

Тяжелый подъем - это все в инструкции CALL_FUNCTION, накладные расходы, которые, безусловно, не несут f-струныне имеет - по крайней мере, в этом случае, поскольку ничего не должно быть eval 'd.

...