‌Node.js内存泄漏:发现、修复与预防的实战指南‌


目录

  1. 概述
  2. 内存泄漏的概念
  3. 常见的内存泄漏类型
  4. 环境与工具准备
  5. 发现内存泄漏

  6. 案例演示:从零到一排查内存泄漏

  7. 修复常见内存泄漏模式

  8. 预防策略与最佳实践
  9. 总结

概述

在生产环境的 Node.js 应用中,内存泄漏(Memory Leak)是一个难以察觉却会逐渐累积,最终导致进程崩溃或 OOM(Out of Memory)的严重问题。常见场景如长时间运行的服务(API Server、微服务、爬虫任务、实时推送等),一旦发生泄漏,内存占用会逐步上涨,直到系统无法调度。本文将从“什么是内存泄漏”讲起,列举常见的泄漏类型,并详细演示如何使用 Node 自带和第三方工具进行诊断、定位与修复,同时给出预防策略,帮助你在项目中真正做到“零泄漏”。


内存泄漏的概念

内存泄漏:指程序在运行过程中申请了内存资源,但因逻辑缺陷导致这段内存永远无法被回收(GC),随着时间推移,泄漏区域不断累积,使得进程可用内存持续走高。

在 V8 引擎中,垃圾回收(GC, Garbage Collection)会跟踪可达性(Reachability):当对象不再可达(没有任何引用链指向它),才有资格被回收。内存泄漏往往是因为“对象依然可达,但已不使用”,导致 GC 无法释放,进而累积。

下面是一张简单的示意图(ASCII 图),说明可达性与 GC 释放行为:

[Root]  
  ├── objA  <── 业务中大量使用的对象  
  └── objB  <── 长期保留,引用了 data1、data2  

GC 扫描:  
- Root 引用 objA:objA 属于活动对象,正常保留  
- Root 引用 objB:objB 也被视作活动对象  

如果 objB 持有对 dataN 的引用,而业务逻辑已不再需要 dataN,就会造成 dataN“挂在内存”一直不被回收:
  objB --> data1  
           data2  
           ...  
           dataN  

objB 本身是一个长生命周期对象(例如单例缓存、全局容器、长连客户端等),而里面存放的 dataN 并未随着业务完成而清理,就形成了内存泄漏。


常见的内存泄漏类型

  1. 全局变量或单例持有过多引用

    • 将大对象直接挂载到全局上下文(globalprocess 或模块级变量),导致其永远不会被 GC。
    • 缓存(Cache)或 Map/Set 无限制增长,旧数据不清理。
  2. 定时器 / 周期任务未清理

    • setInterval()setTimeout() 里引用了闭包内存,如果不恰当调用 clearInterval/clearTimeout,会持续持有闭包状态。
  3. 事件监听器累积

    • EventEmitter 没有及时 removeListeneroff,导致同一个事件不断堆积监听器,且监听函数往往持有上下文闭包。
  4. 流 (Stream) 未关闭 / 数据未消费

    • 文件、网络流 (Readable/Writable) 未 .destroy().end(),底层缓冲区不断积累。
  5. 闭包导致的意外保留

    • 在函数作用域中,内部函数(闭包)引用了大量外部变量,长时间保留导致外部变量无法 GC。
  6. 第三方库使用不当

    • 某些库内部会保存引用(如 ORM 的实体管理、缓存库 CacheManager),如果配置不当,可能导致泄漏。

环境与工具准备

在动手实践前,我们需要安装和了解以下环境和工具:

  1. Node.js 环境

    • 推荐使用 Node.js 14+ 或 16+,确保内置的 --inspectprocess.memoryUsage() 功能可用。
  2. Chrome/Edge DevTools

    • Node 允许通过 node --inspect 启动后,在 Chrome 浏览器中访问 chrome://inspect 对进程进行调试和 Heap 快照分析。
  3. 第三方诊断库(可选,根据场景选择)

    • heapdump:生成 .heapsnapshot 文件,方便在 DevTools 中加载和分析。
    • clinic(原名 Clinic.js):由 NearForm 出品,集成了 clinic doctorclinic flameclinic bubbleprof,能够自动探测内存泄漏并给出可视化报告。
    • memwatch-next:能够在 Node 进程中触发 leak 事件并打印 diff,但新版本兼容性需要注意。

