老司機都開火箭了!Cython 助力 Python NLP 實現百倍加速

2021-01-10 雷鋒網

雷鋒網 AI 研習社按:本文的作者是來自 Hugging face 的科學家 Thomas Wolf,他的研究方向包括機器學習、自然語言處理和深度學習。在這篇博客中,他介紹了如何利用 Cython 和 spaCy 讓 Python 在自然語言處理任務中獲得百倍加速。雷鋒網 AI 研習社根據原文進行了編譯。

SpaceX 獵鷹重型發射器,版權歸 SpaceX 所有

提示:本文中涉及的所有例子都可以在這個 Jupyter notebook 中獲得源碼。

在去年我們發布了用 Python 實現的基於神經網絡的相互引用解析包(Neural coreference resolution package)之後,在社區中獲得了驚人數量的反饋,許多人開始將該解析包用到各種各樣的應用中,有一些應用場景甚至已經超出了我們原本設計的對話框用例(Dialog use-case)。

後來我們發現,雖然這個解析包對於對話框消息而言,解析速度完全夠用,但如果要解析更大篇幅的文章就顯得太慢了。

因此我決定要深入探索解決方案,並最終開發出了 NeuralCoref v3.0。這個版本比之前(每秒解析幾千字)的要快出百倍,同時還保證了相同的準確性,當然,它依然易於使用,也符合 Python 庫的生態環境。

在本篇文章中,我想向大家分享我在開發 NeuralCoref v3.0 過程中學到的一些經驗,尤其將涉及:

我的標題其實有點作弊,因為我實際上要談論的是 Python,同時也要介紹一些 Cython 的特性。不過你知道嗎?Cython 屬於 Python 的超集,所以不要讓它嚇跑了!

小提示:你當前所編寫的 Python 項目已經算是一個 Cython 項目了。

以下給出了一些可能需要採用這種加速策略的場景:

你正在使用 Python 給自然語言處理任務開發一個應用級模塊

你正在使用 Python 分析一個自然語言處理任務的大型數據集

你正在為諸如 PyTorch/TensoFlow 這些深度學習框架預處理大型訓練集,或者你的深度學習模型採用了處理邏輯複雜的批量加載器(Batch loader),它嚴重拖慢了你的訓練速度

提示:我還發布了一個 Jupyter notebook,其中包含了本文中討論的所有示例,歡迎大家下載調試!

百倍加速第一步:代碼剖析

你需要知道的第一件事情是,你的大部分代碼在純 Python 環境下可能都運行良好,但是其中存在一些瓶頸函數(Bottlenecks functions),一旦你能給予它們更多的「關照」,你的程序將獲得幾個數量級的提速。

所以你應該從剖析自己的 Python 代碼開始,找出那些低效模塊。其中一種方法就是使用 cProfile:

import cProfile

import pstats

import my_slow_module

cProfile.run('my_slow_module.run()', 'restats')

p = pstats.Stats('restats')

p.sort_stats('cumulative').print_stats(30)

你很可能會發現低效的原因是因為一些循環控制,或者你使用神經網絡時引入了過多的 Numpy 數組操作(我不會花費時間在這裡介紹 Numpy,這個問題已經有太多文章進行了討論)。

那麼我們該如何來加速循環呢?

在 Pyhthon 中加入 Cython 加速循環計算

讓我們通過一個簡單的例子來解決這個問題。假設有一堆矩形,我們將它們存儲成一個由 Python 對象(例如 Rectangle 對象實例)構成的列表。我們的模塊的主要功能是對該列表進行迭代運算,從而統計出有多少個矩形的面積是大於所設定閾值的。

我們的 Python 模塊非常簡單:

from random import random


class Rectangle:    

    def __init__(self, w, h):        

        self.w = w        

        self.h = h    

    def area(self):        

        return self.w * self.h


    def check_rectangles(rectangles, threshold):    

        n_out = 0    

        for rectangle in rectangles:        

            if rectangle.area() > threshold:            

                n_out += 1    

        return n_out


