我正在对 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 | }
这是一个相当极端的例子,但它说明系统调用最终可能会做比复制内存更多的事情。