翻译 | 更快的Python(二)

更快的 Python(Python Faster Way)使用代码示例来说明如何书写 Python 代码能带来更高的性能。本文对代码进行了讲解,从性能和可读性等角度来选择出最适合的写法。 例子 11:字符串连接 最差/最优时间比:1.15 使用建议:一次性连接多个(3 个以上)的字符串的时候,使用 join,其他情况使用加号或 f-string。 说明:又是一个字符串连接的问题,不过这个例子举的不好,join 适用的场景是一次连接多个字符串,会比加号连接多个字符串要快很多(加号相当于一个一个连接)。 例子 12:数字的格式化 最差/最优时间比:1.29 使用建议:需要复杂格式,推荐使用 format 方法;将数字转换为字符串,直接使用 str 方法。 说明:将数字转为字符串,使用 str 方法要快于 format 方法,因为 format 方法支持在转换过程中增加规则,例如将数字转为货币形式(每三位加一个逗号分隔符)。 例子 13:获取内置列表类型的长度 最差/最优时间比:1.20 使用建议:使用 len()方法。 说明:当调用 len()方法时,系统实际上是调用了对象内置的len方法,从这个层面理解,直接调用len应该比 len()方法更快。但是当 len()内置的列表方法时,Python 解释器做了优化,直接返回了列表对象中存储长度信息的变量,并不会调用len。 例子 14:整数类型的运算 最差/最优时间比:2.63 使用建议:不要直接调用add等魔术方法。 说明:对于整数类型,调用魔术方法完成运算的速度远远慢于直接使用运算符,使用运算符时,Python 解释器直接调用 C 实现的 operaotr 包中的运算方法,所以速度很快;而使用调用魔术方法,在 Python 层面多出了调用add等魔术方法的额外操作。 例子 15:自定义类型的运算符重载 最差/最优时间比:1.06 使用建议:不要直接调用add等魔术方法。 说明:对于重载了运算符的对象,没有对应的 C 实现运算方法,所以直接直接调用魔术方法速度会更快。 例子 16:对 range 结果求和 ...

十月 25, 2018 · 1 分钟 · Zhiya

Go的栈空间管理

栈空间管理的基本逻辑 go 语言通过 goroutine 提供了并发编程支持,goroutine 是 go 运行库的功能,而不是操作系统线程实现的,goroutine 可以被理解成一个用户态的线程。 既然 goroutine 是由 go 运行库管理的,那么 go 运行库也需要为每个 goroutine 创建并管理相应的栈空间,为每个 goroutine 分配的栈空间不能太大,goroutine 开多时会浪费大量空间,也不能太小,会导致栈溢出。go 语言选择栈的栈空间管理的方式是,一开始给一个比较小的空间,随着需要自动增长。当 goroutine 不需要那么大的空间时,栈空间也要自动缩小。 分段栈 Segment Stacks 在 go 1.3 之前,go 使用分段栈。 分段栈实现了一种不连续但是可以持续增长的栈,开始时,栈只有一个段,当需要更多的栈空间时,会分配一个新的段,和上一个栈双向链接。这样,一个栈就是由多个双向链接的段所组成的。当新分配的段使用完毕后,新段会被释放掉。 分段栈实现了栈的按需收缩,在增加新分段时也不需要对原有分段中的数据进行拷贝,使得 goroutine 的使用代价非常低廉。 分段栈的好处是可以按需增长,空间利用率比较高,然而分段栈在某些情况下也存在一定的瑕疵。当一个段即将用尽,这时使用 for 循环执行一个比较耗空间的函数,会导致函数执行时 goroutine 进行段的分配,而执行完成返回时,进行段的销毁,这样就会导致在循环中出现多次栈的扩容和收缩,造成很大的性能损失,这种情况被称作栈分裂(Stack Split)。 连续栈 Contiguous Stacks go 1.3 推出了连续栈,连续栈使用了另外一种策略,不再把栈分成一段一段的,当栈空间不够时,直接 new 一个 2 倍大的栈空间,并将原先栈空间中的数据拷贝到新的栈空间中,而后销毁旧栈。这样当出现栈空间触及边界时,不会产生栈分裂的情况。 继续假设当前栈空间即将用尽,并且需要在 for 循环中执行一个比较消耗空间的函数。当该函数执行时,栈空间发生了扩容,变成原先 2 倍大小,函数执行完成一次后,栈空间的使用量缩小回执行前的大小,但是栈空间的使用量并没有小于栈大小的 1/4,不会触发栈收缩,所以在整个 for 循环执行过程中,不会反复触发栈空间的收缩扩容。 总结 相比于分段栈,连续栈避免了某些场景下栈空间的的频繁伸缩。有一点需要注意的是,连续栈的收缩也是需要重新申请一段空间(原先的 1/2 大小),并进行栈拷贝操作的。

十月 11, 2018 · 1 分钟 · Zhiya

翻译 | 更快的Python(一)

