#562 Python 解释器

2021-06-21

除了 CPython 之外的几种比较知名的 Python 实现:

  1. PyPy
  2. Jython 原名 JPython,原作者停止维护之后,社区将项目改名并继续维护。
  3. IronPython 运行在 .NET 平台
    之前由微软开发(姊妹项目 IronRuby),后来放弃。项目由志愿者维护。
  4. Pyston Dropbox 停止资助该项目,不过还活着,在继续发版。
  5. Unladen Swallow 谷歌的项目,看介绍,思路和 Pyston 差不多(在 pyston 前面),早死翘翘了
  6. Stackless Python: greenlet 就是从这个项目中弄出来的一个成果。
  7. MicroPython: 专门运行在单片机上(有一个 fork 叫 CircuitPython,似乎也有点名气)

#561 常见的 Python 性能提升手段

2021-06-21

Python 本身的性能现在还很有待提升,但是为什么没有阻碍它的推广呢?

因为 Python 常常被当作是一种胶水语言,它有很好的与 C 互操作性,一直维护与 C 库对接的 API,也就是说很容易可以通过 C 拓展来提升性能。

新的运行时

  • PyPy 据说能有很大提升
  • Pyston LLVM 编译器架构 + JIT
    https://github.com/pyston/pyston
    注意:Dropbox 放弃了这个项目,转向 Go 语言,但这个项目还在继续开发中, 比较活跃
  • Psyco http://psyco.sourceforge.net/ 已经没有维护了,据说只维护到 Python 2.4
    官方推荐 PyPy

局部优化

C/C++ 绑定

  • SWIG: C/C++ 写的代码自动绑定到 Python,就是说生成一个 Python 可以调用的 .so 模块
  • pybind11
  • Boost.Python

直接调用 C

直接开发 CPython 拓展模块(include <Python.h>)也可以,但我不觉得这是一个好办法。

GPU

  • GPULib
  • PyStream
  • PyCUDA
  • PyOpenCL

其他

  • F2PY: Fortran to Python, 将 Fortran 代码自动绑定到 Python, NumPy 的一部分
    也是编译成 .so 包
    我不清楚相关应用场景,以及这个方案是否有性能上的优势

参考资料与拓展阅读

#558 Python 源码学习 02: PyObject

2021-06-19

源码

Include/object.h

/* Nothing is actually declared to be a PyObject, but every pointer to
 * a Python object can be cast to a PyObject*.  This is inheritance built
 * by hand.  Similarly every pointer to a variable-size Python object can,
 * in addition, be cast to PyVarObject*.
 */
typedef struct _object {
    _PyObject_HEAD_EXTRA   // 如果开启了 Py_TRACE_REFS 增加一个 _ob_next, _ob_prev
                           // 使 all live heap objects 组成一个双向链表
    Py_ssize_t ob_refcnt;  // 长整型
    PyTypeObject *ob_type;
} PyObject;

/* Cast argument to PyObject* type. */
#define _PyObject_CAST(op) ((PyObject*)(op))
#define _PyObject_CAST_CONST(op) ((const PyObject*)(op))

typedef struct {
    PyObject ob_base;
    Py_ssize_t ob_size; /* Number of items in variable part */
} PyVarObject;

/* Cast argument to PyVarObject* type. */
#define _PyVarObject_CAST(op) ((PyVarObject*)(op))

#define Py_REFCNT(ob)           (_PyObject_CAST(ob)->ob_refcnt)
#define Py_TYPE(ob)             (_PyObject_CAST(ob)->ob_type)
#define Py_SIZE(ob)             (_PyVarObject_CAST(ob)->ob_size)

PyObject

相当于所有 Python 对象的父类,包含类型,引用计数等信息。

注释说的很清楚,不会有直接声明的 PyObject 变量,只会有 PyObject* 指针,所有指向 Python 对象的指针都可以转换成 PyObject*

PyVarObject

表示 ob_size 个 PyObject,也就是说 PyVarObject 是一个 PyObject 的容器。

#557 NumPy 基础

2021-06-18

NumPy 提供了一个高效的数据结构(数组/矩阵)及对应运算支持,据说效率和 C 接近,是 Python 科学计算生态的基础。

#556 Golang RabbitMQ

2021-06-18

RabbitMQ 是啥就不说了,怎么安装部署也不说了,就记录一下 RabbitMQ 在 Golang 开发中的应用。

说明:采用 github.com/streadway/amqp 库。

func (ch *Channel) Publish(exchange, key string, mandatory, immediate bool, msg Publishing) error
func (ch *Channel) Consume(queue, consumer string, autoAck, exclusive, noLocal, noWait bool, args Table) (<-chan Delivery, error)

生产者

生产者基本流程

生产者:连接

  1. amqp.Dial -> amqp.Connection
  2. amqp.Connection.Channel -> amqp.Channel
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
    log.Fatalf("Failed to connect to RabbitMQ: %s", err)
}
ch, err := conn.Channel()
if err != nil {
    log.Fatalf("Failed to open a channel: %s", err)
}

