Linux 页面缓存性能与 memcpy

Linux 页面缓存性能与 memcpy

我正在对 Linux 将数据写入页面缓存而不限制进程或将数据同步到磁盘的情况下的写入性能进行基准测试。我正在做的简单实验如下所示:

long size = 1024;
int fd = open ("file", O_CREAT | O_RDWR | O_TRUNC, ...);
char *buffer = (char *) malloc (size)
**start_time = time.now ();**
write (fd, buffer, size);
**end_time = time.now ();**
close (fd);
printf ("write duration %d\n", end_time - start_time);

在这里,我希望有效地观察内存带宽,因为数据仅复制到操作系统的页面缓存,并且没有任何内容同步到磁盘。此外,操作系统尚未启动后台刷新或限制进程,因为脏率远低于background_dirty_ratio。不过,当我将它与内存带宽(相同大小的 memcpy 成本)进行比较时,它比 memcpy 贵得多:

char *buffer1 = (char *) malloc (size)
char *buffer2 = (char *) malloc (size)
**start_time = time.now ();**
memcpy (buffer1, buffer2, size);
**end_time = time.now ();**
printf ("memcpy duration %d\n", end_time - start_time);

例如,在我的系统(Linux 内核版本 4.2、CentOS)上,我看到 memcpy 带宽几乎为60GB/秒,写入带宽几乎是2GB/秒。据我了解,当调用写入系统调用时,它只是复制页面缓存(内存中)中的数据,并在复制完成后立即返回。所以我期望看到接近内存带宽的带宽。我还使用更大的数据(在操作系统开始限制进程之前)测试了相同的实验,以降低进行系统调用的成本。但我仍然看到几乎相同的结果。有谁知道为什么我在页面缓存上执行写入时没有观察内存带宽?

答案1

这里的主要因素是memcpy由 C 库处理,甚至直接由 C 编译器处理,而write是系统调用,由内核处理。

因此,您的memcpy运行是在进程中进行的,甚至可能没有函数调用的(微小)开销。write另一方面,具有系统调用的所有开销。特别是在您的测试中,由于写入大小很小,因此复制本身的成本与系统调用的成本相比可能相形见绌。即使进行较大的测试,系统调用成本可能仍然是主要因素。

要减少系统调用的重量,请尝试与更大的大小进行比较,必要时更改脏率配置,或者写入 atmpfs以避免写入磁盘的成本。您可能还想禁用 KPTI 和其他会增加系统调用成本的缓解措施,也许还可以研究一下 io_uring。

