011-高频交易系统中行情模块绑核设计

高频交易系统中行情模块绑核设计
  • 原文链接: https://mp.weixin.qq.com/s/AocomOFTTr2V0PPL2rKiRA
  • 本文按照当前 content/post/C++ 目录文章风格整理,核心关注点是低延迟行情链路中的 CPU 绑核设计。

1: 为什么行情模块要绑核

  • 在高频交易系统中,行情接收、排序、因子计算、策略串行处理都属于延迟极其敏感的链路;
  • 如果线程完全交给操作系统调度,线程可能在不同 CPU 核之间迁移;
  • 一旦发生迁移,就可能引入上下文切换、缓存失效、TLB 抖动等额外开销;
  • 对于普通业务系统,这类开销通常可以接受,但在微秒级优化场景里,抖动本身就是问题;
  • 因此,行情模块做绑核,本质上是在用更强的确定性换更低的延迟波动。

2: 绑核前需要先处理哪些前提

  • 绑核并不是简单地把线程固定到某个 CPU 上;
  • 如果机器本身还有很多后台任务、IRQ 中断、SMT 竞争,单纯调用一次绑核接口往往效果有限;
  • 一般要先处理以下几个前提。

2.1 优先使用物理核

  • 对低延迟线程,优先绑定到物理核心而不是超线程;
  • 因为同一个物理核心上的两个超线程仍然共享执行单元、缓存和带宽;
  • 如果关键线程落在 SMT sibling 上,抖动很容易被放大。

2.2 处理 irqbalance

  • irqbalance 会自动平衡硬件中断;
  • 在通用服务器场景这很有用,但在低延迟交易系统中,它可能把网络中断重新分配到不希望被打扰的核心上;
  • 所以实践里常见做法是停掉 irqbalance,然后手动规划网卡队列和 CPU 的对应关系。
1
2
sudo systemctl stop irqbalance
sudo systemctl disable irqbalance

2.3 视情况关闭 SMT / Hyper-Threading

  • 如果机器专门用于极低延迟场景,可以考虑关闭 SMT;
  • 这样做的代价是吞吐下降,但能换来更干净的物理核心资源;
  • 是否关闭,要结合机器用途和实际压测结果决定。

3: C++ 中如何实现线程绑核

  • Linux 下常见做法是使用 pthread_setaffinity_np
  • 如果项目里用的是 std::threadstd::jthread,可以封装一层通用 helper,避免到处散落平台细节;
  • 除了设置亲和性,最好还要增加一次实际验证,确认线程真的运行在预期 CPU 上。

3.1 一个简单的绑核辅助函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <pthread.h>
#include <sched.h>
#include <unistd.h>

#include <iostream>
#include <stdexcept>
#include <thread>

void bind_thread_to_cpu(std::thread& th, int cpu_id) {
    cpu_set_t cpuset;
    CPU_ZERO(&cpuset);
    CPU_SET(cpu_id, &cpuset);

    int rc = pthread_setaffinity_np(
        th.native_handle(),
        sizeof(cpu_set_t),
        &cpuset
    );

    if (rc != 0) {
        throw std::runtime_error("pthread_setaffinity_np failed");
    }
}

void worker() {
    std::cout << "running on cpu: " << sched_getcpu() << std::endl;
}

int main() {
    std::thread th(worker);
    bind_thread_to_cpu(th, 2);
    th.join();
    return 0;
}

3.2 更实用的封装建议

  • cpu_id 做合法性检查,避免绑定到不存在的核心;
  • 给绑定操作加日志,方便排查部署环境差异;
  • 在线程启动后增加一次 sched_getcpu() 验证;
  • 如果是线程池或多阶段流水线,建议把绑核规则和业务线程角色分离配置。

4: 行情模块的绑核分层设计

  • 行情模块不是一个线程,而是一条流水线;
  • 真正有效的做法,不是“所有线程都绑核”,而是按照链路角色进行分层绑定;
  • 常见的划分方式如下。

4.1 行情接收线程

  • 行情接收线程负责从网卡队列读取数据,是链路最前端;
  • 这类线程应当优先独占物理核,并尽量和 NIC queue 一一对应;
  • 这样可以减少中断分发和线程迁移带来的额外抖动。

4.2 排序线程

  • 如果行情源较多,接收后通常还需要排序或重组;
  • 排序线程应与接收线程分开绑定到独立核心;
  • 最好放在拓扑上相近的核心,降低跨核通信代价。

4.3 串行核心线程

  • 因子、模型、策略串行主链路通常更强调确定性;
  • 这一类线程往往要独占一个核心,尽可能不与其他任务共享;
  • 它是整条交易决策链的热点线程之一。

