主頁 > 軟體設計 > 為什么`np.sum(range(N))`很慢?

為什么`np.sum(range(N))`很慢?

2021-10-16 00:46:33 軟體設計

我看到了一個關于 python 中回圈速度的視頻,其中解釋說這樣做sum(range(N))比手動回圈range并將變數加在一起要快得多,因為前者由于使用了內置函式而在 C 中運行,而在后者中求和是在(慢)python 中完成的。我很好奇加入numpy混合物時會發生什么如我所料np.sum(np.arange(N))的是最快的,但sum(np.arange(N))np.sum(range(N))甚至慢于做天真的for回圈。

為什么是這樣?

這是我用來測驗的腳本,一些關于我知道的減速原因的評論(主要來自視頻)以及我在我的機器上得到的結果(python 3.8.10,numpy 1.19.5):

更新腳本:

import numpy as np
from timeit import timeit

N = 10_000_000
repetition = 10

def sum0(N = N):
    s = 0
    i = 0
    while i < N: # condition is checked in python
        s  = i
        i  = 1 # both additions are done in python
    return s

def sum1(N = N):
    s = 0
    for i in range(N): # increment in C
        s  = i # addition in python
    return s

def sum2(N = N):
    return sum(range(N)) # everything in C

def sum3(N = N):
    return sum(list(range(N)))

def sum4(N = N):
    return np.sum(range(N)) # very slow np.array conversion

def sum5(N = N):
    # much faster np.array conversion
    return np.sum(np.fromiter(range(N),dtype = np.int)) 

def sum6(N = N):
    # possibly slow conversion to Py_long from np.int
    return sum(np.arange(N))

def sum7(N = N):
    # list returns a list of np.int-s
    return sum(list(np.arange(N)))

def sum7v2(N = N):
    # tolist conversion to python int seems faster than the implicit conversion
    # in sum(list()) (tolist returns a list of python int-s)
    return sum(np.arange(N).tolist())

def sum8(N = N):
    return np.sum(np.arange(N)) # everything in numpy (fortran libblas?)

def array_basic(N = N):
    return np.array(range(N))

def array_dtype(N = N):
    return np.array(range(N),dtype = np.int)

def array_iter(N = N):
    # np.sum's source code mentions to use fromiter to convert from generators
    return np.fromiter(range(N),dtype = np.int)

print(f"while loop:         {timeit(sum0, number = repetition)}")
print(f"for loop:           {timeit(sum1, number = repetition)}")
print(f"sum_range:          {timeit(sum2, number = repetition)}")
print(f"sum_rangelist:      {timeit(sum3, number = repetition)}")
print(f"npsum_range:        {timeit(sum4, number = repetition)}")
print(f"npsum_fromiterrange:{timeit(sum5, number = repetition)}")
print(f"sum_arange:         {timeit(sum6, number = repetition)}")
print(f"sum_list_arange:    {timeit(sum7, number = repetition)}")
print(f"sum_arange_tolist:  {timeit(sum7v2, number = repetition)}")
print(f"npsum_arange:       {timeit(sum8, number = repetition)}")
print(f"array_basic:        {timeit(array_basic, number = repetition)}")
print(f"array_dtype:        {timeit(array_dtype, number = repetition)}")
print(f"array_iter:         {timeit(array_iter,  number = repetition)}")

# Example output:
#
# while loop:         9.249794696999743
# for loop:           6.026467555000636
# sum_range:          1.4830789409988938
# sum_rangelist:      3.6745876889999636
# npsum_range:        16.216972655000063
# npsum_fromiterrange:3.47655400199983
# sum_arange:         16.656015603000924
# sum_list_arange:    19.500842117000502
# sum_arange_tolist:  4.004777374000696
# npsum_arange:       0.2332638230000157
# array_basic:        16.1631146109994
# array_dtype:        16.550737804000164
# array_iter:         3.9803170430004684

uj5u.com熱心網友回復:

讓我們看看我是否可以總結結果。

sum可以處理任何可迭代物件,反復詢問下一個值并添加它。 range是一個生成器,很高興提供下一個值

# sum_range:          1.4830789409988938

從范圍中制作串列需要時間:

# sum_rangelist:      3.6745876889999636

對預先生成的串列求和實際上比對范圍求和要快:

%%timeit x = list(range(N))
    ...: sum(x)

np.sum旨在對陣列求和。它是np.add.reduce.

np.sum有一個棄用警告np.sum(generator),建議使用fromiter或 Python sum

# npsum_range:        16.216972655000063

fromiter是從生成器制作陣列的最佳方式。使用np.arrayonrange是遺留代碼,將來可能會消失。我認為這是唯一的generatornp.array會接受的。

np.array是一個通用函式,可以處理許多情況,包括嵌套陣列和轉換為各種 dtype。因此,它必須處理整個輸入引數,推匯出 shape 和 dtype。

# npsum_fromiterrange:3.47655400199983

numpy 陣列的迭代比串列慢,因為它必須“拆箱”每個元素。

# sum_arange:         16.656015603000924

同樣,從陣列中創建串列很慢;相同型別的python級別迭代。

# sum_list_arange:    19.500842117000502

arr.tolist()比較快,在編譯代碼中創建一個純python串列。所以速度類似于從范圍內制作串列。

# sum_arange_tolist:  4.004777374000696

np.sum一個陣列是純粹的numpy并且相當快。 np.sum(x)哪里x=np.arange(N)更快(大約 4 倍)

# npsum_arange:       0.2332638230000157

