5 Speed Improvements in Python 3.7 (Translated)

Trong bài viết này chúng ta sẽ đề cập đến những sự thay đổi chính về mặt hiệu năng trong Python 3.7 so với phiên bản Python 3.6.

    Python 3.7 đã release bản Beta! Giờ là lúc chúng ta kiểm tra xem có gì nhanh hơn hay không..

Cảnh báo: một số chủ đề trong bài viết này được trình bày rất chi tiết và ở level hoàn toàn cao so với những bài thông thường tôi hay viết. Nếu bạn không biết một vài thuật ngữ, hãy download và khám phá các ví dụ - tiếp tục đọc và chơi!

1. Calling methods faster (maybe)

Tiêu đề cho sự thay đổi này là "Speedup method calls 1.2x" - thực sự là có một chút đánh lừa..

Có rất nhiều cách để thay đổi CPython, hoặc là thay đổi việc xử lý mã thực thi (opcode - MTT), hoặc là thêm những MTT mới. Thêm những MTT mới đòi hỏi rất nhiều những cuộc bàn bạc, kiểm thử. Các MTT được lựa chọn bởi quá trình biên dịch trong CPython. Một khi code của bạn được convert thành cây cú pháp trừu tượng (Abstract Syntax Tree), trình biên dịch khảo sát mỗi nhánh và convert nó thành các MTT. Việc xử lý code của bạn đi qua các mã thực thi trong một câu lệnh switch lớn nằm trong một vòng loop và gọi các hàm C khác nhau với mỗi MTT.

Để tham chiếu, Python 3.6 có 3 mã thực thi để gọi hàm. Tất cả đều đã được thêm và chỉnh sửa trong Python 3.6.

  • Gọi hàm chỉ chứa các tham biến vị trí: CALL_FUNCTION

  • Gọi hàm chứa tham biến khóa và vị trí: CALL_FUNCTION_KW

  • Gọi hàm chứa tham biến khóa và vị trí thay đổi: CALL_FUNCTION_EX

Python 3.7 thêm hai MTT mới - LOAD_METHODCALL_METHOD - để khi trình biên dịch thấy x.method(...), nó sẽ sử dụng những MTT mới này.

Ví dụ có 3 hàm:

from dis import dis


def function():
    return 1


def function_args(arg1):
    return arg1


def function_kwargs(arg1=None):
    return arg1


def test():
    function()
    function_args(1)
    function_kwargs(arg1=1)


dis(test)

Chạy đoạn code này trong Python 3.6Python 3.7, chúng ta có thể thấy rằng là không có sự thay đổi nào trong kết quả code hay performance.

Một ví dụ khác với các bound method:

from dis import dis


class TestClass(object):
    def function():
        return 1

    def function_args(arg1):
        return arg1

    def function_kwargs(arg1=None):
        return arg1


def test():
    i = TestClass()
    i.function()
    i.function_args(1)
    i.function_kwargs(arg1=1)


dis(test)

Kết quả của đoạn code:

  • MTT LOAD_METHOD mới thay thế các bound method như là các attribute và chỉ gọi chúng như các hàm thông thường. Nhớ rằng LOAD_METHODCALL_METHOD nhanh hơn CALL_FUNCTION đối với các instance method

  • Các bound method với tham biến khóa giống nhau trong Python 3.6 nên sẽ không có sự thay đổi về performance

  • Các bound method không có tham biến bây giờ nhanh hơn

LOAD_METHOD thay thế cho LOAD_ATTR. LOAD_METHOD là một bản copy về logic trong LOAD_ATTR nhưng được tối ưu tốt hơn khi method không được override và nó có các tham biến vị trí.

Ngoài vấn đề này, bạn có thể có những câu hỏi khác:

Vậy nếu tôi đặt các hàm vào một class, liệu chúng có nhanh hơn?

=> Không, bởi vì việc tăng tốc chính là loại bỏ các sự chậm trễ liên quan tối object