生产者:配置(可选)

事先把 MQ 配好就行,但从稳妥起见,还是在连接时加上比较好。

  1. amqp.Channel.QueueDeclare
  2. amqp.Channel.ExchangeDeclare
  3. amqp.Channel.QueueBind
q, err := ch.QueueDeclare(
    "hello", // 队列名称
    true,    // 持久化
    false,   // 自动删除
    false,   // 独占
    false,   // 等待服务器回应
    nil,     // 额外参数
)
if err != nil {
    log.Fatalf("Failed to declare a queue: %s", err)
}

生产者:发送

  1. amqp.Channel.Publish
err = ch.Publish(
    "",     // exchange
    q.Name, // routing key
    false,  // mandatory
    false,  // immediate
    amqp.Publishing{
        ContentType: "text/plain",
        Body:        []byte("hello world"),
    })
if err != nil {
    log.Fatalf("Failed to publish a message: %s", err)
}

生产者:收尾

  1. amqp.Connection.Close
  2. amqp.Channel.Close

消费者

消费者基本流程

和生产者基本一致。只是调用的的是 chan.Consume 而不是 chan.Publish
然后就是配置阶段,消费者只用关心队列在不在。

package main

import (
    "context"
    "encoding/json"
    "log"
    "time"

    "github.com/streadway/amqp"
)

// Consumer RabbitMQ 消费者
type Consumer struct {
    conn    *amqp.Connection
    channel *amqp.Channel
    queue   string
}

// NewConsumer 创建消费者
func NewConsumer(conn *amqp.Connection, ch *amqp.Channel, queueName string) (*Consumer, error) {
    // 声明队列
    _, err = DeclareQueue(ch, queueName)
    if err != nil {
        return nil, err
    }

    return &Consumer{
        conn:    conn,
        channel: ch,
        queue:   queueName,
    }, nil
}

// Consume 消费消息
func (c *Consumer) Consume(ctx context.Context, workerID int) error {
    // 设置 QoS
    err := c.channel.Qos(
        1,     // prefetch count
        0,     // prefetch size
        false, // global
    )
    if err != nil {
        return err
    }

    // 开始消费
    msgs, err := c.channel.Consume(
        c.queue,        // queue
        "",             // consumer
        false,          // auto-ack
        false,          // exclusive
        false,          // no-local
        false,          // no-wait
        nil,            // args
    )
    if err != nil {
        return err
    }

    log.Printf("消费者 %d 已启动,等待消息...", workerID)

    for {
        select {
        case <-ctx.Done():
            log.Printf("消费者 %d 停止", workerID)
            return nil
        case msg, ok := <-msgs:
            if !ok {
                log.Printf("消费者 %d 消息通道关闭", workerID)
                return nil
            }

            // 处理消息
            var message Message
            if err := json.Unmarshal(msg.Body, &message); err != nil {
                log.Printf("解析消息失败: %v", err)
                msg.Nack(false, false) // 拒绝消息,不重新入队
                continue
            }

            // 模拟处理时间
            log.Printf("消费者 %d 处理消息: %+v", workerID, message)
            time.Sleep(1 * time.Second)

            // 确认消息
            if err := msg.Ack(false); err != nil {
                log.Printf("确认消息失败: %v", err)
            }
        }
    }
}

// Close 关闭连接
func (c *Consumer) Close() {
    if c.channel != nil {
        c.channel.Close()
    }
    if c.conn != nil {
        c.conn.Close()
    }
}

复习:Exchange Types

  • Fanout: 忽略 routingKey / bindingKey,将消息广播给所有绑定队列
  • Header: 忽略 routingKey / bindingKey,根据消息和队列绑定时指定的 headers 匹配
    • 支持精准匹配、数字匹配
    • 匹配规则:x-match = all(默认)/ any
    • 据说性能很差,没有使用过,官方文档都没有提供代码示例
  • Direct: routingKey = bindingKey
  • Topic: routingKey 模糊匹配 bindingKey(* 表示一个单词,# 表示若干个单词)

默认 Exchange

  • 名称:空字符串
  • 类型:Direct
  • 特性:所有已声明的队列都会隐式绑定(implicit route)到默认 Exchange,bindingKey queue.Name
    所以 routingKey
    quene.Name 就可以将消息发送给对应的队列

参考资料与拓展阅读

#555 UUID

2021-06-17

Universally Unique Identifier 通用唯一识别码
是 ISO/IEC 标准,也定义在 IETF 的 RFC4122 中。
128 位,也就是 16 字节,通常使用 32 位 16 进制数字,以 8-4-4-4-12 的形式表示,例如:d09abf7e-3e39-11ec-9dbc-b1755772e461

#554 NanoID 能不能取代 UUID

2021-06-17

最近听说一种新的 ID 生成器,叫做 NanoID,很多地方那个都把它拿来和 UUID 做对比。
我的结论:NanoID 没啥了不起,就是个随机字符串,取代不了 UUID。