私は、プロセスを制限したり、データをディスクに同期させることなく、ページキャッシュにデータを書き込む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/秒。私が理解したのは、書き込みシステム呼び出しが呼び出されると、ページキャッシュ(メモリ内)からデータをコピーし、コピーが完了するとすぐに返されます。したがって、メモリ帯域幅に近い帯域幅を見ることが期待されます。さらに、システムコールのコストを削減するために、より大きなデータ(OSがプロセス調整を開始する前)を使用して同じ実験をテストしました。しかし、私はまだほぼ同じ結果を見ています。ページキャッシュへの書き込みを実行するときにメモリ帯域幅を観察できない理由を知っている人はいますか?
答え1
ここで主な要素は、memcpy
Cライブラリによって処理されるか、Cコンパイラによって直接処理されるのではなくwrite
カーネルによって処理されるシステムコールです。
したがって、memcpy
実行が進行中であり、関数呼び出しの(小さい)オーバーヘッドがない可能性があります。write
一方、システムコールのすべてのオーバーヘッドがあります。特に、テストでは書き込みサイズが小さいため、コピー自体のコストがシステムコールのコストに比べて小さくなる可能性があります。大規模なテストでも、システムコールのコストは依然として主要な要因となる可能性があります。
システムコールの重量を減らすには、より大きなサイズと比較し、必要に応じてダーティレート設定を変更したり、tmpfs
ディスクの書き込みコストを回避するためにaを作成します。また、欲しいかもしれませんシステムコールのコストを増加させるKPTIやその他の軽減機能を無効にします。、そしてio_uringを見てください。
処理に関連するタスクのより良いアイデアを得るには、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_submit(2)ハードドライブに物理レベルでデータを書き込むように強制します。
たぶんカーネルモジュールを書く必要があるかもしれません。