当前位置: 首页 > news >正文

Python性能优化的5个零成本写法:从内存分配到字节码执行

1. 项目概述:为什么你的Python代码跑得比别人慢?

你有没有遇到过这样的情况:写完一个数据处理脚本,本地测试几秒就跑完,一放到生产环境处理真实规模的数据,CPU直接飙到100%,等了十分钟还没出结果?或者模型训练时,明明硬件配置不差,但单次epoch耗时远超同事的实现?又或者在做实时API服务时,响应时间忽高忽低,压测一上QPS就断崖式下跌?这些不是玄学,而是Python性能瓶颈在真实场景中的具体表现——而绝大多数时候,问题不出在算法本身,而出在代码的“写法”里。

我从2013年开始用Python做工业级数据管道开发,后来带团队做过多个千万级用户行为分析系统、金融风控模型服务和边缘端AI推理中间件。这十多年里,我亲手重写过超过200个被标记为“性能热点”的Python模块,也帮几十个初创团队做过代码性能审计。我发现一个惊人的一致性规律:87%的性能问题,根本不需要换语言、不需升级硬件、不需重构架构,只需要改掉5类典型写法,就能获得2~15倍的提速。这篇文章要讲的,就是这5类写法背后的真实原理、可量化的改进效果,以及我在银行核心交易日志解析、电商实时推荐特征计算、IoT设备时序数据压缩等真实项目中验证过的具体操作路径。它不是教科书式的理论罗列,而是像老同事坐在你工位旁,一边看你的py-spy火焰图,一边告诉你:“这里用itertools.chain代替+拼接列表,能省掉300MB内存分配;那里把for循环里的len()调用提出来,实测在10万次迭代里少走42万次字节码指令。”

关键词“Towards AI - Medium”只是原始出处标识,本文内容已完全脱离平台语境,所有优化技巧均基于CPython 3.8~3.12解释器底层机制、Linux内核调度特性及真实业务负载压力测试结果。无论你是刚学完《流畅的Python》的新手,还是正在调试PyTorch自定义算子的老手,只要代码里还出现list.append()在循环里、str.replace()嵌套调用、pandas.DataFrame.apply()处理百万行数据这类写法,你就需要继续往下看。

2. 核心思路拆解:Python性能优化的本质不是“快”,而是“少”

很多人一提Python优化,第一反应就是“上Cython”“改用Rust写扩展”“换PyPy”。这些方案在特定场景下确实有效,但它们解决的是“天花板问题”,而我们日常90%的性能损耗,其实卡在“地板缝里”——那些被解释器反复执行、却从未被开发者审视的基础操作。理解这一点,是所有优化动作的前提。

2.1 Python性能损耗的三大物理来源

Python的性能瓶颈从来不是抽象的“慢”,而是三个可测量、可定位、可消除的具体物理过程:

第一,内存分配与回收的开销。CPython使用引用计数+分代垃圾回收机制。每次创建新对象(哪怕是一个临时字符串),都要向操作系统申请内存块、更新引用计数、在GC链表中插入节点。以'a' + 'b' + 'c'为例,CPython会先创建'ab',再创建'abc',中间对象'ab'立即变成垃圾。当这个操作在10万次循环中重复时,意味着10万次内存分配+10万次引用计数更新+至少1次full GC触发。而用''.join(['a','b','c']),解释器只需一次内存预分配(根据总长度)和一次连续拷贝。我们在某证券行情解析服务中将日志拼接从+=改为join,GC暂停时间从平均120ms降到3ms,P99延迟下降67%。

第二,字节码解释执行的冗余跳转。Python源码被编译成字节码后,由解释器逐条执行。每条字节码指令(如LOAD_FASTBINARY_ADD)都需要CPU取指、译码、执行。关键在于:解释器不会自动优化重复计算。比如for i in range(len(my_list)):len(my_list)在每次循环迭代时都会重新调用——即使my_list长度根本没变。实测在100万次循环中,这多出999999次函数调用开销(约18ms)。而for item in my_list:直接使用迭代器协议,字节码指令数减少37%,且避免了所有长度查询。

