自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

用 NumPy 中的視圖來節(jié)省內(nèi)存

開發(fā) 后端
如果您使用 Python 的 NumPy 庫(kù),通常是因?yàn)槟谔幚碚加么罅績(jī)?nèi)存的大型數(shù)組。為了減少內(nèi)存使用,您可能希望盡量減少不必要的重復(fù)項(xiàng)。

[[416569]]

 如果您使用 Python 的 NumPy 庫(kù),通常是因?yàn)槟谔幚碚加么罅績(jī)?nèi)存的大型數(shù)組。為了減少內(nèi)存使用,您可能希望盡量減少不必要的重復(fù)項(xiàng)。

NumPy 有一個(gè)內(nèi)置功能,可以在許多常見情況下透明地執(zhí)行此操作:內(nèi)存視圖。而且,此功能還可以防止數(shù)組被垃圾回收,從而導(dǎo)致更高的內(nèi)存使用率。在某些情況下,它可能會(huì)導(dǎo)致錯(cuò)誤,數(shù)據(jù)會(huì)以意想不到的方式發(fā)生變異。

為了避免這些問題,讓我們了解視圖的工作原理以及對(duì)代碼的影響。

預(yù)備知識(shí):Python 列表

在查看 NumPy 數(shù)組和視圖之前,讓我們考慮一個(gè)有點(diǎn)相似的數(shù)據(jù)結(jié)構(gòu):Python 列表。

Python 列表與 NumPy 數(shù)組一樣,是連續(xù)的內(nèi)存塊。當(dāng)你對(duì)一個(gè) Python 列表進(jìn)行切片時(shí),你會(huì)得到一個(gè)完全不同的列表,這意味著你正在分配一塊新的內(nèi)存: 

  1. >>> from psutil import Process  
  2. >>> Process().memory_info().rss  
  3. 12247040 
  4. >>> list1 = [None] * 1_000_000  
  5. >>> Process().memory_info().rss  
  6. 20463616  
  7. >>> list2 = list1[:500_000]  
  8. >>> Process().memory_info().rss  
  9. 24580096 

切片列表分配了更多內(nèi)存。由于第二個(gè)列表是一個(gè)獨(dú)立的副本,如果我們改變它,這不會(huì)影響第一個(gè)列表: 

  1. >>> list2[0] = "abc"  
  2. >>> print(list2[0])  
  3. abc  
  4. >>> print(list1[0])  
  5. None 

注意,復(fù)制到第二個(gè)列表中的數(shù)據(jù)是指向 Python 對(duì)象的指針,而不是對(duì)象本身的內(nèi)容。因此,即使列表本身不同,底層對(duì)象仍然在兩者之間進(jìn)行共享。

切片時(shí) NumPy 數(shù)組并不進(jìn)行復(fù)制

NumPy 數(shù)組的工作方式不同。因?yàn)榧僭O(shè)您可能正在處理非常大的數(shù)組,所以許多操作不會(huì)復(fù)制數(shù)組,它們只是讓您查看原始數(shù)組指向的同一連續(xù)內(nèi)存塊。

第一個(gè)結(jié)果是切片不會(huì)分配更多內(nèi)存,因?yàn)樗皇窃紨?shù)組的視圖: 

  1. >>> from psutil import Process  
  2. >>> import numpy as np  
  3. >>> arr = np.arange(0, 1_000_000)  
  4. >>> Process().memory_info().rss  
  5. 37810176  
  6. >>> view = arr[:500_000]  
  7. >>> Process().memory_info().rss  
  8. 37810176 

視圖對(duì)象看起來像一個(gè) 500,000 長(zhǎng)的 int64 數(shù)組,因此如果它是一個(gè)新數(shù)組,它將分配大約 4MB 的內(nèi)存。但它只是針對(duì)同一個(gè)原始數(shù)組的一個(gè)視圖,所以不需要額外的內(nèi)存。

從技術(shù)上來說,可能會(huì)為視圖對(duì)象本身分配一小部分內(nèi)存,但這可以忽略不計(jì),除非您有很多視圖對(duì)象。在這種情況下,RSS(常駐內(nèi)存)度量中沒有出現(xiàn)新內(nèi)存,因?yàn)?Python 預(yù)先分配了更大的內(nèi)存塊,然后用小的 Python 對(duì)象填充這些塊。

視圖導(dǎo)致內(nèi)存泄漏

使用視圖的后果之一是您可能會(huì)泄漏內(nèi)存,而不是節(jié)省內(nèi)存。這是因?yàn)橐晥D可以防止原始數(shù)組被垃圾回收 - 對(duì)整個(gè)數(shù)組來說。

假設(shè)您已經(jīng)決定只需要使用大數(shù)組的一小部分: 

  1. >>> import numpy as np  
  2. >>> from psutil import Process  
  3. >>> arr = np.arange(0, 100_000_000)  
  4. >>> Process().memory_info().rss  
  5. 830181376  
  6. >>> small_slice = arr[:10]  
  7. >>> del arr  
  8. >>> Process().memory_info().rss  
  9. 830111744 