4.4 监控与日志线程

  • 监控、埋点、日志落盘线程优先级相对较低;
  • 它们可以被安排到非关键核心,甚至低价值的超线程上;
  • 原则是不要抢占行情主链路的缓存与调度资源。

4.5 一个典型的核心分配示意

线程类型建议 CPU设计原则
行情接收线程CPU 0-3与网卡队列一一对应,独占物理核
排序线程CPU 4-7与接收线程相邻,减少跨核通信
串行核心线程CPU 8独占核心,承载策略主链路
监控 / 日志线程CPU 15放在非关键核或低优先级资源上

5: 仅仅线程绑核还不够

  • 如果操作系统仍然把其它任务调度到这些核心上,那么“独占核心”只是一种错觉;
  • 真正想把关键 CPU 留给行情链路,还需要做系统级隔离。

5.1 isolcpus

  • isolcpus 用于把指定 CPU 从常规调度域里隔离出去;
  • 这样系统中的普通任务不会轻易落到这些 CPU 上。

5.2 nohz_full

  • nohz_full 用于减少指定 CPU 上的调度 tick;
  • 对低延迟线程来说,减少无关系统打扰是有价值的。

5.3 rcu_nocbs

  • rcu_nocbs 可以让指定 CPU 避开 RCU callback 的干扰;
  • 对需要极致稳定性的核心,这一点也很关键。
1
GRUB_CMDLINE_LINUX="isolcpus=0-8 nohz_full=0-8 rcu_nocbs=0-8"
  • 上面的参数只是示例;
  • 实际使用时应结合机器拓扑、服务部署方式以及运维策略统一规划。

6: 如何验证绑核是否真的生效

  • 绑核不是“代码能运行就算完成”;
  • 必须从代码层、系统层和性能层分别验证。

6.1 代码层验证

  • 在线程工作函数中调用 sched_getcpu()
  • 确认线程运行时所在核心是否符合预期。
1
std::cout << "current cpu = " << sched_getcpu() << std::endl;

6.2 系统层验证

  • 使用 taskset -cp <pid> 查看某个线程或进程的 CPU 亲和性;
  • 也可以结合 ps -eLo pid,tid,psr,comm 观察线程实际落在哪个 CPU 上。
1
2
taskset -cp 12345
ps -eLo pid,tid,psr,comm | grep your_process

6.3 性能层验证

  • 最终还是要看上下文切换、cache miss、延迟抖动这些指标有没有下降;
  • 常见工具是 perf stat
  • 如果绑核后上下文切换次数和 cache miss 没有明显改善,就说明设计仍需调整。
1
perf stat -e context-switches,cache-misses,cache-references ./your_app

7: NUMA 是绑核设计里最容易被忽略的问题

  • 在双路或多路服务器上,CPU、内存、PCIe 设备通常分布在不同 NUMA 节点;
  • 如果线程绑在 node0,但网卡和内存分配落在 node1,那么跨 NUMA 访问会直接拉高延迟;
  • 这会抵消很多绑核带来的收益。

7.1 NUMA 设计原则

  • 行情接收线程、网卡队列、中间缓冲区最好放在同一个 NUMA node;
  • 排序线程与后续串行线程也尽量保持局部性;
  • 低延迟系统做绑核时,不能只看 CPU 编号,还要看拓扑关系。

7.2 常用排查工具

1
2
numactl --hardware
lscpu
  • 如果需要更直观的硬件拓扑,也可以借助 lstopo 查看。

8: 一个更稳妥的实践结论

  • 行情模块绑核的目标,不是为了“看起来专业”,而是为了降低链路中的非确定性;

06--Claude Code 最值得装的 10 个Skills

06--Claude Code 最值得装的 10 个Skills

1: 为什么 Claude Code 的差距不只在模型

很多人刚开始用 Claude Code 时,最先关注的是模型强不强、上下文长不长、生成速度快不快。 但真正用一段时间后就会发现,拉开人与人差距的,往往不是模型本身,而是你有没有把一批高频、稳定、可复用的 Skills 装进自己的工作流。

Go 结构体验证神器validator

Go 结构体验证神器validator

1: 概述

  • 在 Web 服务、RPC 接口、配置加载这类场景里,我们经常要对输入数据做合法性校验;

  • 如果完全手写 if/else 或正则,一个字段一个字段判断,代码很快就会变得又长又散;

Zerolog使用入门

Zerolog使用入门