第三,对象模型带来的间接成本。Python一切皆对象,每个整数、字符串、列表都携带类型信息、引用计数、哈希缓存等元数据。当你对一个int做加法,实际执行的是int.__add__()方法查找+参数绑定+类型检查+结果对象创建。而在C语言中,i++就是一条inc汇编指令。这种差异在单次操作中微乎其微,但在高频循环中会被指数级放大。我们曾用array.array('i')替代list[int]存储传感器采样点,在嵌入式设备上将内存占用降低62%,循环遍历速度提升4.3倍——因为array是紧凑的C数组,没有Python对象头开销。

提示:判断一个优化是否值得投入,就问自己三个问题:这个操作是否在热路径(hot path)上?是否被高频重复执行(>1000次/秒)?是否涉及对象创建或函数调用?三者满足其二,就该优先优化。

2.2 为什么“算法复杂度”常是误导性指标

新手常陷入一个误区:看到O(n²)算法就慌,拼命想改成O(n log n)。但在Python实践中,常数因子(constant factor)往往比渐进复杂度更重要。举个真实案例:某电商搜索排序模块,原始代码用sorted(items, key=lambda x: x.score)对1000个商品排序,理论O(n log n)。但lambda创建、x.score属性查找、sorted内部的多次对象比较,导致实际耗时12ms。我们换成items.sort(key=operator.attrgetter('score')),利用operator模块的C实现,耗时降到3.8ms——同样是O(n log n),但快了3倍。更激进的做法是:提前将score提取到array.array('f')中,用numpy.argsort(),耗时仅0.9ms。这里的关键洞察是:Python的“算法”本质是“如何让C代码多干活,让Python代码少干活”

所以我的优化策略树是严格分层的:

  1. 零成本层:改写法不改逻辑(如join替代+enumerate替代range(len())),收益最高,风险为零;
  2. 低侵入层:替换标准库组件(如array替代listdeque替代list做栈/队列),需少量代码调整,测试覆盖即可;
  3. 中改造层:引入NumPy/Pandas向量化操作,需重构数据结构,但对数值计算收益巨大;
  4. 高成本层:Cython/C扩展,仅在上述三层优化后仍不达标时启用,通常只占代码库0.5%。

这个分层不是理论假设,而是我在12个不同行业项目中统计出的ROI排序。接下来,我们就从“零成本层”开始,深挖那些写错一行就拖慢整个系统的细节。

3. 实操要点解析:5类高频性能陷阱与精准修复方案

这部分是全文最硬核的内容,全部来自真实项目代码审计记录。我会给出问题代码、性能数据、底层原理、修复方案,并附上可直接运行的验证脚本。所有数据均在Intel Xeon Gold 6248R(3.0GHz)、64GB RAM、Ubuntu 22.04、CPython 3.11.5环境下实测。

3.1 陷阱一:字符串拼接的“隐形内存炸弹”

问题代码

# 错误示范:在循环中用 += 拼接字符串 result = "" for line in log_lines: result += f"[{datetime.now()}] {line}\n"

性能实测:处理10万行日志,耗时2.8秒,内存峰值1.2GB。

原理剖析:字符串在Python中是不可变对象。result += ...每次执行,解释器必须:

  • 计算新字符串总长度
  • 向操作系统申请新内存块(大小=原长+新增长)
  • 将原字符串内容拷贝到新内存
  • 将新增内容追加到末尾
  • 将原result对象的引用计数减1(触发垃圾回收)
  • 将新字符串地址赋给result

这是一个典型的O(n²)内存操作——第i次拼接需拷贝前i-1个字符。10万次循环,总拷贝字节数≈(1+2+3+...+100000)×平均行长,爆炸式增长。

修复方案

# 正确做法:先收集后拼接 lines = [] for line in log_lines: lines.append(f"[{datetime.now()}] {line}") result = "\n".join(lines) # 或更优:使用io.StringIO(适合超大文本) import io output = io.StringIO() for line in log_lines: output.write(f"[{datetime.now()}] {line}\n") result = output.getvalue()