为了更好地了解处理 a 所涉及的工作write,您可以__x64_sys_write在 x86-64 上跟踪相应的系统调用;这将显示调用链和花费的时间,例如

  9)               |  __x64_sys_write() {
  9)               |    ksys_write() {
  9)               |      __fdget_pos() {
  9)               |        __fget_light() {
  9)   0.465 us    |          __fget_files();
  9)   0.943 us    |        }
  9)   1.155 us    |      }
  9)               |      vfs_write() {
  9)               |        rw_verify_area() {
  9)               |          security_file_permission() {
  9)               |            selinux_file_permission() {
  9)               |              __inode_security_revalidate() {
  9)               |                _cond_resched() {
  9)   0.034 us    |                  rcu_all_qs();
  9)   0.241 us    |                }
  9)   0.649 us    |              }
  9)               |              file_has_perm() {
  9)   0.034 us    |                bpf_fd_pass();
  9)               |                inode_has_perm() {
  9)   0.133 us    |                  avc_has_perm();
  9)   0.376 us    |                }
  9)   0.808 us    |              }
  9)   2.126 us    |            }
  9)   0.032 us    |            bpf_lsm_file_permission();
  9)   2.616 us    |          }
  9)   2.815 us    |        }
  9)               |        __vfs_write() {
  9)               |          new_sync_write() {
  9)               |            pipe_write() {
  9)               |              mutex_lock() {
  9)               |                _cond_resched() {
  9)   0.034 us    |                  rcu_all_qs();
  9)   0.236 us    |                }
  9)   0.566 us    |              }
  9)               |              _cond_resched() {
  9)   0.036 us    |                rcu_all_qs();
  9)   0.232 us    |              }
  9)   0.036 us    |              mutex_unlock();
  9)               |              __wake_up_sync_key() {
  9)               |                __wake_up_common_lock() {
  9)   0.036 us    |                  _raw_spin_lock_irqsave();
  9)               |                  __wake_up_common() {
  9)               |                    pollwake() {
  9)               |                      default_wake_function() {
  9)               |                        try_to_wake_up() {
  9)   0.178 us    |                          _raw_spin_lock_irqsave();
  9)               |                          select_task_rq_fair() {
  9)   0.033 us    |                            available_idle_cpu();
  9)   0.032 us    |                            available_idle_cpu();
  9)   0.040 us    |                            cpus_share_cache();
  9)   0.058 us    |                            available_idle_cpu();
  9)   1.061 us    |                          }
  9)   0.036 us    |                          ttwu_queue_wakelist();
  9)   0.036 us    |                          _raw_spin_lock();
  9)   0.079 us    |                          update_rq_clock();
  9)               |                          ttwu_do_activate() {
  9)               |                            enqueue_task_fair() {
  9)               |                              enqueue_entity() {
  9)   0.040 us    |                                update_curr();
  9)   0.088 us    |                                __update_load_avg_se();
  9)   0.070 us    |                                __update_load_avg_cfs_rq();
  9)   0.032 us    |                                update_cfs_group();
  9)   0.055 us    |                                __enqueue_entity();
  9)   1.347 us    |                              }
  9)               |                              enqueue_entity() {
  9)   0.038 us    |                                update_curr();
  9)   0.077 us    |                                __update_load_avg_se();
  9)   0.050 us    |                                __update_load_avg_cfs_rq();
  9)               |                                update_cfs_group() {
  9)   0.047 us    |                                  reweight_entity();
  9)   0.289 us    |                                }
  9)   0.035 us    |                                __enqueue_entity();
  9)   1.469 us    |                              }
  9)   0.033 us    |                              hrtick_update();
  9)   3.546 us    |                            }
  9)               |                            ttwu_do_wakeup() {
  9)               |                              check_preempt_curr() {
  9)   0.046 us    |                                resched_curr();
  9)   0.279 us    |                              }
  9)   0.671 us    |                            }
  9)   4.653 us    |                          }
  9)   0.038 us    |                          _raw_spin_unlock_irqrestore();
  9)   7.458 us    |                        }
  9)   7.652 us    |                      }
  9)   7.924 us    |                    }
  9)   8.865 us    |                  }
  9)   0.045 us    |                  _raw_spin_unlock_irqrestore();
  9)   9.501 us    |                }
  9)   9.703 us    |              }
  9)   0.033 us    |              kill_fasync();
  9)   0.055 us    |              __sb_start_write();
  9)               |              file_update_time() {
  9)               |                current_time() {
  9)   0.037 us    |                  ktime_get_coarse_real_ts64();
  9)   0.039 us    |                  timestamp_truncate();
  9)   0.454 us    |                }
  9)               |                __mnt_want_write_file() {
  9)   0.057 us    |                  __mnt_want_write();
  9)   0.289 us    |                }
  9)               |                generic_update_time() {
  9)   0.089 us    |                  __mark_inode_dirty();
  9)   0.321 us    |                }
  9)   0.039 us    |                __mnt_drop_write_file();
  9)   1.904 us    |              }
  9)   0.037 us    |              __sb_end_write();
  9) + 14.315 us   |            }
  9) + 14.620 us   |          }
  9) + 14.840 us   |        }
  9)   0.166 us    |        __fsnotify_parent();
  9)   0.095 us    |        fsnotify();
  9) + 18.702 us   |      }
  9)               |      fput() {
  9)   0.035 us    |        fput_many();
  9)   0.238 us    |      }
  9) + 20.668 us   |    }
  9) + 20.907 us   |  }

这是一个相当极端的例子,但它说明系统调用最终可能会做比复制内存更多的事情。

答案2

如果您适当地添加一个调用同步(2)或者同步(2)在您的代码中,您应该期望 Linux 内核将一些数据发送到您的磁盘。

但到了2022年,磁盘通常是SSD,并且磁盘本身有一些缓冲区......

也可以看看时间(7),标准差(4), 使用时钟获取时间(2),可能还有一些(我不知道)ioctl(2)或者fcntl(2)或者io_提交(2)强制你的硬盘在物理层面写入数据......

也许您需要编写一些内核模块......

相关内容