更快的 Python(Python Faster Way)使用代码示例来说明如何书写 Python 代码能带来更高的性能。本文对代码进行了讲解,从性能和可读性等角度来选择出最适合的写法。 例子 1:字符串格式化 最差/最优时间比:1.95 使用建议:Python 3.7 或以上推荐使用 f-string,其他版本推荐使用 format 方法。 说明:字符串格式化是代码中最常遇到的情况,虽然在连接少量字符串的情景中,使用+号的性能最优,但是使用+号的代码可读性最差。如果使用 Python 3.7 或优以上版本,可以使用 f-string 来解决这个问题,f-string 的性能比 format 方法和%操作符的性能都要高,可读性也比+号好。 例子 2:字典的初始化 最差/最优时间比:1.83 使用建议:使用字面量初始化字典(以及其他集合类型)。 说明:Python 中初始化集合类型时使用字面量的方式,解释器会直接调用 BUILD_MAP 等字节码来创建,如果用构造函数的方式来创建,则需要先查询构造方法,再执行构造方法。使用字面量初始化,Python 代码也更简洁。 例子 3:内置排序方法 最差/最优时间比:1.26 使用建议:根据是否需要修改原始值来决定使用哪个方法。 说明:sorted 和 list.sort 方法是 Python 中内置的排序方法,sorted 方法不会修改原始值,list.sort 方法在原始值上直接排序,会修改原始值。比较这两个方法的性能差异,意义不大。 例子 4:初始化多个变量 最差/最优时间比:1.01 使用建议:推荐使用第二种。 说明:从字节码中可以看出两种方式出了执行顺序之外,基本一致,所以性能上也非常接近。 例子 5:多个变量的比较 最差/最优时间比:1.11 使用建议:推荐使用第二种。 说明:使用第一种方法能带来一定的性能提升,但是提升有限,在实际情况中也很少出现多个变量连续比较大小的情况,并且第一种方法非常不 Pythonic,所以推荐使用第二种。 例子 6:if true 的条件判断 最差/最优时间比:1.17 使用建议:推荐使用第一种。 说明:从字节码上看,第一种方法的性能最高,并且语法上也更加简洁。 例子 7:if false 的条件判断 ...

十月 8, 2018 · 1 分钟 · Zhiya

Go语言中defer的一些坑

defer 语句是 Go 中一个非常有用的特性,可以将一个方法延迟到包裹该方法的方法返回时执行,在实际应用中,defer 语句可以充当其他语言中 try…catch…的角色,也可以用来处理关闭文件句柄等收尾操作。 defer 触发时机 A “defer” statement invokes a function whose execution is deferred to the moment the surrounding function returns, either because the surrounding function executed a return statement, reached the end of its function body, or because the corresponding goroutine is panicking. Go 官方文档中对 defer 的执行时机做了阐述,分别是。 包裹 defer 的函数返回时 包裹 defer 的函数执行到末尾时 所在的 goroutine 发生 panic 时 defer 执行顺序 当一个方法中有多个 defer 时, defer 会将要延迟执行的方法“压栈”,当 defer 被触发时,将所有“压栈”的方法“出栈”并执行。所以 defer 的执行顺序是 LIFO 的。 ...

九月 14, 2018 · 2 分钟 · Zhiya

用Python写算法 | 蓄水池算法实现随机抽样

现在有一组数,不知道这组数的总量有多少,请描述一种算法能够在这组数据中随机抽取 k 个数,使得每个数被取出来的概率相等。 如果这组数有 n 个,那么每个数字取到的概率就是 k/n,但是这个问题的难点在于不知道这组数的总数,也就是不知道 n,那么该怎么计算每个数取到的概率呢? 蓄水池算法 游泳池(蓄水池)大家都不陌生,有些游泳池中的水是活的,有入水管也有出水管,那么和泳池体积相当的水流过之后,是不是泳池中所有的水都会被替换呢?当然不是,有的水在泳池中可能会存留很久,有的可能刚进去就流走了。仿照这种现象,蓄水池抽样算法诞生了,蓄水池算法的关键在于保证流入蓄水池的水和已经在池中的水以相同的概率留存在蓄水池中。并且蓄水池算法可以在不预先知道总量的情况下,在时间复杂度 O(N)的情况下,来解决这类采样问题。 核心原理 这一部分涉及公式,为了保证效果直接贴了图过来。 Python 实现 接下来尝试用 Python 实现一下蓄水池算法,由于蓄水池算法是在事先不知道总量的情况下抽样的,所以定义一个方法来接收单个元素,并且把这个方法放在类中,以持有采样后的数据。 import random class ReservoirSample(object): def __init__(self, size): self._size = size self._counter = 0 self._sample = [] def feed(self, item): self._counter += 1 # 第i个元素(i <= k),直接进入池中 if len(self._sample) < self._size: self._sample.append(item) return self._sample # 第i个元素(i > k),以k / i的概率进入池中 rand_int = random.randint(1, self._counter) if rand_int <= self._size: self._sample[rand_int - 1] = item return self._sample 测试代码 接下来实现一个测试用例验证实现的算法是否正确,既然是随机抽样,无法通过单词测试来验证是否正确,所以通过多次执行的方式来验证,比如从 1-10 里随机取样 3 个数,然后执行 10000 次取样,如果算法正确,最后结果中 1-10 被取样的次数应该是相同的,都是 3000 上下。 ...

八月 5, 2018 · 1 分钟 · Zhiya