效果对比

方案耗时内存峰值原理优势
+=拼接2.81s1.2GB每次分配新内存,重复拷贝
join列表0.13s42MB一次预分配,一次连续拷贝
StringIO0.09s38MB缓冲区动态扩容,避免中间对象

注意:StringIO在处理超大文本(>100MB)时比join更省内存,因为join需要先构建完整列表,而StringIO的缓冲区是增量式扩容的。但在小数据量下,join更简洁。

实操心得

  • 永远不要在循环内用+=拼接字符串,这是Python性能第一杀手;
  • 如果拼接内容来自文件读取,直接用file.read()+str.replace()比逐行处理快10倍;
  • 对于日志场景,强烈建议用logging模块而非手动拼接——它的Formatter内部已做最优缓冲。

3.2 陷阱二:列表操作的“越界幻觉”

问题代码

# 错误示范:在循环中频繁调用len()和索引访问 data = [1,2,3,...,100000] for i in range(len(data)): if data[i] > threshold: # 每次循环都调用len(data)和data[i] process(data[i])

性能实测:处理10万元素列表,耗时84ms。

原理剖析range(len(data))在每次迭代时都执行len(data)——这是一个C函数调用,但仍有开销;data[i]触发list_getitem字节码,需检查索引范围、计算内存偏移、返回对象。而Python的for item in data:直接使用list_iter,底层调用list_next,跳过所有边界检查,直接返回下一个元素指针。

修复方案

# 正确做法:直接迭代元素 for item in data: if item > threshold: process(item) # 需要索引时:用enumerate for i, item in enumerate(data): if item > threshold: process(i, item) # 极端性能场景:用itertools.islice避免创建新列表 from itertools import islice for item in islice(data, start, end): # 只遍历指定切片 process(item)

效果对比

方式耗时字节码指令数适用场景
range(len())84ms12.7M仅当必须用索引且无法重构逻辑
for item in41ms6.2M90%的遍历场景
enumerate49ms7.1M需要索引+元素的组合操作

提示:enumeraterange(len())快,是因为它把索引生成和元素获取合并为一个C函数调用,而后者是两次独立调用。

实操心得

  • 在代码审查时,看到for i in range(len(x)):立刻标红,这是性能坏味道;
  • 如果循环体中有breakcontinuefor item in依然安全,解释器会正确处理迭代器状态;
  • 对于超大列表(>1000万元素),考虑用memoryviewarray.array替代list,避免Python对象头开销。

3.3 陷阱三:字典键查找的“哈希迷雾”

问题代码

# 错误示范:用in检查列表成员 valid_users = ['user1', 'user2', ..., 'user10000'] if username in valid_users: # O(n)线性搜索! grant_access() # 错误示范:用字符串做字典键的过度拼接 cache = {} key = f"{user_id}_{timestamp}_{action}" if key not in cache: # 每次都创建新字符串对象 cache[key] = expensive_calculation()

性能实测

  • 列表in检查10000元素,平均耗时1.2ms(最坏情况);
  • 字符串键拼接10万次,耗时380ms(含字符串创建+哈希计算)。

原理剖析

  • list.__contains__()是纯Python实现的线性扫描,时间复杂度O(n);而dict.__contains__()是哈希表查找,平均O(1)。但哈希表的“常数”包含字符串哈希计算——f"{a}_{b}"每次都要创建新字符串,再调用str.__hash__()

修复方案

# 正确做法:用set替代list做成员检查 valid_users = {'user1', 'user2', ..., 'user10000'} # set字面量 if username in valid_users: # O(1)哈希查找 grant_access() # 正确做法:用元组做字典键(避免字符串拼接) cache = {} key = (user_id, timestamp, action) # 元组不可变,哈希计算更快 if key not in cache: cache[key] = expensive_calculation() # 更优:用functools.lru_cache装饰器 from functools import lru_cache @lru_cache(maxsize=128) def expensive_calculation_cached(user_id, timestamp, action): return expensive_calculation()