1: 概述

  • zerolog 是一个面向 Go 的结构化日志库,默认输出 JSON 格式;

  • 它的核心卖点是高性能和低开销,比较适合接口服务、后台任务、网关等对吞吐比较敏感的场景;

  • zero 这个名字通常对应它强调的 zero-allocation 设计思路;

01--mysql-borrow切数据

-mysql-borrow切数据

1: mysql-borrow切数据

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
-- 1. borrow_2026表
CREATE TABLE borrow_2026 LIKE borrow;
INSERT INTO borrow_2026
SELECT * FROM  borrow where tradeDate >= '2026-01-01' ;

-- 2. 创建历史表borrow_2025, 并把历史数据insert,数据太多可以分批插入
CREATE TABLE borrow_2025 LIKE borrow;
INSERT INTO borrow_2025 SELECT * FROM  borrow where tradeDate >= '2025-01-01' and tradeDate < '2025-04-01'; // Q1
INSERT INTO borrow_2025 SELECT * FROM  borrow where tradeDate >= '2025-04-01' and tradeDate < '2025-07-01'; // Q2
INSERT INTO borrow_2025 SELECT * FROM  borrow where tradeDate >= '2025-07-01' and tradeDate < '2025-10-01'; // Q3
INSERT INTO borrow_2025 SELECT * FROM  borrow where tradeDate >= '2025-10-01' and tradeDate < '2026-01-01'; // Q4

-- 3. rename backupdate borrow表
RENAME TABLE borrow TO borrow_2025_backup ;

-- 4. rename borrow_2026表 -> borrow新表
RENAME TABLE borrow_2026 TO borrow;

-- 5. 核对数据

01--005-Windows安装部署OpenClaw

01--Windows安装部署OpenClaw

1: OpenClaw 介绍

OpenClaw(曾用名 Clawdbot、Moltbot)是一个运行在本地电脑的开源 AI 智能体。

FeatureDescription
操作电脑以读写文件、操作浏览器、调用系统功能.
接入聊天工具出门在外用手机给它留言,它就能自动干活,还能实时同步截图和执行过程.
定时任务系统用自然语言创建定时任务,如"提醒我7:30打开空调".
长期记忆记忆存储在本地,越用越聪明.

2: OpenClaw 的安装准备

2.1: 安装Node.js

  • 访问 Node.js 官网:https://nodejs.org/zh-cn/download
  • 下载 LTS 版本(长期支持版)的 Windows 安装包
  • 双击安装,一路 Next 即可
  • node -v 查看node版本

2.2: 安装Git

  • 访问 Git 官网:https://git-scm.com/install/windows
  • 点击下载最新版本
  • 双击安装,一路 Next 即可(保持默认选项)

2.3: 配置 PowerShell 脚本执行权限

  • Windows 默认会拦截安装脚本,需要先放开权限。
    • 步骤 1:以管理员身份打开 PowerShell右键点击「开始」菜单 → 选择「Windows PowerShell (管理员)」
    • 步骤 2:查看当前权限 Get-ExecutionPolicy -List,如果 CurrentUser 显示 Restricted,说明不允许运行脚本。
    • 步骤 3:修改权限Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

2.4: 准备 API Key

3: 安装OpenClaw

3.1 安装OpenClaw

  • 打开 OpenClaw 官网:https://openclaw.ai/
  • 复制 Windows 安装命令
  • 打开 PowerShell,粘贴以下命令并回车:iwr -useb https://openclaw.ai/install.ps1 | iex
  • 或者iwr -useb https://openclaw.ai/install.ps1 | iex
  • 注意: 要配置ssh key到github或者``

3.2 Run OpenClaw

  • 验证是否安装成功,查看oepnclaw的版本openclaw --version
  • 运行 openclaw onboard --install-daemon
  • 启动 Gateway默认 UI 在 http://127.0.0.1:18789/
  • 配置文件默认在 ~/.openclaw/openclaw.json
  • 清除npm缓存:npm cache clean --force
  • 卸载旧版本:npm uninstall -g openclaw
  • npm重新安装openclaw:npm install -g openclaw@latest
  • 如果遇到网络问题,可以尝试使用淘宝镜像:npm install -g openclaw@latest --registry=https://registry.npmmirror.com
  • 确保Node.js和npm版本是最新的

4: 初始化配置OpenClaw

5: OpenClaw接入飞书

5: OpenClaw接入微信

7: OpenClaw 案例

01-FastCode

01--FastCode

1: FastCode 介绍

FastCode是一个高效利用token的综合代码理解和分析框架,旨在为大规模代码库和软件架构提供卓越的速度、出色的准确性和成本效益。通过结合先进的语义理解和智能结构导航,FastCode彻底改变了开发者探索、理解以及与复杂代码库交互的方式。