下面先通过 npm 安装常见库示例:

npm install --save-dev heapdump clinic memwatch-next

发现内存泄漏

在发现内存泄漏环节,我们主要分三步:一、监控内存增长趋势;二、采集 Heap 快照;三、对比分析,定位泄漏源

5.1 使用 process.memoryUsage() 游戏化监控

process.memoryUsage() 会返回当前 Node 进程的内存占用情况,示例输出:

{
  rss: 24698432,         // Resident Set Size,包含代码段、堆、栈、C++ 对象等
  heapTotal: 4030464,    // V8 用到的堆总量
  heapUsed: 2854176,     // V8 实际使用的堆大小
  external: 8232,        // C++ 对象占用的内存
}

示例代码:

// monitor.js
setInterval(() => {
  const mem = process.memoryUsage();
  console.log(
    `[${new Date().toISOString()}] heapUsed: ${(mem.heapUsed / 1024 / 1024).toFixed(2)} MB, ` +
    `heapTotal: ${(mem.heapTotal / 1024 / 1024).toFixed(2)} MB, ` +
    `rss: ${(mem.rss / 1024 / 1024).toFixed(2)} MB`
  );
}, 5000);

运行时,结合你自己的业务逻辑模块一起启动,比如:

node --inspect app.js monitor.js

接下来,你会在控制台看到每 5 秒一次的内存占用变化。若在持续负载或压力测试场景下,heapUsed 一直回不下降,反而呈持续增长趋势,就非常可能存在内存泄漏。

示意图(内存趋势)

 ▲ heapUsed (MB)
 │
 │                              /
 │                          /
 │                      /
 │                 /
 │            / 
 │      /
 │  /
 └──────────────────────────► time
   t0          t1   t2   t3   t4

理想情况下,若业务有周期性请求,heapUsed 会在 GC 后回落;但若曲线越走越高,趋势线不会回调,就要进行深度分析。


5.2 通过 Chrome DevTools 进行 Heap 快照分析

  1. 启动带调试标志的进程

    node --inspect-brk app.js
    • --inspect-brk 会在第一行代码前暂停,启动后在浏览器中打开 chrome://inspect,点击“inspect”连接到 Node 进程。
  2. 现场监控并触发快照

    • 在 DevTools 的 Memory 面板,可以看到 Heap Snapshot、Allocation instrumentation on timeline、Allocation sampling 等选项。
    • 常用流程:先采集一份基线快照(Snapshot A),然后让应用运行一段时间、或模拟一定负载(如发起 N 次接口调用),再采集第二份快照(Snapshot B)。
  3. 对比两次快照差异

    • 在第二份快照中,将切换到“Comparison”视图,或直接查看在第二次快照中新出现或数量增多的对象(尤其是 Detached DOM trees、Closure、Array、Object 等)。
    • 分析 Retainers Tree(保留路径)可以看到哪些对象引用链中始终保持对泄漏对象的强引用,从而定位泄漏源所在的模块或函数。
示例场景:假设 WebSocket 每次消息回调中都向数组 msgs push,而从不清理,时间一长 msgs 会一直增大。快照中会看到 Array 对象在 Diff 中暴涨,点击进入点击进入 Retainers 树,一路往上就是 WebSocket 回调中的 msgs 变量,进一步确认泄漏。

5.3 借助第三方诊断工具(heapdump、clinic、memwatch)

5.3.1 使用 heapdump 生成 .heapsnapshot

安装:

npm install --save heapdump

示例代码:

// app.js
const heapdump = require('heapdump');
const express = require('express');
const app = express();
const port = 3000;

let dataStore = [];

app.get('/leak', (req, res) => {
  // 故意往 dataStore 放大量对象
  for (let i = 0; i < 1000; i++) {
    dataStore.push({ idx: i, timestamp: Date.now(), payload: new Array(1000).fill('x') });
  }
  res.send('Leaked some objects.');
});

// 触发生成 Heap Snapshot
app.get('/snapshot', (req, res) => {
  const filename = `./${Date.now()}.heapsnapshot`;
  heapdump.writeSnapshot(filename, (err, filePath) => {
    if (err) return res.status(500).send(err.message);
    res.send(`Heap snapshot written to ${filePath}`);
  });
});