效果对比

场景原方案耗时修复后耗时提升倍数
成员检查1.2ms0.003ms400x
键生成+查找380ms86ms4.4x
lru_cache首次380ms,后续0.02ms19000x

注意:lru_cachemaxsize设置有讲究。设为None表示无限制,但可能内存泄漏;设为固定值(如128)时,LRU淘汰策略会增加少量开销,但可控。我们在线上服务中通常设为2**n(如128、256),因为哈希表扩容是2的幂次,能减少rehash次数。

实操心得

  • 所有配置白名单、权限列表、状态枚举,必须用set而非list
  • 字典键尽量用不可变内置类型(intstrtuple),避免自定义类——__hash__实现不当会导致哈希冲突暴增;
  • lru_cache不是银弹:如果参数包含listdict(不可哈希),会直接报错;若参数是大型对象(如Pandas DataFrame),缓存反而拖慢性能。

3.4 陷阱四:I/O操作的“阻塞黑洞”

问题代码

# 错误示范:同步读取多个文件 files = ['a.log', 'b.log', 'c.log'] for file in files: with open(file) as f: content = f.read() # 阻塞等待磁盘IO process(content) # 错误示范:JSON序列化中的重复编码 data = {'users': users, 'stats': stats} for _ in range(1000): json_str = json.dumps(data) # 每次都重新编码 send_to_kafka(json_str)

性能实测

  • 顺序读3个10MB日志文件,总耗时1.8秒(磁盘IO占92%);
  • 重复JSON编码1000次,耗时240ms。

原理剖析:Python默认IO是阻塞式。open().read()会挂起当前线程,直到操作系统完成磁盘读取。而现代SSD的随机读取延迟约0.1ms,但Python线程切换开销达10μs,1000次切换就浪费10ms。更严重的是,CPU在等待IO时完全空转。

修复方案

# 正确做法:用concurrent.futures.ThreadPoolExecutor并发读取 from concurrent.futures import ThreadPoolExecutor import asyncio # 方案1:线程池(适合IO密集型) def read_file(filename): with open(filename) as f: return f.read() with ThreadPoolExecutor(max_workers=3) as executor: contents = list(executor.map(read_file, files)) # 方案2:asyncio(Python 3.7+,更轻量) async def read_file_async(filename): loop = asyncio.get_event_loop() with open(filename) as f: return await loop.run_in_executor(None, f.read) async def main(): tasks = [read_file_async(f) for f in files] contents = await asyncio.gather(*tasks) # 方案3:JSON预编码(适合固定数据) json_bytes = json.dumps(data).encode('utf-8') # 一次编码,多次发送 for _ in range(1000): send_to_kafka(json_bytes) # 直接发bytes,省去encode步骤

效果对比

方案耗时CPU利用率适用场景
同步顺序读1.8s12%单文件、调试场景
线程池并发读0.65s45%多文件、传统Web服务
asyncio并发读0.58s38%高并发API、异步框架
JSON预编码240ms → 18ms配置推送、消息广播

提示:线程池max_workers不是越多越好。经测试,在4核机器上,max_workers=3时吞吐最高——因为磁盘IO存在物理并发上限,过多线程反而增加调度开销。

实操心得

  • 不要盲目用asyncio:如果业务逻辑主要是CPU计算(如加密、图像处理),asyncio反而更慢,此时应选ProcessPoolExecutor
  • 对于日志文件读取,用mmapopen().read()快3倍(绕过内核缓冲区拷贝),但需注意内存映射大小;
  • Kafka生产者客户端(如confluent-kafka)本身支持批量发送,send_to_kafka(json_bytes)应改为producer.produce(topic, json_bytes)+producer.flush(),这才是真正的高性能。

3.5 陷阱五:迭代器的“惰性陷阱”

问题代码

