<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 3519字,需瀏覽 8分鐘

           ·

          2021-08-11 11:58

          如果您使用 Python 的 NumPy 庫,通常是因?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)存:

          >>> from psutil import Process>>> Process().memory_info().rss12247040>>> list1 = [None] * 1_000_000>>> Process().memory_info().rss20463616>>> list2 = list1[:500_000]>>> Process().memory_info().rss24580096

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

          >>> list2[0] = "abc">>> print(list2[0])abc>>> print(list1[0])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ù)組的視圖:

          >>> from psutil import Process>>> import numpy as np>>> arr = np.arange(0, 1_000_000)>>> Process().memory_info().rss37810176>>> view = arr[:500_000]>>> Process().memory_info().rss37810176

          視圖對(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ù)組的一小部分:

          >>> import numpy as np>>> from psutil import Process>>> arr = np.arange(0, 100_000_000)>>> Process().memory_info().rss830181376>>> small_slice = arr[:10]>>> del arr>>> Process().memory_info().rss830111744

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

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

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

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

          >>> del small_slice>>> Process().memory_info().rss29642752

          其他改變

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

          >>> l = [1, 2, 3]>>> l2 = l[:]>>> l2[0] = 17>>> l2[17, 2, 3]>>> l[1, 2, 3]

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

          >>> arr = np.array([1, 2, 3])>>> view = arr[:]>>> view[0] = 17>>> viewarray([17,  2,  3])>>> arrarray([17,  2,  3])

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

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

          >>> arr = np.array([1, 2, 3])>>> arr2 = arr[:]>>> arr2.base is arrTrue>>> arr3 = arr[[True, False, True]]>>> arr3.base is arrFalse

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

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

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

          >>> arr = np.arange(0, 100_000_000)>>> Process().memory_info().rss829464576>>> small_slice = arr[:10].copy()>>> del arr>>> Process().memory_info().rss29700096>>> print(small_slice.base)None

          在這種情況下,刪除 arr 釋放了內(nèi)存,因?yàn)?nbsp;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() 方法。





          點(diǎn)擊下方閱讀原文加入社區(qū)會(huì)員

          瀏覽 56
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  韩日一级无码 | 日本亚洲天堂 | 免费成人做爱视频 | 天天插日日干 | 四虎影库久久久 |