---
title: 一次nginx返回文件性能差异问题定位-官方技术文章-鲲鹏社区
description: 某场景中，同样的硬件、内核版本，不同OS，nginx返回文件的性能差距一倍。经分析原因是glibc的fstat函数使用了不同的系统调用实现。
keywords: 一次,返回文件性能,差异问题定位,官方技术文章,鲲鹏社区,问题现象,问题分析,方案验证
url: https://www.hikunpeng.com/developer/techArticles/20250705-2?envFlag=1
section: (其他)
---

# 一次nginx返回文件性能差异问题定位-官方技术文章-鲲鹏社区

URL: https://www.hikunpeng.com/developer/techArticles/20250705-2?envFlag=1
描述: 某场景中，同样的硬件、内核版本，不同OS，nginx返回文件的性能差距一倍。经分析原因是glibc的fstat函数使用了不同的系统调用实现。
关键词: 一次,返回文件性能,差异问题定位,官方技术文章,鲲鹏社区,问题现象,问题分析,方案验证

官方技术文章 [了解详情](https://www.hikunpeng.com/zh/developer/techArticles)

一次nginx返回文件性能差异问题定位

一次nginx返回文件性能差异问题定位

案例分享

发表于 2025/07/05

0

作者 | 吴亦航

## 问题现象

某场景在CentOS 7.6和openEuler 22.03 LTS SP2上分别对nginx进行HTTP请求压测，测试观察到服务端nginx返回静态文件和不返回静态文件时的性能差距很大。返回静态文件的nginx配置：

```
        location / {
            root         /usr/share/nginx/some_file;
        }

```

此时openEuler的QPS仅为CentOS一半。但若不返回静态文件，只令nginx返回内存中一段等长且预设好的数据（测试时512字节的数据，此处仅示意）：

```
        location = /index_64B.html {
            return 200 'some_512_bytes_data';
        }

```

此时两边QPS相近。

## 问题分析

上述现象说明nginx中“对文件的操作”影响了性能。由于读文件涉及多个系统调用，故使用strace对比openEuler和CentOS在整个测试期间的系统调用情况。使用以下命令抓取系统调用的统计。

```
strace -c -p {PID}
```

结果如下：

可以观察到，两边对文件相关的两个系统调用——打开文件和获取文件属性，用的是不同的实现。并且openEuler用的实现平均耗时显著高于CentOS。

用strace抓取“不返回静态文件”测试中的系统调用，发现系统调用只有recvfrom，writev，因此这个场景下openEuler和CentOS的性能相近。

事实上，在频繁访问静态文件的场景下，启用nginx的open_file_cache选项能极大地提升整体性能。这个选项会使nginx缓存文件描述符及相应的元数据，不用在每个请求中都执行打开、文件的操作。此时，系统调用只有recvfrom，pread64，writev。这种情况下测得两边的性能也非常接近。

为避免内核差异影响，已手动在两个OS上安装相同的内核，那么系统调用的差异大概率是由glibc版本差异引入的。经CentOS环境的glibc版本为2.17，openEuler环境的glibc版本为2.33。在openEuler社区上能看到相关讨论 [了解详情](https://gitee.com/src-openeuler/glibc/issues/I7C2H4?from=project-issue)，即openEuler 22.03 SP2里glibc的fstat函数使用了newfstatat系统调用作为实现，导致性能降低。SP3中重新用回fstat系统调用作为fstat函数的实现，不再存在这个性能问题。

## 方案验证

下载openEuler 22.03 LTS SP3版本里最新的glibc，glibc-common，glibc-devel，安装后进行测试，结果发现openEuler环境性能提升至与CentOS持平。

## 根因分析

对比相似场景下fstat和newfstatat的调用时间：

```
#include <linux/fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

const int NUM_ITERS = 1000000;
const char *FILE_NAME = "/usr/share/nginx/html/index_512B.html";

int main(int argc, char *argv[]) {
    struct timeval begin, end;
    long elapsedTime;
    int fd;

    struct stat sb;

    // fstat
    gettimeofday(&begin, NULL);
    fd = open(FILE_NAME, O_RDONLY | O_NONBLOCK);
    for (int i = 0; i < NUM_ITERS; ++i) {
#if !TEST_NEWFSTATAT
        fstat(fd, &sb);
#else
        fstatat(fd, "", &sb, AT_EMPTY_PATH);
#endif
    }
    close(fd);
    gettimeofday(&end, NULL);
    elapsedTime = end.tv_sec * 1000000 + end.tv_usec - begin.tv_sec * 1000000 - begin.tv_usec;

#if !TEST_NEWFSTATAT
    printf("Testing fstat: %d us\n", elapsedTime);
#else
    printf("Testing newfstatat: %d us\n", elapsedTime);
#endif
}

```

编译

```
gcc -o fstat test_fstat.c
gcc -DTEST_NEWFSTATAT -o newfstatat test_fstat.c

```

执行测试

```
time ./fstat
time ./newfstatat

```

结果如下，发现内核态耗时差距为一倍。

newfstatat性能差的原因

首先，newfstatat是一个比fstat更复杂的系统调用，会做一些额外的动作，本身会有一定耗时。

更为重要的是，newfstatat系统调用的传参形式为 (fd, "", &stat_buf, AT_EMPTY_PATH) ，其第一个参数是文件描述符，第二个参数是字符串格式的路径参数，即newfstatat既支持通过文件描述符调用，也支持传文件路径调用。相较之下，fstat系统调用的传参形式为 (fd, &statbuf) ，仅支持使用文件描述符调用。然而，newfstatat的灵活性导致在使用文件描述符调用时，也要传一个空的字符串过去。内核在处理该系统调用时，按规定 [了解详情](https://lwn.net/Articles/944512/)，需要判断这个字符串参数是什么，因而需要将用户内存中的字符串拷贝到内核内存（strncpy_from_user），这里会产生开销。事实上，所有*at()形式是系统调用都会受到影响。但在现实负载下，通常只有newfstatat的性能下降会被诟病，因为只有它的使用频率可能会很高 [了解详情](https://lwn.net/ml/libc-alpha/CAHk-=wg_6CYB0qDQ9eDfjZSsqzgKcF2ZMd2DObfeFc_N-PxUjg@mail.gmail.com/)（如与open搭配使用），导致性能问题会被积累放大。

glibc上游社区于2023-09-11在master分支合入了修复此问题的commit [了解详情](https://sourceware.org/git/?p=glibc.git;a=commit;h=551101e8240b7514fc646d1722f8b79c90362b8f)，于行文之时，该commit只存在于master分支，没有合入2.38及更低版本的分支。至于为什么glibc社区当初选择用性能更差的newfstatat系统调用 [了解详情](https://lwn.net/Articles/944214/)作为fstat函数的实现，Linus认为这只是社区演进中一个不经意的错误 [了解详情](https://lwn.net/ml/linux-kernel/CAHk-=wjrvn+J=z0_schGSROK0HCK-xs4wgky6pRKy7kVLhDeLg@mail.gmail.com/)。

src-openEuler/glibc仓于2023-10-20在openEuler-22.03-LTS-SP3分支中引入了上游社区的commit作为patch修复了fstat的性能问题，相应的glibc Release版本为2.34-138。因此在openEuler 22.03 SP3的镜像中，这个性能问题不复存在。

本页内容