Do đâu mà các tham biến khóa và tham biến thay đổi lại nhận được sự đối đãi đặc biệt?

=> Các tham biến khóa đòi hỏi sự quan tâm đặc biệt bởi vì không có cái tương tự trong C (CPython được viết bằng C) nên cần có một đoạn code phụ để biên dịch hai tuple truyền vào method.

=> Các tham biến thay đổi, dù là vị trí hay khóa, đều đòi hỏi những sự chăm sóc đặc biệt.

2. str.find() is faster for some characters

Một số ký tự unicode không may lại gặp vấn đề khi sử dụng str.find(x): tốc độ giảm 25 lần

$ ./python -m perf timeit -s 's = "一丁丂七丄丅丆万丈三上下丌不与丏丐丑丒专且丕世丗丘丙业丛东丝丞丟丠両丢丣两严並丧丨丩个丫丬中丮丯丰丱串丳临丵丶丷丸丹为主丼丽举丿乀乁乂乃乄久乆乇么义乊之乌乍乐乑乒乓乔乕乖乗乘乙乚乛乜九乞也习乡乢乣乤乥书乧乨乩乪乫乬乭乮乯买乱乲乳乴乵乶乷乸乹乺乻乼乽乾乿亀亁亂亃亄亅了亇予争 亊事二亍于亏亐云互亓五井亖亗亘亙亚些亜亝亞亟亠亡亢亣交亥亦产亨亩亪享京亭亮亯亰亱亲亳亴亵亶亷亸亹人亻亼亽亾亿什仁仂仃仄仅仆仇仈仉今介仌仍从仏仐仑仒仓仔仕他仗付仙仚仛仜 仝仞仟仠仡仢代令以仦仧仨仩仪仫们仭仮仯仰仱仲仳仴仵件价仸仹仺任仼份仾仿"*100' -- 's.find("乎")'

Unpatched:  Median +- std dev: 761 us +- 108 us
Patched:    Median +- std dev: 117 us +- 9 us

Trong Python 3.7, kích thước điểm mã (code-point) Unicode kỳ vọng không còn bị fix cứng và các method đã được tối ưu cho các ký tự dài.

Những method này vẫn chậm hơn nhưng giờ chỉ còn 3 lần so với ASCII thay vì 25 lần như trước đó!

3. os.fwalk is 2x faster

Hàm fwalk trong module os (chỉ trong Python 3) là một bộ sinh cây thư mục.

Nó hoạt động chính xác như walk(), ngoại trừ việc nó trả về kết quả gồm 4 tuple (dirpath, dirnames, filenames, dirfd).

Sự thay đổi là để sửa đổi bản implementation để sử dụng method scandir thay vì listdir, cái mà được hệ điều hành tối ưu và thực thi nhanh hơn rất nhiều.

4. Regular expressions are faster*

Trong module re, có method compile có chức năng biên dịch một chuỗi biểu thức chính quy và một tập các cờ tùy chọn. Các cờ này có thể là các cờ RegEx, được truyền tới thư viện RegEx.

Một sự thay đổi trong Python 3.6 làm chậm việc gọi này khi các cờ được truyền vào là integer. Python 3.7 đã giải quyết vấn đề này nhưng vẫn không nhanh như Python 3.5.

5. Regular expressions are faster for case-insensitive matching

Change log:

Matching and searching case-insensitive regular expressions is much slower than matching and searching case-sensitive regular expressions. Case-insensitivity requires converting every character in input string to lower case and disables some optimizations. But there are only 2669 cased characters (52 in ASCII mode). For all other characters in the pattern we can use case-sensitive matching.

Sự cải thiện là đáng kể. Nếu bạn đang match các ký tự ASCII, bạn có thể thấy tốc độ được đẩy lên 20 lần

Nguồn: https://hackernoon.com/5-speed-improvements-in-python-3-7-1b39d1581d86


All Rights Reserved