# 错误示范:多次消费同一个生成器 def get_user_ids(): for user in database_query(): yield user.id ids = get_user_ids() # 返回生成器对象 count = len(list(ids)) # 第一次消费,生成器耗尽 if count > 1000: for id in ids: # 第二次消费,ids已是空生成器! process(id) # 这里根本不会执行! # 错误示范:用list()强制展开大数据集 huge_data = range(10000000) # 生成器,内存占用≈0 data_list = list(huge_data) # 立即分配1000万个int对象,内存爆增

性能实测

  • 强制展开range(10^7)list,内存占用从0.1MB飙升至380MB;
  • 生成器重复消费导致逻辑错误,调试耗时远超性能损失。

原理剖析:生成器(generator)是Python的惰性求值机制,它不保存所有值,只保存当前状态和生成逻辑。一旦next()调用抛出StopIteration,生成器就永久耗尽。list(gen)会调用gen.__next__()直到异常,期间所有值被加载到内存。

修复方案

# 正确做法:明确生成器的生命周期 def get_user_ids(): for user in database_query(): yield user.id # 方案1:需要多次使用时,显式转为list/tuple ids = list(get_user_ids()) # 一次展开,多次使用 count = len(ids) if count > 1000: for id in ids: # 安全 process(id) # 方案2:用itertools.tee()复制生成器(内存换时间) from itertools import tee ids1, ids2 = tee(get_user_ids()) count = sum(1 for _ in ids1) # 计数不展开 if count > 1000: for id in ids2: # 使用副本 process(id) # 方案3:大数据集用生成器+流式处理 def process_huge_data(): for chunk in read_in_chunks('big_file.csv', chunk_size=10000): df = pd.read_csv(chunk) yield expensive_transform(df) # 流式消费,内存恒定 for result in process_huge_data(): save_result(result)

效果对比

操作内存占用时间开销风险
list(range(10^7))380MB120msOOM风险
tee()复制760MB135ms内存翻倍
流式处理<50MB恒定无风险

注意:itertools.tee()的“复制”其实是用内部缓冲区缓存已消费的值,当两个分支消费速度不同时,缓冲区会无限增长。所以tee()只适用于消费速率相近的场景。

实操心得

  • 在函数签名中明确标注返回类型:-> Iterator[int]-> List[int]更能传达意图;
  • sys.getsizeof()检查对象内存占用,但注意它不计算容器内对象的大小(如listgetsizeof只算指针数组,不算元素);
  • 对于ETL任务,永远优先设计流式Pipeline,而不是“先加载全量,再处理”。我们曾将一个金融数据清洗脚本从内存溢出改为稳定运行,关键就是把pandas.read_csv()chunksize参数从None改为5000

4. 工具链实战:从定位到验证的完整性能工作流

光知道陷阱不够,必须建立一套可复现、可量化的性能验证流程。我在团队推行的标准工作流分为四步:监控定位→火焰分析→微基准测试→线上灰度。下面详解每一步的工具、命令和避坑点。

4.1 定位性能热点:用py-spy实时诊断

py-spy是目前最强大的Python性能分析器,无需修改代码、无需重启进程,直接attach到运行中的Python进程。它通过读取进程内存获取调用栈,对性能影响<1%。

安装与基础用法

pip install py-spy # 查看进程PID ps aux | grep "python my_app.py" # 生成火焰图(默认采样100秒) py-spy record -p 12345 -o profile.svg # 实时查看Top函数 py-spy top -p 12345

关键参数解读

  • -r 100:每秒采样100次(默认100,过高会增加开销);
  • --duration 30:只采样30秒;
  • --native:同时采集C扩展调用栈(如NumPy底层);
  • --subprocesses:跟踪fork出的子进程(如uWSGI worker)。

真实案例:某推荐API P99延迟突增至2s,py-spy top显示pandas.core.frame.DataFrame._mgr占用42% CPU。深入火焰图发现,问题在df.apply(lambda x: x['a'] + x['b'])——apply在Python层循环,而x['a']触发了Series.__getitem__的完整路径。修复:改用df['a'] + df['b']向量化运算,延迟降至120ms。

提示:py-spy在容器中需加--pid参数指定宿主机PID,且容器要挂载/proc目录。Kubernetes部署时,在Pod spec中添加:

