首页 > 编程知识 正文

python制作短视频(lambda python)

时间:2023-05-06 13:49:45 阅读:103377 作者:4207

原文地址

作者简介

不愿透露真实姓名的网易游戏运营与基础设施部高级运维工程师渣飞。

多年来,他半夜在票圈放毒。他是网易游戏的高级运维工程师。他对代码性能和系统原理非常感兴趣。三人行必有我师。现在他负责监控相关业务发展。

前言

Python的列表是一个非常灵活的数组,长度可以随意调整。因为这种便利,我们不得不修改阵列以满足我们的需求。与插入、弹出等相比,追加用法更为常见。

像这样使用它:

test=test . append(1)test . append({ 2 })test . append([3])print test #输出[1,set([2]),[3]]也是这样使用的:

测试=范围(4)内的I :测试。追加(I)打印测试#输出[0,1,2,3]非常愉快和满意。

但实际上,每当遇到可以动态修改数据长度的场景,我们都应该立即做出反应,那就是内存管理的问题。

如果同时满足运营效率和便利性,那将是一大福音。

然而,当上帝为你打开一扇窗时,他一定关上了一扇门!

吝啬的初始化

深受预分配知识的影响,我们也认为在列表的初始化中分配了一定的长度,否则每次申请内存都会“低”很多。

然后其实榜单真的这么“低”:

导入测试=test _ 1=[1]打印系统。getsizeof(测试)打印系统。getsizeof (test _ 1)-sys。getsizeof (test) #输出72 #空列表内存大小,它也是列表对象的总大小。8 #代表成员的增加,以及列表的增加大小(这个大小是对象指针的长度)。我们的猜测是,在定义了列表之后,会提前分配一定大小的池来进行数据的插拔,避免轻易申请内存。

然而,在上面的实验中,可以看到成员列表的长度只比空列表的长度(对象指针的大小)大8字节。如果真的存在这样一个预分配的池,那么在预分配的数量内添加成员应该保持两者的内存大小不变。

所以我们可以猜测这个列表没有这样一个预分配的内存池。这里需要一把真正的锤子:

