select和poll之间的性能对比

poll 通过 revents 字段直接标记事件,而 select 需要遍历三个独立的位图。这种设计差异直接导致了性能上的差距。以下是详细对比:


1. poll 的事件检测:单次遍历 + 直接标记

(1)pollfd 结构体

cpp 复制代码
struct pollfd {
    int fd;         // 文件描述符
    short events;   // 用户关心的事件(如 POLLIN、POLLOUT)
    short revents;  // 内核填充的实际发生的事件
};
  • events :用户设置的监听事件掩码(如 POLLIN | POLLHUP)。
  • revents :由内核填充,表示该 fd 上实际发生的事件(如 POLLIN | POLLERR)。

(2)内核处理逻辑

  1. 用户调用 :

    cpp 复制代码
    struct pollfd fds[1000];
    for (int i = 0; i < 1000; i++) {
        fds[i].fd = ...;
        fds[i].events = POLLIN; // 只关心可读事件
    }
    poll(fds, 1000, timeout);
  2. 内核行为 :

    • 单次遍历 :内核直接遍历 fds 数组(1000 次)。
    • 直接标记 :
      • 对每个 fds[i],内核检查 fd 的状态。
      • 如果事件发生(如可读),则将 revents 设置为对应标志(如 POLLIN)。
      • 无需额外遍历或位操作。
  3. 用户检查 :

    cpp 复制代码
    for (int i = 0; i < 1000; i++) {
        if (fds[i].revents & POLLIN) {
            // fd 可读
        }
    }

(3)优势

  • 高效 :内核只需遍历一次数组,且直接填充 revents,无冗余操作。
  • 灵活 :revents 可以同时标记多个事件(如 POLLIN | POLLHUP)。

2. select 的事件检测:三次遍历 + 位图操作

(1)select 的参数

cpp 复制代码
int select(
    int nfds,          // 最大 fd + 1
    fd_set *readfds,   // 可读事件位图
    fd_set *writefds,  // 可写事件位图
    fd_set *exceptfds, // 异常事件位图
    struct timeval *timeout
);
  • 三个位图:分别对应读、写、异常事件。
  • nfds :强制内核遍历 0 到 nfds-1 的所有 fd,即使未被监听。

(2)内核处理逻辑

  1. 用户调用 :

    cpp 复制代码
    fd_set read_fds, write_fds;
    FD_ZERO(&read_fds);
    FD_ZERO(&write_fds);
    for (int fd = 3; fd <= 1002; fd++) {
        FD_SET(fd, &read_fds);  // 监听 1000 个 fd 的可读事件
    }
    select(1003, &read_fds, NULL, NULL, NULL);
  2. 内核行为 :

    • 拷贝位图 :将 read_fds、write_fds、except_fds 从用户空间拷贝到内核。
    • 三次遍历 :
      1. 遍历 read_fds :
        • 对每个 fd(从 0 到 1002):
          • 如果 FD_ISSET(fd, &read_fds) 为真,检查是否可读。
          • 如果可读,保留该 fd 在位图中(内核可能直接修改位图)。
      2. 遍历 write_fds(同理)。
      3. 遍历 except_fds(同理)。
    • 无效操作 :
      • 即使 fd=0,1,2 未被设置,内核仍会检查它们(共 3 次无效操作)。
      • 如果 fd 数量更多(如 10,000),无效操作会线性增加。
  3. 用户检查 :

    cpp 复制代码
    for (int fd = 3; fd <= 1002; fd++) {
        if (FD_ISSET(fd, &read_fds)) {
            // fd 可读
        }
    }

(3)劣势

  • 低效 :
    • 内核需要三次独立遍历(读、写、异常),且每次遍历都可能涉及全局扫描。
    • 每次遍历需通过 FD_ISSET 位操作检查 fd 是否被监听。
  • 局限性 :
    • 受 FD_SETSIZE 限制(通常 1024),无法直接支持大量 fd。
    • 位图操作在 fd 稀疏时(如跳过前 1000 个 fd)浪费大量计算。

3. 直观对比:poll vs select 的事件检测

步骤 poll select
事件标记方式 revents 字段直接填充 三个独立位图(读、写、异常)
内核遍历次数 1 次(遍历 pollfd 数组) 3 次(遍历三个位图)
无效 fd 检查 无(仅检查用户提供的 fd) 有(必须检查 0 到 nfds-1)
位图操作 无 每次检查需 FD_ISSET 位操作
支持 fd 数量 无限制(仅受系统资源限制) 受 FD_SETSIZE 限制(通常 1024)

4. 为什么 poll 更高效?

  1. 单次遍历 vs 三次遍历 :
    • poll 只需遍历用户提供的 fd 列表一次,而 select 需要遍历三个位图各一次。
  2. 直接标记 vs 位图操作 :
    • poll 的 revents 直接填充事件标志,无需位操作。
    • select 需通过 FD_ISSET 检查位图,额外开销大。
  3. 无效操作优化 :
    • poll 完全跳过未监听的 fd,而 select 必须检查所有 fd(即使未被监听)。

5. 代码示例对比

(1)poll 检测可读事件

cpp 复制代码
struct pollfd fds[1000];
for (int i = 0; i < 1000; i++) {
    fds[i].fd = 3 + i;
    fds[i].events = POLLIN;
}
poll(fds, 1000, timeout);

for (int i = 0; i < 1000; i++) {
    if (fds[i].revents & POLLIN) {
        printf("fd %d is readable\n", fds[i].fd);
    }
}

(2)select 检测可读事件

cpp 复制代码
fd_set read_fds;
FD_ZERO(&read_fds);
for (int fd = 3; fd <= 1002; fd++) {
    FD_SET(fd, &read_fds);
}
select(1003, &read_fds, NULL, NULL, timeout);

for (int fd = 3; fd <= 1002; fd++) {
    if (FD_ISSET(fd, &read_fds)) {
        printf("fd %d is readable\n", fd);
    }
}

6. 总结

  • poll 的 revents 设计 :
    通过单次遍历和直接标记事件,避免了 select 的三次遍历和位图操作,因此性能更高。
  • select 的局限性 :
    全局扫描和位图操作使其在处理大量 fd 时效率低下,且受 FD_SETSIZE 限制。
  • 适用场景 :
    • 高并发(大量 fd)时,优先使用 poll(或更现代的 epoll/kqueue)。
    • 低并发或遗留代码中,select 仍可能被使用,但性能较差。
相关推荐
Pin_Qing2 分钟前
账单没超,请求先被拒了
大数据·开发语言·人工智能
2601_962885724 分钟前
如何用 Python 计算 VaR 和 CVaR(在险价值)?
开发语言·python
yuniko-n10 分钟前
【算法】约瑟夫环问题
java·算法
郝亚军19 分钟前
manifest 中 `sha256` 与 `signValue` 的作用
java·开发语言·数据结构
l1t33 分钟前
测试DuckDB v2.1.0-alpha49002的bignum乘法
数据库·sql·算法·duckdb
譕痕34 分钟前
stripe 实例
java·开发语言·stripe·checksession
Nil20841 分钟前
LeetCode 283移动零
数据结构·算法·leetcode
墨子夕42 分钟前
设计模式——Java × Python 双语言精讲 · 23 个经典模式 + 现代演进(前言篇)
java·开发语言·设计模式
_菜鸟果果1 小时前
高德地图 JS API 2.0 在国产 Linux/ARM 电脑上渲染失败 的排查与解决
linux·开发语言·javascript
CQU_JIAKE1 小时前
10.18【A】
算法