def main():    

    n_rectangles = 10000000    

    rectangles = list(Rectangle(random(), random()) for i in range(n_rectangles))    

    n_out = check_rectangles(rectangles, threshold=0.25)    

    print(n_out)

其中 check_rectangles 函數就是我們程序的瓶頸!它對一個很長的 Python 對象列表進行迭代,而這一過程會相當緩慢,因為 Python 解釋器在每次迭代中都需要做很多工作(查找類中的 area 方法、參數的打包和解包、調用 Python API 等等)。

這時候該有請 Cython 出場幫助我們加速循環操作了。

Cython 語言是 Python 的一個超集,它包含有兩種類型的對象:

該循環只要採用 Cython 進行復現就能獲得更高的執行速度,不過在 Cython 中我們只能夠操作 Cython C 對象。

定義這種循環最直接的一種方法就是,定義一個包含有計算過程中我們所需要用到的所有對象的結構體。具體而言,在本例中就是矩形的長度和寬度。

然後我們可以將矩形對象列表存儲到 C 的結構數組中,再將數組傳遞給 check_rectangles 函數。這個函數現在將接收一個 C 數組作為輸入,此外我們還使用 cdef 關鍵字取代了 def(注意:cdef 也可以用於定義 Cython C 對象)將函數定義為一個 Cython 函數。 

這裡是 Cython 版本的模塊程序:

from cymem.cymem cimport Pool

from random import random


cdef struct Rectangle:    

    float w    

    float h


cdef int check_rectangles(Rectangle* rectangles, int n_rectangles, float threshold):    

    cdef int n_out = 0    

    # C arrays contain no size information => we need to give it explicitly    

    for rectangle in rectangles[:n_rectangles]:        

        if rectangle[i].w * rectangle[i].h > threshold:            

            n_out += 1    

    return n_out


def main():    

    cdef:        

        int n_rectangles = 10000000        

        float threshold = 0.25        

        Pool mem = Pool()        

        Rectangle* rectangles = <Rectangle*>mem.alloc(n_rectangles, sizeof(Rectangle))    

    for i in range(n_rectangles):        

        rectangles[i].w = random()        

        rectangles[i].h = random()    

    n_out = check_rectangles(rectangles, n_rectangles, threshold)

    print(n_out)

這裡我們使用了原生的 C 數組指針,不過你還有其它選擇,特別是 C++ 中諸如向量、二元組、隊列這樣的結構體。在這段程序中,我還使用了一個來自 cymem 提供的 Pool() 內存管理對象,它可以避免手動釋放所申請的 C 數組內存空間。當不再需要使用 Pool 中的對象時,它將自動釋放該對象所佔用的內存空間。

補充:spaCy API 的 Cython 標準頁面提供了在實際應用中使用 Cython 實現自然語言處理任務的參考資料。 

讓我們開始動手吧!

有很多辦法來測試、編譯和發布 Cython 代碼。Cython 甚至可以像 Python 一樣直接用於 Jupyter Notebook 中。

通過 pip install cython 命令安裝 Cython。

首先在 Jupyter 中進行測試

使用 %load_ext Cython 指令在 Jupyter notebook 中加載 Cython 擴展。

然後通過指令 %%cython,我們就可以像 Python 一樣在 Jupyter notebook 中使用 Cython。

如果在執行 Cython 代碼的時候遇到了編譯錯誤,請檢查 Jupyter 終端的完整輸出信息。

大多數情況下可能都是因為在 %%cython 之後遺漏了 -+ 標籤(比如當你使用 spaCy Cython 接口時)。如果編譯器報出了關於 Numpy 的錯誤,那就是遺漏了 import numpy。

正如我在一開始就提到的,請好好閱讀這份 Jupyter notebook 和這篇文章,它包含了我們在 Jupyter 中討論到的所有示例。

編寫、使用和發布 Cython 代碼

