在分析算法的内存消耗时,我感到惊讶的是,有时对于较小的输入,需要更多的内存。
这全部归结为pandas.unique()
的以下用法:
import numpy as np
import pandas as pd
import sys
N=int(sys.argv[1])
a=np.arange(N, dtype=np.int64)
b=pd.unique(a)
使用N=6*10^7
时,它需要3.7GB
峰值内存,但是使用N=8*10^7
“仅” 3GB
时。
扫描不同的输入大小会产生下图:
出于好奇和自我教育:如何解释N=5*10^7
,N=1.3*10^7
周围的违反直觉的行为(即,更多的内存用于更小的输入大小)?
以下是在Linux上生成内存消耗图表的脚本:
pandas_unique_test.py :
import numpy as np
import pandas as pd
import sys
N=int(sys.argv[1])
a=np.arange(N, dtype=np.int64)
b=pd.unique(a)
show_memory.py :
import sys
import matplotlib.pyplot as plt
ns=[]
mems=[]
for line in sys.stdin.readlines():
n,mem = map(int, line.strip().split(" "))
ns.append(n)
mems.append(mem)
plt.plot(ns, mems, label='peak-memory')
plt.xlabel('n')
plt.ylabel('peak memory in KB')
ymin, ymax = plt.ylim()
plt.ylim(0,ymax)
plt.legend()
plt.show()
run_perf_test.sh :
WRAPPER="/usr/bin/time -f%M" #peak memory in Kb
N=1000000
while [ $N -lt 100000000 ]
do
printf "$N "
$WRAPPER python pandas_unique_test.py $N
N=`expr $N + 1000000`
done
现在:
sh run_perf_tests.sh 2>&1 | python show_memory.py
答案 0 :(得分:7)
让我们看看...
pandas.unique
说它是“基于哈希表的唯一”。
它调用this function来获取数据的正确哈希表实现,即htable.Int64HashTable
。
哈希表初始化为size_hint
=值向量的长度。这意味着kh_resize_DTYPE(table, size_hint)
被呼叫。
定义(模板化)here in khash.h
的那些功能。
似乎为存储桶分配了(size_hint >> 5) * 4 + (size_hint) * 8 * 2
字节的内存(可能更多,也许更少,我可能不在这里)。
然后,调用HashTable.unique()
。
从quadruple开始,它分配一个空的Int64Vector
,它们看起来像128的大小一样。
然后遍历您的值,找出它们是否在哈希表中;如果不是,则将它们添加到哈希表和向量中。 (这是向量可能增长的地方;由于大小提示,哈希表不需要增长。)
最后,使NumPy ndarray
指向矢量。
所以,我认为您在某些阈值上看到向量大小增加了三倍(如果我的深夜数学站立的话,应该是
>>> [2 ** (2 * i - 1) for i in range(4, 20)]
[
128,
512,
2048,
8192,
32768,
131072,
524288,
2097152,
8388608,
33554432,
134217728,
536870912,
2147483648,
8589934592,
34359738368,
137438953472,
...,
]
希望这对事情有所启发:)
答案 1 :(得分:0)
@AKX答案解释了为什么内存消耗在跳转中增加,但没有解释,为什么它可能随着元素的增加而减少-这个答案填补了空白。
pandas
使用khash
映射来查找唯一元素。创建哈希映射后,数组is used as a hint中的元素数:
def unique(values):
...
table = htable(len(values))
...
但是,meaning of the hint是“映射中将有n个值”:
cdef class {{name}}HashTable(HashTable):
def __cinit__(self, int64_t size_hint=1):
self.table = kh_init_{{dtype}}()
if size_hint is not None:
size_hint = min(size_hint, _SIZE_HINT_LIMIT)
kh_resize_{{dtype}}(self.table, size_hint)
但是khash地图将其理解为we need at least n
buckets(并且我们不需要n
元素的位置)
SCOPE void kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets)
...
关于khash-map中存储桶数的两个重要实现细节:
会有什么后果?让我们看一下包含1023个元素的数组:
具有1025个元素的数组会发生什么?
每增加一倍数组大小,就会发生这种内存消耗模式。这就是我们观察到的。
说明以产生较小的效果:
np.resize
将没有提交的附加内存置零(至少在Linux上如此)。这是一个小实验,显示np.zeros(...)
不会提交内存,只会保留它:
import numpy as np
import psutil
process = psutil.Process()
old = process.memory_info().rss
a=np.zeros(10**8)
print("commited: ", process.memory_info().rss-old)
# commited: 0, i.e. nothign
a[0:100000] = 1.0
print("commited: ", process.memory_info().rss-old)
# commited: 2347008, more but not all
a[:] = 1.0
print("commited: ", process.memory_info().rss-old)
# commited: 799866880, i.e. all
NB:a=np.full(10**8, 0.0)
将直接提交内存。