app.listen(port, () => {
  console.log(`App listening at http://localhost:${port}`);
});

运行 node --inspect app.js,在浏览器或 Postman 中访问 /leak 接口多次,随后访问 /snapshot,会在项目根目录生成 .heapsnapshot 文件。打开 Chrome DevTools 的 Memory 面板,点击“Load”导入该文件,即可进行分析。

5.3.2 使用 clinic 做自动化诊断

安装:

npm install --global clinic

运行示例:

clinic doctor -- node app.js
  • clinic doctor 会启动 Node 进程,并在页面中开启自动检测。你可以持续对 /leak 发请求,直到诊断工具提示内存泄漏。结束后,会生成一个 HTML 报告,帮助定位瓶颈。

另外 clinic flame 可以生成火焰图,用于 CPU 性能剖析,clinic bubbleprof 用于异步流程剖析。单纯查内存泄漏时,clinic doctor 最为方便。

5.3.3 使用 memwatch-next 监听 Leak 事件

注意memwatch-next 与部分新版本 Node 兼容性有差异,实践时需注意版本选择。

安装:

npm install --save memwatch-next

示例代码:

// memwatch_example.js
const memwatch = require('memwatch-next');

memwatch.on('leak', (info) => {
  console.error('内存泄漏警告:', info);
});

let arr = [];
setInterval(() => {
  for (let i = 0; i < 100; i++) {
    arr.push({ time: Date.now(), data: new Array(1000).fill('y') });
  }
  // 定期打印内存
  const mem = process.memoryUsage();
  console.log(`heapUsed: ${(mem.heapUsed / 1024 / 1024).toFixed(2)} MB`);
}, 2000);

当泄漏过多时,memwatch 会触发 leak 事件并打印信息。你可以根据 info 中的 growthreason 等字段来判断泄漏速度。


案例演示:从零到一排查内存泄漏

下面通过一个完整案例,演示如何构造、诊断并修复内存泄漏。

6.1 构造故意泄漏的示例

在本例中,我们用一个简单的 HTTP Server,不断把请求数据存到全局数组中,却永远不进行清理,模拟典型的“缓存无限增长”场景。

// leak_demo.js
const http = require('http');
const url = require('url');

const PORT = 4000;

// 全局存储,故意不清理
let cache = [];