Cython 代碼的文件後綴是 .pyx,這些文件將被 Cython 編譯器編譯成 C 或 C++ 文件,再進一步地被 C 編譯器編譯成字節碼文件。最終 Python 解釋器將能夠調用這些字節碼文件。

你也可以使用 pyximport 將一個 .pyx 文件直接加載到 Python 程序中:

>>> import pyximport; pyximport.install()

>>> import my_cython_module

你也可以將自己的 Cython 代碼作為 Python 包構建,然後像正常的 Python 包一樣將其導入或者發布,更多細節請參考這裡。不過這種做法需要花費更多的時間,特別是你需要讓 Cython 包能夠在所有的平臺上運行。如果你需要一個參考樣例,不妨看看 spaCy 的安裝腳本。

在我們開始優化自然語言處理任務之前,還是先快速介紹一下 def、cdef 和 cpdef 這三個關鍵字。它們是你開始學會使用 Cython 之前需要掌握的最主要的知識。

你可以在 Cython 程序中使用三種類型的函數:

Python 函數由 def 關鍵字定義,它的輸入和輸出都是 Python 對象。在函數內可以使用 Python 和 C/C++ 對象,並且能夠調用 Cython 和 Python 函數。

Cython 函數由 cdef 關鍵字進行定義,它可以作為輸入對象,在函數內部也可以操作或者輸出 Python 和 C/C++ 對象。這些函數不能從 Python 環境中訪問(即 Python 解釋器和其它可以導入 Cython 模塊的純 Python 模塊),但是可以由其它 Cython 模塊進行導入。

通過關鍵字 cpdef 定義的 Cython 函數與 cdef 定義的 Cython 函數很相似,但是 cpdef 定義的函數同時還提供了 Python 裝飾器,所以它們能夠在 Python 環境中被直接調用(函數採用 Python 對象作為輸入與輸出),此外也支持在 Cython 模塊中被調用(函數採用 C/C++ 或者 Python 對象作為輸入)。

cdef 關鍵字的另一個用途就是,在代碼中表明某一個對象是 Cython C/C++ 對象。所以除非你在代碼中使用 cdef 聲明對象,否則這些對象都會被解釋器當做 Python 對象(這會導致訪問速度變慢)。

使用 Cython 和 spaCy 加速自然語言處理

這一切看起來都很好,但是......我們到現在都還沒開始涉及優化自然語言處理任務!沒有字符串操作,沒有 unicode 編碼,也沒有我們在自然語言處理中所使用的妙招。

此外 Cython 的官方文檔甚至建議不要使用 C 語言類型的字符串:

通常來說:除非你明確地知道自己正在做什麼,不然就該避免使用 C 類型字符串,而應該使用 Python 的字符串對象。

那麼當我們在操作字符串時,要如何在 Cython 中設計一個更加高效的循環呢?

spaCy 引起了我們的注意力。

spaCy 處理該問題的做法就非常地明智。

將所有的字符串轉換為 64 位哈希碼

spaCy 中所有的 unicode 字符串(一個標記的文本、它的小寫形式文本、它的引理形式、POS 標記標籤、解析樹依賴標籤、命名實體標籤等等)都被存儲在一個稱為 StringStore 的數據結構中,它通過一個 64 位哈希碼進行索引,例如 C 類型的 uint64_t。

StringStore 對象實現了 Python unicode 字符串與 64 位哈希碼之前的查找映射。

它可以從 spaCy 的任何地方和任意對象進行訪問,例如 npl.vocab.strings、doc.vocab.strings 或者 span.doc.vocab.string。

當某一個模塊需要在某些標記(tokens)上獲得更快的處理速度時,你可以使用 C 語言類型的 64 位哈希碼代替字符串來實現。調用 StringStore 查找表將返回與該哈希碼相關聯的 Python unicode 字符串。

但是 spaCy 能做的可不僅僅只有這些,它還允許我們訪問文檔和詞彙表完全填充的 C 語言類型結構,我們可以在 Cython 循環中使用這些結構,而不必去構建自己的結構。