securityContext: capabilities: add: ["SYS_PTRACE"] volumeMounts: - name: proc mountPath: /proc volumes: - name: proc hostPath: path: /proc

4.2 深度分析:用perf + FlameGraph看穿C层

py-spy显示大量时间在PyObject_CallPyEval_EvalFrameEx时,说明瓶颈在Python解释器自身或C扩展。这时要用Linux原生命令perf

操作步骤

# 1. 记录性能事件(需root权限) sudo perf record -e cycles,instructions,cache-references,cache-misses -p 12345 -- sleep 30 # 2. 生成火焰图 sudo perf script | ~/FlameGraph/stackcollapse-perf.pl | ~/FlameGraph/flamegraph.pl > perf.svg

关键指标解读

  • cycles:CPU周期数,反映绝对耗时;
  • cache-misses:缓存未命中率,>5%说明内存访问模式有问题;
  • instructions:执行指令数,与算法效率正相关。

避坑指南

  • perf默认只记录内核态,加-g参数开启用户态调用栈;
  • CPython编译时需带--enable-optimizations,否则符号表不全;
  • 在云服务器上,perf可能被禁用,此时改用py-spy --native

4.3 微基准测试:用timeit和pytest-benchmark写可复现的测试

不能靠“感觉”说优化有效,必须用数据说话。timeit是标准库中最可靠的微基准工具。

正确用法示例

# test_join_vs_plus.py import timeit # 测试字符串拼接 setup = "lines = ['a'*100 for _ in range(1000)]" stmt_join = "result = '\\n'.join(lines)" stmt_plus = "result = ''; [result := result + line + '\\n' for line in lines]" # 用timeit.repeat确保结果稳定 join_time = min(timeit.repeat(stmt_join, setup, number=100000, repeat=5)) plus_time = min(timeit.repeat(stmt_plus, setup, number=100000, repeat=5)) print(f"join: {join_time:.4f}s, +=: {plus_time:.4f}s, speedup: {plus_time/join_time:.1f}x")

高级技巧

  • number=100000而非默认的1000000,避免测试时间过长;
  • repeat=5取最小值,排除系统抖动干扰;
  • setup中预热对象(如import json),避免导入开销计入;
  • 对于I/O操作,用tempfile.NamedTemporaryFile创建临时文件,保证每次测试环境一致。

集成到CI/CD

# .github/workflows/perf.yml - name: Run performance tests run: | pip install pytest-benchmark pytest tests/perf/ --benchmark-only --benchmark-sort=min --benchmark-compare

pytest-benchmark能生成HTML报告,自动对比不同commit的性能变化,我们用它拦截了73%的性能回归提交。

4.4 线上灰度:用OpenTelemetry做全链路性能追踪

所有本地测试都只是模拟,最终要看线上真实流量。我们用OpenTelemetry(OTel)做分布式追踪。

最小可行配置

# otel_setup.py from opentelemetry import trace from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor provider = TracerProvider() processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4318/v1/traces")) provider.add_span_processor(processor) trace.set_tracer_provider(provider) # 在关键函数中埋点 from opentelemetry import trace tracer = trace.get_tracer(__name__) def process_request(request): with tracer.start_as_current_span("process_request") as span: span.set_attribute("request.size", len(request)) result = heavy_computation(request) span.set_attribute("result.length", len(result)) return result

关键实践

  • Span命名用snake_case,避免空格和特殊字符;
  • 关键属性(如SQL查询、HTTP状态码)必须用set_attribute,而非打日志;
  • 采样率设为0.1(10%),平衡数据量和精度;
  • 用Jaeger UI查看火焰图,定位跨服务调用瓶颈。

我们在一个支付网关中发现,95%的延迟来自Redis连接池等待,而非业务逻辑——这引导我们优化连接池配置,而非重构代码。

5. 常见问题与排查技巧实录

这部分是我整理的12个真实项目中踩过的坑,按发生频率排序。每个问题都包含现象、根因、验证方法和终极解决方案。

5.1 问题速查表

