1. 提高代碼效率的6個Python內(nèi)存優(yōu)化技巧

        共 7554字,需瀏覽 16分鐘

         ·

        2024-04-01 02:30

        來源:Deephub IMBA

        當項目變得越來越大時,有效地管理計算資源是一個不可避免的需求。Python與C或c++等低級語言相比,似乎不夠節(jié)省內(nèi)存。

        但是其實有許多方法可以顯著優(yōu)化Python程序的內(nèi)存使用,這些方法可能在實際應(yīng)用中并沒有人注意,所以本文將重點介紹Python的內(nèi)置機制,掌握它們將大大提高Python編程技能。

        59ae6f5534085dfc5719aade10f7c12d.webp

        首先在進行內(nèi)存優(yōu)化之前,我們首先要查看內(nèi)存的使用情況

        分配了多少內(nèi)存?

        有幾種方法可以在Python中獲取對象的大小??梢允褂胹ys.getsizeof()來獲取對象的確切大小,使用objgraph.show_refs()來可視化對象的結(jié)構(gòu),或者使用psutil.Process().memory_info()。RSS獲取當前分配的所有內(nèi)存。

         >>> import numpy as np
         >>> import sys
         >>> import objgraph
         >>> import psutil
         >>> import pandas as pd
         
         
         >>> ob = np.ones((1024, 1024, 1024, 3), dtype=np.uint8)
         
         ### Check object 'ob' size
         >>> sys.getsizeof(ob) / (1024 * 1024)
         3072.0001373291016
         
         ### Check current memory usage of whole process (include ob and installed packages, ...)
         >>> psutil.Process().memory_info().rss / (1024 * 1024)
         3234.19140625
         
         ### Check structure of 'ob' (Useful for class object)
         >>> objgraph.show_refs([ob], filename='sample-graph.png')
         
         ### Check memory for pandas.DataFrame
         >>> from sklearn.datasets import load_boston
         >>> data = load_boston()
         >>> data = pd.DataFrame(data['data'])
         >>> print(data.info(verbose=False, memory_usage='deep'))
         <class 'pandas.core.frame.DataFrame'>
         RangeIndex: 506 entries, 0 to 505
         Columns: 13 entries, 0 to 12
         dtypes: float64(13)
         memory usage: 51.5 KB
           
         ### Check memory for pandas.Series
         >>> data[0].memory_usage(deep=True)   # deep=True to include all the memory used by underlying parts that construct the pd.Series
         4176

        這樣我們才能根據(jù)對象的內(nèi)存占用來查看實際的優(yōu)化結(jié)果

        __slots__

        Python作為一種動態(tài)類型語言,在面向?qū)ο蠓矫婢哂懈蟮撵`活性。在運行時可以向Python類添加額外屬性和方法的能力。

        例如,下面的代碼定義了一個名為Author的類。最初它有兩個屬性name和age。但是我們以后可以很容易地添加一個額外的job:

         class Author:
            def __init__(self, name, age):
                self.name = name
                self.age = age
         
         
         me = Author('Yang Zhou', 30)
         me.job = 'Software Engineer'
         print(me.job)
         # Software Engineer

        但是這種靈活性在底層浪費了更多內(nèi)存。

        因為Python中每個類的實例都維護一個特殊的字典( _ _ dict _ _ )來存儲實例變量。因為字典的底層基于哈希表的實現(xiàn)所以消耗了大量的內(nèi)存。

        在大多數(shù)情況下,我們不需要在運行時更改實例的變量或方法,并且 _ _ dict _ _ 不會(也不應(yīng)該)在類定義后更改。所以Python為此提供了一個屬性: _ _ slots _ _ 。

        它通過指定類的所有有效屬性的名稱來作為白名單:

         class Author:
            __slots__ = ('name', 'age')
         
            def __init__(self, name, age):
                self.name = name
                self.age = age
         
         
         me = Author('Yang Zhou', 30)
         me.job = 'Software Engineer'
         print(me.job)
         # AttributeError: 'Author' object has no attribute 'job'

        白名單只定義了兩個有效的屬性name和age。由于屬性是固定的,Python不需要為它維護字典,只為 _ _ slots _ _ 中定義的屬性分配必要的內(nèi)存空間。

        下面我們做一個簡單的比較:

         import sys
         
         
         class Author:
            def __init__(self, name, age):
                self.name = name
                self.age = age
         
         
         class AuthorWithSlots:
            __slots__ = ['name', 'age']
         
            def __init__(self, name, age):
                self.name = name
                self.age = age
         
         
         # Creating instances
         me = Author('Yang', 30)
         me_with_slots = AuthorWithSlots('Yang', 30)
         
         # Comparing memory usage
         memory_without_slots = sys.getsizeof(me) + sys.getsizeof(me.__dict__)
         memory_with_slots = sys.getsizeof(me_with_slots) # __slots__ classes don't have __dict__
         
         print(memory_without_slots, memory_with_slots)
         # 152 48
         print(me.__dict__)
         # {'name': 'Yang', 'age': 30}
         print(me_with_slots.__dict__)
         # AttributeError: 'AuthorWithSlots' object has no attribute '__dict__'

        可以看到 152 和 48 明顯節(jié)省了內(nèi)存。

        Generators

        生成器是Python中列表的惰性求值版本。每當調(diào)用next()方法時生成一個項,而不是一次計算所有項。所以它們在處理大型數(shù)據(jù)集時非常節(jié)省內(nèi)存。

         def number_generator():
            for i in range(100):
                yield i
         
         numbers = number_generator()
         print(numbers)
         # <generator object number_generator at 0x104a57e40>
         print(next(numbers))
         # 0
         print(next(numbers))
         # 1

        上面的代碼顯示了一個編寫和使用生成器的基本示例。關(guān)鍵字yield是生成器定義的核心。應(yīng)用它意味著只有在調(diào)用next()方法時才會產(chǎn)生項i。

        讓我們比較一個生成器和一個列表,看看哪個更節(jié)省內(nèi)存:

         mport sys
         
         numbers = []
         for i in range(100):
            numbers.append(i)
         
         def number_generator():
            for i in range(100):
                yield i
         
         numbers_generator = number_generator()
         print(sys.getsizeof(numbers_generator))
         # 112
         print(sys.getsizeof(numbers))
         # 920

        可以看到使用生成器可以顯著節(jié)省內(nèi)存使用。如果我們將列表推導(dǎo)式的方括號轉(zhuǎn)換成圓括號,它將成為生成器表達式。這是在Python中定義生成器的更簡單的方法:

         import sys
         
         numbers = [i for i in range(100)]
         numbers_generator = (i for i in range(100))
         
         print(sys.getsizeof(numbers_generator))
         # 112
         print(sys.getsizeof(numbers))
         # 920

        利用內(nèi)存映射文件支持大文件處理

        內(nèi)存映射文件I/O,簡稱“mmap”,是一種操作系統(tǒng)級優(yōu)化。

        簡單地說,當使用mmap技術(shù)對文件進行內(nèi)存映射時,它直接在當前進程的虛擬內(nèi)存空間中創(chuàng)建文件的映射,而不是將整個文件加載到內(nèi)存中,這節(jié)省了大量內(nèi)存。

        Python已經(jīng)提供了用于使用此技術(shù)的內(nèi)置模塊,因此我們可以輕松地利用它,而無需考慮操作系統(tǒng)級別的實現(xiàn)。

        以下是如何在Python中使用mmap進行文件處理:

         import mmap
         
         
         with open('test.txt', "r+b") as f:
            # memory-map the file, size 0 means whole file
            with mmap.mmap(f.fileno(), 0) as mm:
                # read content via standard file methods
                print(mm.read())
                # read content via slice notation
                snippet = mm[0:10]
                print(snippet.decode('utf-8'))

        Python使內(nèi)存映射文件I/O技術(shù)的使用變得方便。我們所需要做的只是應(yīng)用mmap.mmap()方法,然后使用標準文件方法甚至切片符號處理打開的對象。

        選擇適當?shù)臄?shù)據(jù)類型

        開發(fā)人員應(yīng)仔細而精確地選擇數(shù)據(jù)類型。因為在某些情況下,使用一種數(shù)據(jù)類型比使用另一種數(shù)據(jù)類型更節(jié)省內(nèi)存。

        1、元組比列表更節(jié)省內(nèi)存

        元組是不可變的(在創(chuàng)建后不能更改),它允許Python在內(nèi)存分配方面進行優(yōu)化。列表是可變的,因此需要額外的空間來容納潛在的修改。

         import sys
         
         my_tuple = (1, 2, 3, 4, 5)
         my_list = [1, 2, 3, 4, 5]
         
         print(sys.getsizeof(my_tuple))
         # 80
         print(sys.getsizeof(my_list))
         # 120

        元組my_tuple比列表使用更少的內(nèi)存,如果創(chuàng)建后不需要更改數(shù)據(jù),我們應(yīng)該選擇元組而不是列表。

        2、數(shù)組比列表更節(jié)省內(nèi)存

        Python中的數(shù)組要求元素具有相同的數(shù)據(jù)類型(例如,所有整數(shù)或所有浮點數(shù)),但列表可以存儲不同類型的對象,這不可避免地需要更多的內(nèi)存。如果列表的元素都是相同類型,使用數(shù)組會更節(jié)省內(nèi)存:

         import sys
         import array
         
         my_list = [i for i in range(1000)]
         
         my_array = array.array('i', [i for i in range(1000)])
         
         print(sys.getsizeof(my_list))  
         # 8856
         print(sys.getsizeof(my_array))
         # 4064

        另外:Python是數(shù)據(jù)科學(xué)的主導(dǎo)語言。有許多強大的第三方模塊和工具提供更多的數(shù)據(jù)類型,如NumPy和Pandas。如果我們只需要一個簡單的一維數(shù)字數(shù)組,而不需要NumPy提供的廣泛功能,那么Python的內(nèi)置數(shù)組是一個不錯的選擇。但當涉及到復(fù)雜的矩陣操作時,使用NumPy提供的數(shù)組是所有數(shù)據(jù)科學(xué)家的首選,也可能是最佳選擇。

        字符串駐留

        看看下面的代碼:

         >>> a = 'Y'*4096
         >>> b = 'Y'*4096
         >>> a is b
         True
         >>> c = 'Y'*4097
         >>> d = 'Y'*4097
         >>> c is d
         False

        為什么a是b是真,而c是d是假呢?

        這在Python中被稱作字符串駐留(string interning).如果有幾個值相同的小字符串,它們將被Python隱式地存儲并在內(nèi)存中并引用相同的對象。定義小字符串閾值數(shù)字是4096。

        由于c和d的長度為4097,因此它們是內(nèi)存中的兩個對象而不是一個對象,不再隱式駐留字符串。所以當執(zhí)行c = d時,我們得到一個False。

        駐留是一種優(yōu)化內(nèi)存使用的強大技術(shù)。如果我們想要顯式地使用它可以使用sys.intern()方法:

         >>> import sys
         >>> c = sys.intern('Y'*4097)
         >>> d = sys.intern('Y'*4097)
         >>> c is d
         True


        作者:Yang Zhou

            

        加入知識星球 【我們談?wù)摂?shù)據(jù)科學(xué)】

        600+ 小伙伴一起學(xué)習(xí)!





        瀏覽 33
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 日韩欧美黄片 | 国产精品啪啪啪 | 欧美精品无码久久久精品酒店 | 狂操视频 | 亚洲色欲av |