SpaCy 的內部數據結構

與 spaCy 文檔有關的主要數據結構是 Doc 對象,該對象擁有經過處理的字符串的標記序列(「words」)以及 C 語言類型對象中的所有標註,稱為 doc.c,它是一個 TokenC 的結構數組。

TokenC 結構包含了我們需要的關於每個標記的所有信息。這種信息被存儲成 64 位哈希碼,它可以與我們剛剛所見到的 unicode 字符串進行重新關聯。

如果想要準確地了解這些漂亮的 C 結構中的內容,可以查看新建的 spaCy 的 Cython API 文檔。

接下來看一個簡單的自然語言處理的例子。

更快的自然語言處理

假設現在有一個文本文檔的數據集需要分析。

import urllib.request

import spacy


with urllib.request.urlopen('https://raw.githubusercontent.com/pytorch/examples/master/word_language_model/data/wikitext-2/valid.txt') as response:   

    text = response.read()

nlp = spacy.load('en')

doc_list = list(nlp(text[:800000].decode('utf8')) for i in range(10))

我寫了一個腳本用於創建一個包含有 10 份文檔的列表,每份文檔都大概含有 17 萬個單詞,採用 spaCy 進行分析。當然我們也可以對 17 萬份文檔(每份文檔包含 10 個單詞)進行分析,但是這樣做會導致創建的過程非常慢,所以我們還是選擇了 10 份文檔。

我們想要在這個數據集上展開某些自然語言處理任務。例如,我們可以統計數據集中單詞「run」作為名詞出現的次數(例如,被 spaCy 標記為「NN」詞性標籤)。

採用 Python 循環來實現上述分析過程非常簡單和直觀:

def slow_loop(doc_list, word, tag):    

    n_out = 0    

    for doc in doc_list:        

        for tok in doc:            

            if tok.lower_ == word and tok.tag_ == tag:                

                n_out += 1    

     return n_out


def main_nlp_slow(doc_list):    

    n_out = slow_loop(doc_list, 'run', 'NN')    

    print(n_out)

但是這個版本的代碼運行起來非常慢!這段代碼在我的筆記本上需要運行 1.4 秒才能獲得答案。如果我們的數據集中包含有數以百萬計的文檔,為了獲得答案,我們也許需要花費超過一天的時間。

我們也許能夠採用多線程來實現加速,但是在 Python 中這種做法並不是那麼明智,因為你還需要處理全局解釋器鎖(GIL)。另外請注意,Cython 也可以使用多線程!Cython 在後臺可以直接調用 OpenMP。不過我沒有時間在這裡討論並行性,所以請查看此連結以了解更多詳情。

現在讓我們嘗試使用 spaCy 和 Cython 來加速 Python 代碼。

首先需要考慮好數據結構,我們需要一個 C 類型的數組來存儲數據,需要指針來指向每個文檔的 TokenC 數組。我們還需要將測試字符(「run」和「NN」)轉成 64 位哈希碼。

當所有需要處理的數據都變成了 C 類型對象,我們就可以以純 C 語言的速度對數據集進行迭代。

這裡展示了這個例子被轉換成 Cython 和 spaCy 的實現:

%%cython -+

import numpy # Sometime we have a fail to import numpy compilation error if we don't import numpy

from cymem.cymem cimport Pool

from spacy.tokens.doc cimport Doc

from spacy.typedefs cimport hash_t

from spacy.structs cimport TokenC


cdef struct DocElement:    

    TokenC* c    

    int length


cdef int fast_loop(DocElement* docs, int n_docs, hash_t word, hash_t tag):    

    cdef int n_out = 0    

    for doc in docs[:n_docs]:        

        for c in doc.c[:doc.length]:            

            if c.lex.lower == word and c.tag == tag:                

                n_out += 1    

    return n_out