const server = http.createServer((req, res) => {
  const { pathname, query } = url.parse(req.url, true);

  if (pathname === '/add') {
    // 把 query.data 推到 cache
    cache.push({
      data: query.data || 'default',
      timestamp: Date.now(),
      payload: new Array(5000).fill('*') // 占内存
    });
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end(`Added item. Current cache size: ${cache.length}`);
  } else if (pathname === '/status') {
    // 返回简单检测数据
    res.writeHead(200, { 'Content-Type': 'application/json' });
    const mem = process.memoryUsage();
    res.end(JSON.stringify({
      cacheSize: cache.length,
      heapUsedMB: (mem.heapUsed / 1024 / 1024).toFixed(2),
      heapTotalMB: (mem.heapTotal / 1024 / 1024).toFixed(2),
    }));
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
});

server.listen(PORT, () => {
  console.log(`Leak demo server listening on http://localhost:${PORT}`);
});

使用步骤:

  1. 在终端启动:

    node --inspect leak_demo.js
  2. 打开另一个终端,用 curl 或浏览器重复调用 /add

    for i in {1..200}; do curl "http://localhost:4000/add?data=item${i}"; done
  3. 同时观察 /status 输出:

    curl http://localhost:4000/status

你会发现 cacheSize 不断增长,heapUsedMB 也随之攀升,很快就出现内存高企的现象。


6.2 通过监控与快照定位泄漏位置

6.2.1 监控内存趋势

/status 或者通过 process.memoryUsage() 定时打印,可以看到类似:

{
  "cacheSize": 50,
  "heapUsedMB": "30.12",
  "heapTotalMB": "40.00"
}
...
{
  "cacheSize": 150,
  "heapUsedMB": "80.43",
  "heapTotalMB": "100.00"
}
...
{
  "cacheSize": 300,
  "heapUsedMB": "155.22",
  "heapTotalMB": "200.00"
}

趋势图(示意):

 ▲ heapUsedMB
 │         *
 │       *
 │     *
 │   *
 │ *
 └──────────────────▶ time
   0s  30s  60s  90s...

可以确认泄漏存在。

6.2.2 采集 Heap Snapshot

  1. 在 DevTools Memory 面板中点击“Take Heap Snapshot”,等待采集完成,命名为 Snapshot A。
  2. 再调用更多 /add 请求,比如再加 200 次,继续采集 Snapshot B。

6.2.3 分析快照差异

在第二次快照的“Comparison”视图中,会看到大量 ArrayObjectBuffer 等节点实例迅速增加。展开其中一个突然变大的 Array,可以在 Retainers 路径中看到:

Global (window)  
  └── cache (Array)  
        └── [i] (Object)  
              └── payload (Array)  

说明全局的 cache 数组里存放了大量对象,才造成了占用不断增长。结合源码位置(cache.push(...)),就可以定位到是 /add 路由里没有做清理。


6.3 修复示例代码并验证结果

要修复“缓存无限增长”模式,可以采取以下几种策略:

  • 设置缓存上限:当超过一定长度时,自动清理最旧数据。
  • 定期过期清理:按时间戳过滤掉过期数据。
  • 持久化到外部存储:将不常用数据序列化到磁盘或数据库,减少内存压力。

下面给出一个简单的“固定大小环形缓存”实现,保证 cache 长度不超出 100:

// fix_demo.js
const http = require('http');
const url = require('url');

const PORT = 4001;

// 环形缓存,最大长度 = 100
class RingBuffer {
  constructor(limit) {
    this.limit = limit;
    this.data = [];
  }

  push(item) {
    if (this.data.length >= this.limit) {
      // 删除最早的元素
      this.data.shift();
    }
    this.data.push(item);
  }

  size() {
    return this.data.length;
  }
}

const cache = new RingBuffer(100);

const server = http.createServer((req, res) => {
  const { pathname, query } = url.parse(req.url, true);

  if (pathname === '/add') {
    cache.push({
      data: query.data || 'default',
      timestamp: Date.now(),
      payload: new Array(5000).fill('*')
    });
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end(`Added item. Current cache size: ${cache.size()}`);
  } else if (pathname === '/status') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    const mem = process.memoryUsage();
    res.end(JSON.stringify({
      cacheSize: cache.size(),
      heapUsedMB: (mem.heapUsed / 1024 / 1024).toFixed(2),
      heapTotalMB: (mem.heapTotal / 1024 / 1024).toFixed(2),
    }));
  } else {
    res.writeHead(404);
    res.end('Not Found');
  }
});

server.listen(PORT, () => {
  console.log(`Fix demo server listening on http://localhost:${PORT}`);
});

验证步骤:

  1. 启动修复后的服务:

    node --inspect fix_demo.js
  2. 循环调用 /add 多次(超过 100),同时持续观察 /status

    for i in {1..500}; do curl "http://localhost:4001/add?data=item${i}"; done
  3. 你会发现 cacheSize 永远不会超过 100,heapUsedMB 随后缓慢稳定甚至有周期性下降,不会再无限制增长。

再次采集 Heap Snapshot,可以看到已不再有对应的 cache 泄漏路径,说明修复成功。


修复常见内存泄漏模式

前文演示了“缓存无限增长”场景。接下来,我们分别针对其他常见模式展开示例和修复方案。

7.1 闭包与全局变量导致的泄漏

问题描述

// closure_leak.js
let savedCallback;

function registerHandler() {
  const largeArray = new Array(1000000).fill('leak');
  savedCallback = () => {
    console.log(largeArray.length);
  };
}

registerHandler();
// 此时 savedCallback 持有 largeArray,导致 largeArray 无法被回收
  • savedCallback 是一个全局变量,保存了对 largeArray 的引用,即使 registerHandler 执行完毕,largeArray 依然可达。

修复方法

  • 避免将大对象直接挂全局:如果确实需要缓存,应当在不再需要时显式将引用置为 null
  • 使用弱引用:可以借助 WeakMapWeakRef 等,让 GC 有机会回收。
// closure_fix.js
let savedCallback;