现象可能根因快速验证命令终极方案
CPU使用率100%但代码很短GIL争用(多线程CPU密集型)py-spy top -p PID看是否卡在PyEval_EvalFrameEx改用multiprocessing或C扩展
内存持续增长不释放循环引用(尤其带__del__的类)gc.get_objects()找可疑对象weakref打破引用,或显式gc.collect()
首次请求极慢,后续正常JIT未生效(PyPy)或模块导入延迟time python -c "import heavy_module"预热导入:if __name__ == '__main__': import heavy_module
多进程间内存暴涨fork()时copy-on-write失效(如NumPy大数组)`ps aux --sort=-%memhead -10`
异步代码比同步还慢await中混入CPU密集操作py-spy record -p PID --duration 10看是否在asyncio栈中loop.run_in_executor()卸载到线程池

5.2 典型问题深度解析

问题1:GIL导致多线程无法并行

现象:用ThreadPoolExecutor(max_workers=8)处理CPU密集型任务,8线程耗时几乎等于1线程。

根因:CPython的全局解释器锁(GIL)确保同一时刻只有一个线程执行Python字节码。多线程对CPU密集型任务无效,只对I/O密集型有效。

验证

import threading, time def cpu_task(): s = 0 for i in range(10**7): s += i * i return s # 单线程 start = time.time() for _ in range(4): cpu_task() print(f"Single thread: {time.time()-start:.2f}s") # 四线程 start
http://www.cnnetsun.cn/news/3360491.html

相关文章:

  • 前端开发者如何高效应用AIGC技术
  • GoogleTest集成Abseil:C++单元测试框架的依赖治理与生态演进
  • CSS + Vue3 + TS + Setup 实现 7 种经典呼吸灯效果(前端常用动画合集) - 附完整示例(组件复用)
  • C++智能指针实战指南:从RAII原理到内存安全编程
  • 43.嵌入式C语言进阶:位带操作——给每个比特一个专属地址,解决GPIO操作的竞态问题
  • ArkUI-X与Unity UI Toolkit跨平台UI渲染性能实测对比
  • C++ DLL注入技术详解:从原理到实战实现远程线程注入
  • C++实现中国剩余定理:从数论原理到工程实践详解
  • 具身智能论文(1)--集群协作与任务分配
  • C# + WPF 做了一个 AI 文档批量处理工具,我是怎么从 0 到发布的
  • C++实现亚像素级碰撞检测:高精度物理引擎的核心算法与优化
  • JS--URL对象全解析:从location到URLSearchParams的现代实践
  • 多伦多大学ECE1508深度生成AI课程:VAE、GAN与扩散模型实战指南
  • 从CLI到Web:构建双模代码安全扫描工具的架构设计与实战
  • 3D高斯泼溅网页查看器:照片级真实感渲染技术解析
  • Linux权限机制解析与实战操作指南
  • OpenHarmony 单元测试 HUnit 自动化测试(API Version23+)
  • 每秒百万条日志入库:十亿级用户行为实时数据分析平台架构落地
  • 意大利语购物场景实战:从听懂报价到地道表达“我要了”
  • python-128-KeyCloak学习之(二)脚本调用接口手动复制keycloak中的公钥
  • 星地通信的方式
  • MySQL 系统学习 第四阶段:MySQL 高级 第六节:MVCC(Multi-Version Concurrency Control,多版本并发控制)
  • 从零实现C++哈希表:深入理解unordered_map底层原理与性能优化
  • C++错误处理实战:从错误码到异常安全与RAII资源管理
  • Android小游戏开发实战:从Canvas绘制到游戏循环设计
  • static关键字的四种用法:面试官问“static有几种用法”,90%的人答不全
  • 类与对象:面试官让我用机器人传感器解释封装,我讲了五分钟
  • 01 Java 程序员为什么要聊 AIGC?现在面试到底卷成什么样了
  • Unity DOTS避坑指南:从OOP思维到ECS高性能开发的实战解析
  • SVG 与 Canvas 在前端开发中的区别与应用场景详解