def main_nlp_fast(doc_list):    

    cdef int i, n_out, n_docs = len(doc_list)    

    cdef Pool mem = Pool()    

    cdef DocElement* docs = <DocElement*>mem.alloc(n_docs, sizeof(DocElement))    

    cdef Doc doc    

    for i, doc in enumerate(doc_list): # Populate our database structure        

        docs[i].c = doc.c        

        docs[i].length = (<Doc>doc).length    

    word_hash = doc.vocab.strings.add('run')    

    tag_hash = doc.vocab.strings.add('NN')    

    n_out = fast_loop(docs, n_docs, word_hash, tag_hash)    

    print(n_out)

代碼有點長,因為我們必須在調用 Cython 函數之前在 main_nlp_fast 中聲明和填充 C 結構。

補充:如果你在代碼中需要多次使用低級結構,比每次填充 C 結構更優雅的做法是,使用 C 類型結構的 Cython 擴展類型裝飾器來設計 Python 代碼。這就是大多數 spaCy 代碼所採用的結構,它非常優雅,兼具高效、低內存花銷和易於訪問的特性。

這串代碼雖然變長了,但是運行效率卻更高!在我的 Jupyter notebook上,這串 Cython 代碼只運行了大概 20 毫秒,比之前的純 Python 循環快了大概 80 倍。

使用 Jupyter notebook 單元編寫模塊的速度很可觀,它可以與其它 Python 模塊和函數自然地連接:在 20 毫秒內掃描大約 170 萬個單詞,這意味著我們每秒能夠處理高達 8 千萬個單詞。

對使用 Cython 進行自然語言處理加速的介紹到此為止,希望大家能喜歡它。

關於 Cython 還有很多其它的東西可以介紹,但是已經超出了這篇文章的初衷(這篇文章只是作為簡介)。從現在開始,最好的資料也許是這份綜述性的 Cython 教程和介紹 spaCy 自然語言處理的 Cython 頁面。

如果你還想要獲得更多類似的內容,請記得給我們點讚喲!

Via 100 Times Faster Natural Language Processing in Python,雷鋒網(公眾號:雷鋒網) AI 研習社編譯整理

雷鋒網版權文章,未經授權禁止轉載。詳情見轉載須知。