function registerHandler() {
  const largeArray = new Array(1000000).fill('leak');

  // 只保留必要数据,比如长度,而不是整个数组
  const length = largeArray.length;
  savedCallback = () => {
    console.log(length);
  };

  // 手动释放大对象
  // 注意:仅当后续不再使用 largeArray 时
  // largeArray = null; // 这里 largeArray 是局部变量,可直接出栈,不用显式清空
}

registerHandler();
// largeArray 已经不再被引用,GC 可回收

或者使用 WeakRef(Node.js 14+ 提供):

// 使用 WeakRef 缓存对象
let savedRef;

function registerHandler() {
  const largeObj = { data: new Array(1000000).fill('x') };
  savedRef = new WeakRef(largeObj);
  // largeObj 超出作用域后,如果没有其他强引用,GC 可回收
}

registerHandler();

// 后续使用时
const deref = savedRef.deref();
if (deref) {
  console.log(deref.data.length);
} else {
  console.log('对象已经被回收');
}

7.2 定时器未清理

问题描述

// interval_leak.js
function startTask() {
  setInterval(() => {
    // 这里引用外部大数据
    const arr = new Array(500000).fill('interval');
    console.log(arr.length);
    // 不清理,匿名函数会一直执行并持有闭包
  }, 1000);
}

startTask();
// 如果不主动 clearInterval,setInterval 会一直跑下去

修复方法

  • 保存定时器 ID,必要时清理
// interval_fix.js
let intervalId;

function startTask() {
  intervalId = setInterval(() => {
    const arr = new Array(500000).fill('interval');
    console.log(arr.length);
  }, 1000);
}

// 运行一段时间后,清理定时器
function stopTask() {
  clearInterval(intervalId);
  intervalId = null; // 释放引用
}

startTask();

// 10 秒后停止任务
setTimeout(stopTask, 10000);
  • 短生命周期任务尽量用 setTimeout 代替 当任务无需持续运行时,用 setTimeout 更直观。

7.3 事件监听器累积

问题描述

// listener_leak.js
const EventEmitter = require('events');
const emitter = new EventEmitter();

function registerListener() {
  emitter.on('data', (payload) => {
    // 假设回调持有大量闭包数据
    const big = new Array(100000).fill('listener');
    console.log(`Received: ${payload}`);
  });
}

// 每次调用都会新增一个 listener
setInterval(() => {
  registerListener();
  emitter.emit('data', 'hello');
}, 1000);
  • 随着 registerListener 不断被调用,emitter 上会积累大量 listener,且每个 listener 的闭包都持有内存。

修复方法

  • 如果不再需要某个 listener,要及时 removeListener
// listener_fix.js
const EventEmitter = require('events');
const emitter = new EventEmitter();

function onData(payload) {
  const big = new Array(100000).fill('listener');
  console.log(`Received: ${payload}`);
}

function registerAndUnregister() {
  emitter.on('data', onData);
  emitter.emit('data', 'hello');
  // 触发一次后立即移除
  emitter.removeListener('data', onData);
}

setInterval(registerAndUnregister, 1000);
  • 减少不必要的重复订阅:如果只是想实时监听一次,可使用 once 代替 on

7.4 缓存/Map/Set 无上限增长

问题描述

// map_leak.js
const cacheMap = new Map();

function cacheData(key, data) {
  cacheMap.set(key, data);
}

// 假设 key 永远不同,且从不清理
setInterval(() => {
  const key = Date.now().toString();
  cacheData(key, new Array(10000).fill('map'));
}, 500);
  • cacheMap 对象持续增加键值对,没有清理策略,会导致内存不断攀升。

修复方法

  • 引入最大缓存容量,达到阈值后删除最旧条目。可以利用 Map 保证插入顺序:
// map_fix.js
const MAX_CACHE_SIZE = 100;
const cacheMap = new Map();

function cacheData(key, data) {
  if (cacheMap.size >= MAX_CACHE_SIZE) {
    // 删除最早插入的元素,Map iterator 保证顺序
    const firstKey = cacheMap.keys().next().value;
    cacheMap.delete(firstKey);
  }
  cacheMap.set(key, data);
}

