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

創(chuàng)新互聯(lián)建站制作網(wǎng)站網(wǎng)頁找三站合一網(wǎng)站制作公司,專注于網(wǎng)頁設(shè)計(jì),成都做網(wǎng)站、網(wǎng)站建設(shè)、外貿(mào)營銷網(wǎng)站建設(shè),網(wǎng)站設(shè)計(jì),企業(yè)網(wǎng)站搭建,網(wǎng)站開發(fā),建網(wǎng)站業(yè)務(wù),680元做網(wǎng)站,已為上千余家服務(wù),創(chuàng)新互聯(lián)建站網(wǎng)站建設(shè)將一如既往的為我們的客戶提供最優(yōu)質(zhì)的網(wǎng)站建設(shè)、網(wǎng)絡(luò)營銷推廣服務(wù)!
但是其實(shí)有許多方法可以顯著優(yōu)化Python程序的內(nèi)存使用,這些方法可能在實(shí)際應(yīng)用中并沒有人注意,所以本文將重點(diǎn)介紹Python的內(nèi)置機(jī)制,掌握它們將大大提高Python編程技能。
首先在進(jìn)行內(nèi)存優(yōu)化之前,我們首先要查看內(nèi)存的使用情況。
分配了多少內(nèi)存?
有幾種方法可以在Python中獲取對象的大小??梢允褂胹ys.getsizeof()來獲取對象的確切大小,使用objgraph.show_refs()來可視化對象的結(jié)構(gòu),或者使用psutil.Process().memory_info()。RSS獲取當(dāng)前分配的所有內(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'))
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)存占用來查看實(shí)際的優(yōu)化結(jié)果。
__slots__
Python作為一種動(dòng)態(tài)類型語言,在面向?qū)ο蠓矫婢哂懈蟮撵`活性。在運(yùn)行時(shí)可以向Python類添加額外屬性和方法的能力。
例如,下面的代碼定義了一個(gè)名為Author的類。最初它有兩個(gè)屬性name和age。但是我們以后可以很容易地添加一個(gè)額外的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但是這種靈活性在底層浪費(fèi)了更多內(nèi)存。
因?yàn)镻ython中每個(gè)類的實(shí)例都維護(hù)一個(gè)特殊的字典(__dict__)來存儲實(shí)例變量。因?yàn)樽值涞牡讓踊诠1淼膶?shí)現(xiàn)所以消耗了大量的內(nèi)存。
在大多數(shù)情況下,我們不需要在運(yùn)行時(shí)更改實(shí)例的變量或方法,并且__dict__不會(也不應(yīng)該)在類定義后更改。所以Python為此提供了一個(gè)屬性:__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'白名單只定義了兩個(gè)有效的屬性name和age。由于屬性是固定的,Python不需要為它維護(hù)字典,只為__slots__中定義的屬性分配必要的內(nèi)存空間。
下面我們做一個(gè)簡單的比較:
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中列表的惰性求值版本。每當(dāng)調(diào)用next()方法時(shí)生成一個(gè)項(xiàng),而不是一次計(jì)算所有項(xiàng)。所以它們在處理大型數(shù)據(jù)集時(shí)非常節(jié)省內(nèi)存。
def number_generator():
for i in range(100):
yield i
numbers = number_generator()
print(numbers)
#
print(next(numbers))
# 0
print(next(numbers))
# 1 上面的代碼顯示了一個(gè)編寫和使用生成器的基本示例。關(guān)鍵字yield是生成器定義的核心。應(yīng)用它意味著只有在調(diào)用next()方法時(shí)才會產(chǎn)生項(xiàng)i。
讓我們比較一個(gè)生成器和一個(gè)列表,看看哪個(gè)更節(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)換成圓括號,它將成為生成器表達(dá)式。這是在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)化。
簡單地說,當(dāng)使用mmap技術(shù)對文件進(jìn)行內(nèi)存映射時(shí),它直接在當(dāng)前進(jìn)程的虛擬內(nèi)存空間中創(chuàng)建文件的映射,而不是將整個(gè)文件加載到內(nèi)存中,這節(jié)省了大量內(nèi)存。
Python已經(jīng)提供了用于使用此技術(shù)的內(nèi)置模塊,因此我們可以輕松地利用它,而無需考慮操作系統(tǒng)級別的實(shí)現(xiàn)。
以下是如何在Python中使用mmap進(jìn)行文件處理:
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()方法,然后使用標(biāo)準(zhǔn)文件方法甚至切片符號處理打開的對象。
選擇適當(dāng)?shù)臄?shù)據(jù)類型
開發(fā)人員應(yīng)仔細(xì)而精確地選擇數(shù)據(jù)類型。因?yàn)樵谀承┣闆r下,使用一種數(shù)據(jù)類型比使用另一種數(shù)據(jù)類型更節(jié)省內(nèi)存。
1、元組比列表更節(jié)省內(nèi)存
元組是不可變的(在創(chuàng)建后不能更改),它允許Python在內(nèi)存分配方面進(jìn)行優(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ù)或所有浮點(diǎn)數(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)語言。有許多強(qiáng)大的第三方模塊和工具提供更多的數(shù)據(jù)類型,如NumPy和Pandas。如果我們只需要一個(gè)簡單的一維數(shù)字?jǐn)?shù)組,而不需要NumPy提供的廣泛功能,那么Python的內(nèi)置數(shù)組是一個(gè)不錯(cuò)的選擇。但當(dāng)涉及到復(fù)雜的矩陣操作時(shí),使用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).如果有幾個(gè)值相同的小字符串,它們將被Python隱式地存儲并在內(nèi)存中并引用相同的對象。定義小字符串閾值數(shù)字是4096。
由于c和d的長度為4097,因此它們是內(nèi)存中的兩個(gè)對象而不是一個(gè)對象,不再隱式駐留字符串。所以當(dāng)執(zhí)行c = d時(shí),我們得到一個(gè)False。
駐留是一種優(yōu)化內(nèi)存使用的強(qiáng)大技術(shù)。如果我們想要顯式地使用它可以使用sys.intern()方法:
>>> import sys
>>> c = sys.intern('Y'*4097)
>>> d = sys.intern('Y'*4097)
>>> c is d
True 文章標(biāo)題:提高代碼效率的六個(gè)Python內(nèi)存優(yōu)化技巧
本文URL:http://m.5511xx.com/article/cdhihph.html


咨詢
建站咨詢