Py object * PyList _ New(Py _ ssize _ t size){ PyList object * op;size _ t字节;if(size 0){ PyErr _ BadInternal call;返回;} /*检查没有实际溢出的溢出,*这可能导致编译器优化输出*/如果((SIZE _ t)SIZE PY _ SIZE _ MAX/sizeof(PyObject *)返回PyErr _ NoMemory//列表缓存if(num free){ num free-;op=free _ list[num free];_ Py _ new REference((pyoObject *)op);} else { op=PyObject _ GC _ New(PyListObject,PyList _ Type);if (op==)返回;}//列表成员的内存应用nbytes=size * sizeof(PyObject *);if(size=0)op-ob _ item=;else { op-ob _ item=(PyObject * *)PyMem _ MALLOC(n字节);if(op-ob _ item==){ Py _ DERUF(op);返回PyErr _ NoMemory} memset(op-ob_item,0,n字节);} Py _ SIZE(op)=SiZe;op-allocated=大小;_ pyoObject _ GC _ TRACK(op);return(pyObject *)op;}当我们执行test=[1]时,我们实际上只做了两件事:

根据成员数量,构造相应长度的空列表;(以上代码)

把这些成员一个个塞进去;

有些童鞋可能会想,在封堵成员的步骤中,可能会触发什么机制让它变大?

不幸的是,因为初始化方法是PyList_SET_ITEM,所以没有触发机制,只有简单的数组成员赋值:

#定义pylist _ set _ item (op,I,v)((pylist object *)(op)-ob _ item[I]=(v))所以整个列表的初始化真的是

木有预分配的内存池,直接按需申请,一个萝卜一个坑,实在得狠;

可变长的关键

初始化过程是这样还可以理解,如果运行中还这样的话,那就有点说不过去了。

试想下,在文章开头用 append的例子中,如果每append一个元素就申请一次内存,那么list可能要被吐槽到怀疑人生了, 所以很明显,在对于内存的申请,它还是有自己的套路的。

在 list里面,不管是insert、pop还是append,都会遇到list_resize,故名思义,这个函数就是用来调整list对象的内存占用的。

static int list_resize(PyListObject *self, Py_ssize_t newsize) { PyObject **items; size_t new_allocated; Py_ssize_t allocated = self->allocated; /* Bypass realloc when a previous overallocation is large enough to accommodate the newsize. If the newsize falls lower than half the allocated size, then proceed with the realloc to shrink the list. */ if (allocated >= newsize && newsize >= (allocated >> 1)) { assert(self->ob_item != || newsize == 0); Py_SIZE(self) = newsize; return 0; } /* This over-allocates proportional to the list size, making room * for additional growth. The over-allocation is mild, but is * enough to give linear-time amortized behavior over a long * sequence of appends in the presence of a poorly-performing * system realloc. * The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... */ # 确定新扩展之后的占坑数 new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6); /* check for integer overflow */ if (new_allocated > PY_SIZE_MAX - newsize) { PyErr_NoMemory; return -1; } else { new_allocated += newsize; } if (newsize == 0) new_allocated = 0; # 申请内存 items = self->ob_item; if (new_allocated <= (PY_SIZE_MAX / sizeof(PyObject *))) PyMem_RESIZE(items, PyObject *, new_allocated); else items = ; if (items == ) { PyErr_NoMemory; return -1; } self->ob_item = items; Py_SIZE(self) = newsize; self->allocated = new_allocated; return 0; }

在上面的代码中,频繁看到两个名词:newsize和new_allocated, 这里需要解释下,newsize并不是增加/减少的个数,而是增加/减少之后的成员总数目。比方说:

a = [1, 2, 3] a.append(1)

上面的 append触发list_resize时,newsize是 3 + 1, 而不是 1;这边比较重要,因为在pop这类减少列表成员时候,就是传入缩减后的总数目。

在 list 的结构定义中,关于长度的定义有两个,分别是 ob_size(实际的成员数),allocated(总成员数)

它们之间的关系就是:

0 <= ob_size <= allocated len(list) == ob_size

所以 new_allocated就很好理解了,这个就是新的总坑数。

当名词含义理解得差不多时,我们就能顺藤摸瓜知道一个列表在list_resize之后,大小会变成怎样?

方法其实从上面注释和代码都说得很明白了,这里再简单整理下:

先确定一个基数:new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);

判断下 new_allocated + newsize有没有超过PY_SIZE_MAX, 如果超过了,直接报错;

最终确定新的总坑数是:new_allocated + newsize, 如果newsize是 0, 那么总坑数直接为 0 ;

下面演示下:

#coding: utf8 import sys test = raw_size = sys.getsizeof(test) test.append(1) print "1 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "2 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "3 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "4 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "5 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size) test.append(1) print "6 次 append 减去空列表的内存大小:%s " % (sys.getsizeof(test) - raw_size)# 输出结果 1 次 append 减去空列表的内存大小:32 2 次 append 减去空列表的内存大小:32 3 次 append 减去空列表的内存大小:32 4 次 append 减去空列表的内存大小:32 5 次 append 减去空列表的内存大小:64 6 次 append 减去空列表的内存大小:64

开始简单的代入法一步步算:

其中:

new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6) + newsize (因为下面的 newsize > 0)

当原allocated >= newsize 并且 newsize >= 原allocated / 2 时,不改变 allocated 不申请内存直接返回

第 n 次 append列表原长度新增成员数原 allocatednewsizenew_allocated10100 + 1 = 13 + 1 = 421141 + 1 = 2无需改变32142 + 1 = 3无需改变43143 + 1 = 4无需改变54144 + 1 = 53 + 5 = 865185 + 1 = 6无需改变

通过上面的表格,应该比较清楚看到什么时候会触发改变 allocated,并且当触发时它们是如何计算的。为什么我们需要这样关注allocated?理由很简单,因为这个值决定了整个 list 的动态内存的占用大小;

扩容是这样,缩容也是照猫画虎。反正都是算出新的 allocated, 然后由 PyMem_RESIZE来处理。

#define PyMem_REALLOC(p, n) ((size_t)(n) > (size_t)PY_SSIZE_T_MAX ? : realloc((p), (n) ? (n) : 1)) #define PyMem_RESIZE(p, type, n) ( (p) = ((size_t)(n) > PY_SSIZE_T_MAX / sizeof(type)) ? : (type *) PyMem_REALLOC((p), (n) * sizeof(type))

基本上,就是判断是否超过最大数,否则的话就是和 C resize 函数近似了,以下摘抄了一段关于该函数的描述:

多说几句

综上所述,在一些明确列表成员或者简单处理再塞入列表的情况下,我们不应该再用下面的方式:

test = for i in xrange(4): test.append(i) print test

而是应该用列表推导式:test = [i for i in xrange(4)]。

为什么推荐列表推导呢?显而易见的效果就有:

简练、清晰;

用多少就申请多少,不会因为 append触发PyMem_RESIZE申请过多内存;容易造成内存浪费;

相比 for i in xxx,列表推导方式直接增加元素,少了一些函数调用,如:SETUP_LOOP、CALL_FUNCTION等;

但是上面的推荐肯定也是在某些前提条件下才合适咯:

真的只是为了得到一个列表;

循环体内逻辑简单,没有太复杂的处理、判断、调用等等;

PS: 切记勿为了使用列表推导而使用,合理使用才是科学之道;

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。