如果這是一個(gè) Python 列表,刪除原始對(duì)象將釋放內(nèi)存。然而,在這種情況下,即使我們沒有對(duì)數(shù)組的直接引用,視圖仍然可以起作用,這意味著內(nèi)存沒有被釋放,即使我們只對(duì)其中的一小部分感興趣。

您實(shí)際上可以通過視圖訪問原始數(shù)組: 

  1. >>> small_slice  
  2. array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])  
  3. >>> small_slice.base 
  4. array([0, 1, 2, ..., 99999997, 99999998, 99999999]) 

結(jié)果,只有當(dāng)我們刪除所有視圖時(shí),原始數(shù)組的內(nèi)存才會(huì)被釋放: 

  1. >>> del small_slice  
  2. >>> Process().memory_info().rss  
  3. 29642752 

其他改變

使用視圖的另一個(gè)后果是修改視圖會(huì)改變?cè)紨?shù)組?;叵胍幌拢瑢?duì)于 Python 列表,修改切片結(jié)果不會(huì)修改原始列表,因?yàn)樾聦?duì)象是一個(gè)副本: 

  1. >>> l = [1, 2, 3]  
  2. >>> ll2 = l[:]  
  3. >>> l2[0] = 17  
  4. >>> l2  
  5. [17, 2, 3]  
  6. >>> l  
  7. [1, 2, 3] 

使用 NumPy 視圖后,改變視圖確實(shí)改變了原始對(duì)象,它們都指向同一個(gè)內(nèi)存地址: 

  1. >>> arr = np.array([1, 2, 3])  
  2. >>> view = arr[:]  
  3. >>> view[0] = 17  
  4. >>> view  
  5. array([17,  2,  3])  
  6. >>> arr  
  7. array([17,  2,  3]) 

這個(gè)結(jié)果不是我們想要的!

由于某些 NumPy API 可能會(huì)根據(jù)情況返回視圖或副本,因此更有可能發(fā)生意外變化。例如,某些切片結(jié)果可能不是視圖: 

  1. >>> arr = np.array([1, 2, 3])  
  2. >>> arrarr2 = arr[:]  
  3. >>> arr2.base is arr  
  4. True  
  5. >>> arrarr3 = arr[[True, False, True]]  
  6. >>> arr3.base is arr  
  7. False 

改變 arr2 也會(huì)改變 arr,但改變 arr3 不會(huì)改變 arr。

使用 copy() 進(jìn)行顯式復(fù)制

當(dāng)您不想引用原始內(nèi)存時(shí),顯式復(fù)制允許您創(chuàng)建一個(gè)新數(shù)組。這對(duì)于防止改變很有用,并且在您不想將原始數(shù)組保留在內(nèi)存中的情況下也很有用: 

  1. >>> arr = np.arange(0, 100_000_000)  
  2. >>> Process().memory_info().rss  
  3. 829464576  
  4. >>> small_slice = arr[:10].copy()  
  5. >>> del arr  
  6. >>> Process().memory_info().rss  
  7. 29700096  
  8. >>> print(small_slice.base)  
  9. None 

在這種情況下,刪除 arr 釋放了內(nèi)存,因?yàn)?small_slice 是副本,而不是視圖。

要點(diǎn):高效安全地使用視圖

鑒于各種 NumPy API 會(huì)自動(dòng)返回視圖,您需要在編寫代碼時(shí)考慮它們:

•在文檔中注意 API 是否會(huì)返回視圖、副本或兩者。

•如果您想從內(nèi)存中清除一個(gè)大數(shù)組,請(qǐng)確保不僅沒有直接引用它,而且沒有引用它的視圖。

•如果你要改變一個(gè)數(shù)組,確保它不會(huì)因?yàn)樗鼘?shí)際上是一個(gè)視圖而意外改變其他一些數(shù)組。

•如果您不需要視圖,請(qǐng)使用 copy() 方法。 

 

責(zé)任編輯:龐桂玉 來源: Python中文社區(qū)
相關(guān)推薦

2021-08-10 09:04:43

內(nèi)存視圖 NumPy

2011-04-13 09:13:02

Java內(nèi)存

2011-04-06 14:20:50

Java編程

2020-02-25 17:40:52

Python循環(huán)內(nèi)存

2017-09-30 12:53:28

內(nèi)存

2017-10-09 16:27:27

Glide內(nèi)存加載庫(kù)

2023-03-06 08:46:12

2024-12-17 08:04:04

2022-04-02 15:56:43

神經(jīng)網(wǎng)絡(luò)人工智能技術(shù)

2018-02-08 09:37:27

Pandas大數(shù)據(jù)Spark

2022-01-08 19:00:09

NumPyPython編程語(yǔ)言

2023-03-03 12:37:50

JavaJVM內(nèi)存溢出

2023-03-07 15:55:31

谷歌Chrome瀏覽器

2021-09-26 08:42:51

RedisGeo 類型數(shù)據(jù)類型

2021-09-29 08:00:00

Kubernetes集群容器

2021-12-17 08:27:55

NumpyPython 機(jī)器學(xué)習(xí)

2010-05-26 14:16:45

替代MySQL

2019-11-11 13:40:45

Python 開發(fā)編程語(yǔ)言

2018-01-17 17:11:13

OpenAI開源工具包

2009-11-11 16:13:19

路由器協(xié)議
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)