相關焦點

  • 乾貨| 請收下這份2018學習清單:150個最好的機器學習,NLP和Python...
    找到超過25個有關ML的「小抄」後,我寫一篇博文(https://unsupervisedmethods.com/cheat-sheet-of-machine-learning-and-python-and-math-cheat-sheets-a4afe4e791b6),裡面的資源都有超連結。
  • ...請收下這份2018學習清單:150個最好的機器學習,NLP和Python教程
    找到超過25個有關ML的「小抄」後,我寫一篇博文(https://unsupervisedmethods.com/cheat-sheet-of-machine-learning-and-python-and-math-cheat-sheets-a4afe4e791b6),裡面的資源都有超連結。
  • 乾貨 | 請收下這份2018學習清單:150個最好的機器學習,NLP和Python教程
    找到超過25個有關ML的「小抄」後,我寫一篇博文(https://unsupervisedmethods.com/cheat-sheet-of-machine-learning-and-python-and-math-cheat-sheets-a4afe4e791b6),裡面的資源都有超連結。
  • 加快Python算法的四個方法(二)Numba
    使用Numba,你可以加速所有以集中計算的、計算量大的python函數(例如循環)的速度。它還支持numpy庫!因此,你也可以在計算中使用numpy,並加快整體計算的速度,因為python中的循環非常慢。你還可以使用python標準庫中的數學庫的許多功能,例如sqrt等。2.為什麼選擇Numba?所以,為什麼要選擇Numba?
  • 駕校教練默默流淚,看小白司機如何快速成為老司機!
    小白司機、新手司機有多可怕?恐怕女司機看到了也得退讓三分...相信不少人都有經歷過駕校練車、駕照考試的層層歷練,最終拿到駕照小本本的愉悅痛快感。但在駕照到手後,開著車在路上措手不及的小白司機估計不會佔少數。駕校練出來的駕駛技能,面對千變萬化的道路交通狀況,確實不太足夠能應付...
  • 開過寶馬X2,換了臺凱迪拉克XT4,老司機說出了兩款車的優劣
    拿比較熱門的凱迪拉克XT4和寶馬X2來說,許多人也比較迷茫,不知其優劣,今天給各位帶來的是《開過寶馬X2,換了臺凱迪拉克XT4,老司機說出了兩款車的優劣》。先看凱迪拉克XT4的外觀,外觀沒的說,就說那鐵皮吧,挺厚的。外觀大氣,車頭很猛,就是走雨路車體會有很多泥,鐵皮有點薄。外觀就要霸氣多了,不光犀利更多的就是運動氣息。外觀肯定中意的,唯一不滿意的是車漆。
  • 雲貴高原開高鐵加速實現全面小康夢
    雲貴高原作為我國少數民族種類最多的地區,由於自然環境原因,晴天一身灰、雨天兩腳泥,交通不便、出行困難,與東部相比發展不平衡不充分問題突出,成為全面建成小康社會、實現社會主義現代化的短板和薄弱地區。貧窮不是社會主義,惡劣的自然環境、原始的交通工具、飢轆的日常生活等等境況,更與小康社會相差十萬八千裡。幫助他們與全國人民一道實現小康夢,是黨中央的部署要求,也是各級組織和廣大企業的義務責任。
  • NLP入門+實戰必讀:一文教會你最常見的10種自然語言處理技術
    pip install spacy#python -m spacy download enimport spacynlp=spacy.load("en")doc="good better bestpython -m spacy download en nlp=spacy.load('en')sentence="Ashok killed the snake with a stick"for token in nlp(sentence):
  • 老司機車牌號是什麼意思啥梗?老司機車牌號怎麼用方法介紹
    老司機車牌號什麼意思 老司機車牌號怎麼用方法介紹  自從老司機這個網絡詞上線後,成為了大多數網民們專用的一個詞彙,這個詞指的是在各個網站、論壇裡接觸時間比較長,熟悉站內各種規則,內容以及技術、玩法,並且掌握著一定資源的老手。
  • 掌握這幾個開車小技巧,你也可以成為老司機
    茶餘飯後,談論新手和老司機的區別時,最常用來區分兩者的通常是駕齡,認為只要開車時間足夠的長,公裡數足夠的多,那麼就能稱之為老司機。其實並非如此,老司機之所以能稱之為老司機,往往是因為一些普通駕駛者沒有掌握的開車技巧以及寶貴的駕駛經驗,小編這裡給大家總結了幾條實用的經驗技巧。
  • 老司機都說開夜車:走灰不走白,見黑停下來,到底是什麼意思呢
    老司機都說開夜車:「走灰不走白,見黑停下來」,到底是什麼意思呢?白天開車和夜間開車的視覺效果是截然不同的,特別是夜間開車,因為白天視野會更加開闊,有助於駕駛員對路況進行判斷,夜間開車受到了多元化的阻撓,視線降低,而且還有很多駕駛員有著夜晚開車開遠光燈的習慣,強光會眼中影響對向車輛的視覺,從而引發交通事故。
  • 新手司機的一千公裡,要怎麼開才是老司機?
    開車最費油的就是加速階段,緩慢加速可以以時間換低油耗,溫柔駕駛對自己、他人、車輛、安全、乘客的乘坐舒適度、駕駛者的心態都有好處。 2、隨時可加速、隨時可減速是控制車速的基本要求。這裡說的控制減速不是說在油門全開的時候猛踩剎車,而是養成收油門踏板的習慣,油門和剎車配合使用來給車輛降速,如果出遠門最好利用導航提前查詢路況信息,避開擁堵路段。
  • 為什麼現在老司機發片都不用「磁力鏈」了?
    不過,也不得不感嘆,在前4-5年,知道「1024」這個梗的真正意義的人,還是挺多的,但到了這1-2年,「新上路」的老司機對「1024」都已經基本上不太知道了。特別是伴隨著盜版問題的出現,很多大型的BT伺服器供應者都被法律制裁強制關停,如「BT天堂」、「海盜灣」等網站,BT生態圈就會受到毀滅式打擊。2.下載、保存種子變成了一件麻煩事。種子文件雖然小,但畢竟再小,它也是一個文件,總聽到有老司機說自己電腦有幾個GB的種子(注意:不是幾個BG的電影),就可以明白,其實種子的保存也是個讓人頭疼的事。
  • 火箭加速器 免費一年 老司機科學上網
    提前普及:網絡加速器(Web accelerator):是上網加速軟體,由用戶終端軟體以及加速伺服器構成,具有高性能的網絡優化網關,通過改良 HTTP 協議與文字、影像壓縮技術,大幅改善網頁瀏覽速度和訪問速度。加速器,可借v。p。。n技術虛擬撥號加速軟體。
  • 如何讓Python像Julia一樣快地運行
    包含下面的所有基準測試的完整代碼的 Notebook 可在此處(https://www.ibm.com/developerworks/community/blogs/jfp/resource/julia_python.zip)找到。 鑑於各種社交媒體上的評論,我添加了這樣一句話:我沒有在這裡使用 Python 的替代性實現。我沒有編寫任何 C代碼:如果您不信,可試試尋找分號。
  • 經開區商業航天加速起飛
    星河動力首飛箭「穀神星一號(遙一)」是一枚怎樣的火箭?成功發射有何意義?作為國內民營火箭公司聚集地、著力打造「飛天」場景的北京經開區還將有怎樣的進展?  衛星物聯網+商業發射「雙領先」  星河動力成立時間不長,發展速度卻很快。  2018年2月,星河動力公司註冊成立,定位為一家商業化運載火箭方案提供商,致力於實現低成本的火箭發射任務。
  • 如何在 i5 上實現 20 倍的 Python 運行速度?
    他對外宣布:在配備四核 i5 的 iMAC 上實現了 20 倍的性能加速!至於他是怎麼做到的,請繼續往下看(含代碼)。James ReindersJames Reinders:利用 Intel Distribution for Python,我實現了 Python 的 20 倍加速,並且可用單個命令關閉/啟用。
  • 汽車暖風開了老久還不暖和是咋回事?老司機:空調用錯一切都白費
    汽車暖風開了老久還不暖和是咋回事?老司機:空調用錯一切都白費馬上就要進入10月份了,氣溫已經明顯有下降的趨勢了。維度較高的地區,很多車友已經早早的就開啟了暖風!但是很多抱怨說,暖風開了沒啥用,也不知道是啥情況。暖風不頂事,也許就是你用錯了汽車空調!究竟怎麼回事你?
  • 高速上老司機都懂的詞語,新手司機趕緊學起來,出門在外安全第一
    高速上老司機都懂的詞語,新手司機趕緊學起來,出門在外安全第一如今私家汽車這一交通工具在國內越來越普及啦,大家都認為它快捷方便,因此,去考駕證的朋友們也越來越多了。不少朋友們對自己的車技都是十分自信,在經過實習期之後,就放心地開上高速公路一展身手。今天我就來跟大家分享一個小知識,能使得各位車主在駕駛過程中更加安全哦。有個詞語叫做「逢三進一」,新手小白們是不明白這個詞語的意思,而駕齡比較久的老司機們就清楚啦。簡單來說,就是建議司機們每過三個服務區,就要進去休息一下。
  • 開衝什麼意思什麼梗? 汙力滿滿的梗老司機秒懂
    開衝什麼意思什麼梗? 汙力滿滿的梗老司機秒懂時間:2019-11-23 15:57   來源:小雞詞典   責任編輯:沫朵 川北在線核心提示:原標題:開衝什麼意思什麼梗? 汙力滿滿的梗老司機秒懂 是指男性自慰,也可廣泛指開始衝呀,開始做某件事等。