021-Python读取各种编码的csv文件

1: Python读取各种编码的csv文件

1.1 需求背景

  • 因为工作原因需要导出导入大量的CSV文件数据,这些CSV文件中存在大量的中文字符数据。但是不同数据来源的CSV格式文件的编码格式并不固定,为正确解析中文字符需要一个可以批量读取不同编码格式的CSV程序;

1.2 问题描述

  • 通常Python CSV库读取CSV文件经常遇到莫名其妙的编码错误异常,报错信息主要是unicode解码错误,具体报错信息如下:
  • UnicodeDecodeError: 'utf-8' codec can't decode byte 0xb1 in position 0: invalid start byte

2: 解决方案

  • 中文编码规则主要有gbk、uft-8、utf-8-sig、GB2312、gb18030、iso-8859-1,为解析各种编码特别是涉及中文的CSV文件,防止解析出错出现中文乱码情况,需要造一个轮子(自定义函数)便于解决乱码问题;

2.1 解决方案1:一般方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import csv 
def read_csv(filename):
    #预设编码列表
    encodings = ['gbk','utf-8','utf-8-sig','GB2312','gb18030','iso-8859-1']
    #循环编码列表,直到正确解析返回解析后的数组data,否则返回false
    for e in encodings:
        data = []
        try:
            with open(filename, encoding=e) as f:
                reader = csv.reader(f)
                header = next(reader)
                # print(header)
                for row in reader:
                    data.append(row)
            print(filename,e)
            return data
        except: 
            print(filename,e)
    return False

2.2 解决方案2:引入chardet模块来读取文件的编码格式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import csv
import chardet

def read_csv(filename):
    # 使用chardet检测文件编码
    with open(filename, 'rb') as f:
        result = chardet.detect(f.read())
        encoding = result['encoding']
    # 使用检测到的编码读取CSV文件
    with open(filename, encoding=encoding) as f:
        reader = csv.reader(f)
        header = next(reader)
        data = [row for row in reader]
    print(filename, encoding)
    return data

2.3 解决方案3:使用pandas库的read_csv函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import chardet
import pandas as pd

def read_csv_pd(filename, sep=','): 
    # 1. 使用 'rb' 模式打开文件并读取前几行/字节进行编码检测
    # chardet 有时可能不可靠,但常用于猜测编码
    try:
        with open(filename, 'rb') as f:
            # 读取部分文件内容以提高检测速度
            raw_data = f.read(4096) 
            encode = chardet.detect(raw_data)['encoding']
            
            # 确保检测到的编码名称是小写字符串
            if encode:
                encode = encode.lower()
            else:
                print(f"未能检测到文件编码: {filename}")
                return False
    except Exception as e:
        print(f"文件读取或编码检测失败: {e}")
        return False

    # 2. 尝试使用检测到的编码读取文件
    try:
        # **修复点 1:变量和编码名称统一使用字符串**
        if encode in ['utf-8', 'ascii']: # chardet 有时可能返回 ascii
            # 尝试最常见的 UTF-8
            data = pd.read_csv(filename, encoding='utf-8', sep=sep)
        
        elif 'gb' in encode:
            # **修复点 2:简化中文编码处理逻辑**
            # GB2312, GBK, GB18030 兼容性:从兼容性最好的 GB18030 开始尝试
            try:
                data = pd.read_csv(filename, encoding='gb18030', sep=sep)
            except UnicodeDecodeError:
                # 如果 GB18030 失败,尝试其他检测到的编码,例如原始的 'gbk'
                data = pd.read_csv(filename, encoding='gbk', sep=sep)
        
        elif encode == 'utf-8-sig':
            data = pd.read_csv(filename, encoding='utf-8-sig', sep=sep)
        
        elif encode == 'iso-8859-1':
            # **修复点 3:修正编码名称和参数拼写**
            data = pd.read_csv(filename, encoding='iso-8859-1', sep=sep)
            
        else:
            # 对于其他未知编码,直接用检测到的编码尝试读取
            print(f"尝试使用检测到的未知编码: {encode}")
            data = pd.read_csv(filename, encoding=encode, sep=sep)
            
    except UnicodeDecodeError as e:
        # **修复点 4:通用错误处理**
        print(f"读取文件 {filename} 时发生编码错误: {e}")
        # **解决方案:忽略错误**
        try:
             data = pd.read_csv(filename, encoding=encode, sep=sep, errors='ignore') 
        except Exception as final_e:
             print(f"无法使用任何方式读取文件: {final_e}")
             return False

    except Exception as e:
        print(f"读取文件时发生未知错误: {e}")
        return False
        
    return data