np.sum from range 或 list 由首先創建陣列的成本決定:

# array_basic:        16.1631146109994
# array_dtype:        16.550737804000164
# array_iter:         3.9803170430004684

uj5u.com熱心網友回復:

sumcpython 源代碼sum最初似乎嘗試了一條假設所有輸入都是相同型別的快速路徑。如果失敗,它只會迭代:

/* Fast addition by keeping temporary sums in C instead of new Python objects.
   Assumes all inputs are the same type.  If the assumption fails, default
   to the more general routine.
*/

我并不完全確定幕后發生了什么,但很可能是 C 型別到 Python 物件的重復創建/轉換導致了這些減速。值得注意的是,sumrange都是用 C 實作的。


下一點并不是這個問題的真正答案,但我想知道我們是否可以加速sumpython ranges,因為它range一個非常智能的物件

為此,我使用functools.singledispatch專門為該型別覆寫的內置sum函式range然后實作了一個小函式來計算一個等差數列的總和

from functools import singledispatch

def sum_range(range_, /, start=0):
    """Overloaded `sum` for range, compute arithmetic sum"""
    n = len(range_)
    if not n:
        return start
    return int(start   (n * (range_[0]   range_[-1]) / 2))

sum = singledispatch(sum)
sum.register(range, sum_range)

def test():
    """
    >>> sum(range(0, 100))
    4950
    >>> sum(range(0, 10, 2))
    20
    >>> sum(range(0, 9, 2))
    20
    >>> sum(range(0, -10, -1))
    -45
    >>> sum(range(-10, 10))
    -10
    >>> sum(range(-1, -100, -2))
    -2500
    >>> sum(range(0, 10, 100))
    0
    >>> sum(range(0, 0))
    0
    >>> sum(range(0, 100), 50)
    5000
    >>> sum(range(0, 0), 10)
    10
    """

if __name__ == "__main__":
    import doctest
    doctest.testmod()

我不確定這是否完整,但它絕對比回圈快。

uj5u.com熱心網友回復:

np.sum(range(N))很慢,主要是因為當前的 Numpy 實作沒有使用足夠的關于 generator 提供的值的確切型別/內容的資訊range(N)一般問題的核心本質上是由于 Python 和大整數的動態型別,盡管 Numpy 可以優化這種特定情況。

首先,range(N)回傳一個動態型別的 Python 物件,它是一種(特殊型別的)Python 生成器。此生成器提供的物件也是動態型別的。它實際上是一個純 Python 整數

問題是Numpy 是用靜態型別語言 C 撰寫的,因此它不能有效地處理動態型別的純 Python 物件。Numpy 的策略是盡可能將此類物件轉換為 C 型別。在這種情況下,一個大問題是生成器提供的整數在理論上可能很大:Numpy 不知道這些值是否可以溢位一個np.int32甚至一個np.int64型別。因此,Numpy 首先檢測要使用的好型別,然后使用該型別計算結果。

這個轉換程序可能非常昂貴,而且似乎不需要這里,因為range(10_000_000). 然而,range(5_000_000_000)回傳與純Python整數相同的物件型別溢位 np.int32numpy的需要自動檢測這種情況下不回傳錯誤的結果。問題也是可以正確識別輸入型別(np.int32在我的機器上),這并不意味著輸出結果將是正確的,因為在計算和的程序中可能會出現溢位。可悲的是,我的機器就是這種情況。

Numpy 開發人員決定棄用這種用法并放入np.fromiter應該使用的檔案中np.fromiter有一個dtype必需的引數讓用戶定義要使用的好型別。

在實踐中檢查這種行為的一種方法是簡單地使用創建一個臨時串列:

tmp = list(range(10_000_000))

# Numpy implicitly convert the list in a Numpy array but 
# still automatically detect the input type to use
np.sum(tmp)

更快的實作如下:

tmp = list(range(10_000_000))

# The array is explicitly converted using a well-defined type and 
# thus there is no need to perform an automatic detection 
# (note that the result is still wrong since it does not fit in a np.int32)
tmp2 = np.array(tmp, dtype=np.int32)
result = np.sum(tmp2)

第一種情況在我的機器上需要 476 毫秒,而第二種情況需要 289 毫秒。請注意,這np.sum僅需要 4 毫秒。因此,大部分時間都花在將純 Python 整數物件轉換為內部 int32 型別(更具體地說是純 Python 整數的管理)上。list(range(10_000_000))也很昂貴,因為它需要 205 毫秒。這又是由于純 Python 整數開銷(即分配、釋放、參考計數、可變大小整數的增量、記憶體間接和動態型別導致的條件)以及generator開銷

sum(np.arange(N))很慢,因為它sum是一個處理 Numpy 定義物件的純 Python 函式。CPython 解釋器需要呼叫 Numpy 函式來執行基本的添加此外,Numpy 定義的整數物件仍然是 Python 物件,因此它們會受到參考計數、分配、釋放等的影響。更不用說 Numpy 和 CPython 在函式中添加了許多檢查,旨在最終將兩個本地數字相加。支持 Numpy 的即時編譯器(例如 Numba)可以解決此問題事實上,Numba 在我的機器上需要 23 毫秒來計算np.arange(10_000_000)(代碼仍然用 Python 撰寫)的總和,而 CPython 解釋器需要 556 毫秒。

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/316910.html

標籤:Python 麻木的 表现

上一篇:可以在每次迭代中使用更新變數向量化這個for回圈嗎?

下一篇:foldTree的分步評估

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more