setInterval(() => {
  const key = Date.now().toString();
  cacheData(key, new Array(10000).fill('map'));
}, 500);
  • 使用弱引用容器 WeakMap/WeakSet:仅当你能保证“某对象一旦没有其他强引用即可丢弃缓存”的场景,可使用 WeakMap 缓存临时对象。但是不能遍历,也无法手动删除单个键。

预防策略与最佳实践

在实际项目中,“发现并修复”往往代价高昂,还会给线上服务带来风险。因此日常开发时,应当遵循以下预防策略:

  1. 模块化与职责分离

    • 将各项资源(缓存、定时器、事件监听)集中在可控对象中,方便统一清理。
    • 例如:在某个业务模块销毁时,统一调用 dispose(),释放所有定时器、事件监听、缓存引用。
  2. 合理使用作用域与引用

    • 少在全局范围定义大对象,尽量将对象局部化,让 GC 能更快回收。
    • 若必须保留长生命周期对象,关注它所持有的子引用的数据量,并定期做清理。
  3. 定期监控与自动告警

    • 利用 process.memoryUsage()、或者 APM(Application Performance Monitoring)/日志系统,实时上报内存使用数据。
    • heapUsed 超过阈值时,触发告警并自动收集 Heap Snapshot。
  4. 使用稳定的第三方库

    • 选用社区信赖度高且维护良好的组件(例如 Redis 作为分布式缓存,替代进程内缓存)。
    • 如果必须在进程内缓存,可使用已经实现了 LRU 淘汰策略的现成库,如 lru-cache
  5. 资源使用后及时清理

    • 文件、网络流、数据库连接、Child Process 等资源使用完毕后,务必 .destroy().end().close()
    • 在单元测试或集成测试时,模拟高并发、长时间运行场景,检测是否有“未关闭”资源导致泄漏。
  6. 避免不必要的闭包引用

    • 当编写回调函数时,尽量只闭包所需数据,减少对外部大对象的无效引用。
    • 可以将大型数据或上下文拆分成小对象,按需传递,避免给闭包带来整个父对象。
  7. 代码审查与测试用例

    • 在 PR 评审中重点关注“是否可能引入长时间持有引用”的变更。
    • 编写“内存泄漏回归测试”,比如用 mochajest,模拟短时间内多次调用接口,检测 heapUsed 是否回落。
  8. 及时升级 Node.js 版本、应用补丁

    • 早期版本的 V8、Node.js 存在已知内存泄漏漏洞,定期升级能修复底层引擎的缺陷。

总结

Node.js 的内存泄漏问题,既可能来源于自身业务逻辑,也可能由第三方库不当使用导致。不论是“缓存无限增长”“闭包过度持有”“未清理定时器或事件监听器”,还是“流/连接未关闭”,本质都是“对象持续可达,导致 GC 无法释放”。

本文系统地介绍了:

  1. 内存泄漏的概念与可达性原理
  2. 如何使用 process.memoryUsage()、Chrome DevTools Heap 快照、heapdumpclinicmemwatch 等手段进行监控与诊断
  3. 通过示例演示如何定位“缓存无限增长”泄漏,并演示修复后的验证过程
  4. 总结并剖析了常见的闭包、定时器、事件监听、缓存类泄漏模式,给出对应的修复代码示例
  5. 给出了日常预防策略:模块化设计、合理引用、代码审查与测试、使用成熟组件、持续监控与告警

希望本指南能帮助你在实际项目中:

  • 迅速发现:通过监控趋势快速察觉内存曲线异常;
  • 有效定位:利用快照与第三方工具精准找到泄漏根源;
  • 彻底修复:按场景使用合适的清理/淘汰策略,让内存恢复健康;
  • 长效预防:通过最佳实践避免新的隐患。

最后,记得定期关注 Node.js 与 V8 的发布日志,保持依赖库的更新,以便修复底层内存管理的潜在问题。祝你在 Node.js 内存管理方面游刃有余,打造高可用、零泄漏的生产级服务!


附录:常用工具 & 资源链接(可自行收藏)

最后修改于:2025年05月30日 11:10

评论已关闭

推荐阅读

DDPG 模型解析,附Pytorch完整代码
2024年11月24日
DQN 模型解析,附Pytorch完整代码
2024年11月24日
AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日