【问题标题】:Linux Multi-Threaded Performance Enhancements for File open()文件 open() 的 Linux 多线程性能增强
【发布时间】:2010-07-28 17:36:55
【问题描述】:

我正致力于优化高性能、大容量数据引擎的性能,该引擎最终服务于最终用户的网络体验。具体来说,委托给我的文章围绕表征多线程文件 IO 和数据到本地缓存的内存映射。在编写测试应用程序以隔离时序高极点时,已经暴露了几个问题。代码已最小化,只执行系统文件打开 (open(O_RDONLY)) 调用。我希望这个查询的结果可以帮助我们理解基本的低级系统流程,从而可以理解一个完整的预测(或至少是关系)时序模型。建议总是受欢迎的。我们似乎遇到了时间障碍,并希望了解行为并确定是否可以打破该障碍。

测试程序:

  1. 用 C 语言编写,使用 gnu C 编译器编译,如下所述;
  2. 最低限度地编写以将发现的问题隔离到单个系统文件“open()”;
  3. 可配置为同时启动请求数量的 pthread;
  4. 加载约 8K 大小的 1000 个文本文件的列表;
  5. (简单地)创建线程而不修改属性;
  6. 每个线程对预先确定的文件列表中的下一个可用文件执行多个顺序文件 open() 调用,直到文件列表用尽时,单个线程应打开所有 1000 个文件,2 个线程应理论上打开 500 个文件(尚未证明)等);

我们已经多次运行测试,参数化地改变线程数、文件大小以及文件是位于本地还是远程服务器上。出现了几个问题。

观察结果(打开远程文件):

  1. 第一次打开文件的时间较长(正如预期的那样,由于文件缓存);
  2. 使用一个线程运行测试应用程序以加载所有远程文件需要 X 秒;
  3. 似乎在机器上以 1 到 # 个可用 CPU 的线程数运行应用程序会导致时间与 CPU 数量(nX 秒)成正比。
  4. 使用线程计数运行应用程序 > #CPUs 导致运行时间似乎与使用 #CPUs 线程运行所需的时间大致相同(这是巧合,还是系统限制,或者什么?)。
  5. 运行多个并发进程(例如,同一测试应用的 25 个并发实例)导致时间与选定线程数的进程数大致呈线性关系。
  6. 在不同的服务器上运行应用程序显示相似的结果

观察结果(打开驻留在本地的文件):

  1. 时间快几个数量级(正如预期的那样);
  2. 随着线程数的增加,LOW 时序拐点出现在大约 4-5 个活动线程处,然后再次增加,直到线程数等于 CPU 数,然后再次趋于平稳;
  3. 运行多个并发进程(相同的测试)导致时间与进程数大致呈线性关系,线程数保持不变(与上述 #5 的结果相同)。

此外,我们注意到本地打开大约需要 0.01 毫秒,而顺序网络打开在 1 毫秒时要慢 100 倍。打开网络文件,我们在 8 个线程的情况下获得了高达 8 倍的线性吞吐量增加,但 9+ 个线程什么都不做。在超过 8 个同时请求后,网络开放调用似乎被阻止。我们期望的是初始延迟等于网络往返,然后与本地吞吐量大致相同。也许在本地和远程系统上完成了额外的互斥锁,这需要 100 倍的时间。也许有一些内部远程调用队列只能容纳 8 个。

预期的结果和问题将通过测试或类似论坛的答案来回答:

  1. 运行多个线程会导致在更短的时间内完成相同的工作;
  2. 是否存在最佳线程数;
  3. 线程数和可用 CPU 之间是否存在关系?
  4. 是否存在其他系统性原因导致文件数限制为 8-10?
  5. 对“open()”的系统调用如何在多线程进程中工作?
  6. 每个线程都有其上下文切换的时间片;
  7. open() 调用是否会阻塞并等待文件打开/加载到文件缓存中?或者调用是否允许在操作进行时发生上下文切换?
  8. 当 open() 完成时,调度程序是否会重新确定该线程的优先级以更快地执行,或者线程是否必须以循环方式等待轮到它;
  9. 将 1000 个文件所在的已装载卷设置为只读或读/写会有所不同吗?
  10. 当使用完整路径调用 open() 时,路径中的每个元素是否都经过了 stat() 处理?在文件树列表中打开()一个公共目录,然后通过相对路径打开()该公共目录下的文件是否更有意义?

开发测试设置:

Red Hat Enterprise Linux Server release 5.4 (Tikanga)

8-CPUS, each with characteristics as shown below:

processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 23
model name      : Intel(R) Xeon(R) CPU           X5460  @ 3.16GHz
stepping        : 6
cpu MHz         : 1992.000
cache size      : 6144 KB
physical id     : 0
siblings        : 4
core id         : 1
cpu cores       : 4
apicid          : 1
fpu             : yes
fpu_exception   : yes
cpuid level     : 10
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm syscall lm constant_tsc pni monitor ds_cpl vmx est tm2 ssse3 cx16 xtpr sse4_1 lahf_lm
bogomips        : 6317.47
clflush size    : 64
cache_alignment : 64
address sizes   : 38 bits physical, 48 bits virtual
power management:

GNU C compiler, version:
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-46)

【问题讨论】:

  • 老兄,这是很多问题。请阅读格式化 btw 以使其更具可读性。
  • 如果允许您插入基准图表,这也将非常有用。

标签: performance file-io linux-kernel


【解决方案1】:

不确定这是否是您的问题之一,但它可能有用。

在优化单个 SATA 磁盘上的数千个随机读取时,令我印象深刻的一件事是,在没有额外线程的情况下,在 linux 中以干净的方式执行非阻塞 I/O 并不是那么容易。

(目前)不可能在块设备上发出非阻塞read();即它将阻塞磁盘需要的 5 ms 寻道时间(在 3 GHz 下,5 ms 是永恒的)。将O_NONBLOCK 指定为open() 只是为了向后兼容,用于CD 刻录机或其他东西(这是一个相当模糊的问题)。通常,open() 不会阻塞或缓存任何内容,它主要只是为了获取文件句柄以便稍后执行一些数据 I/O。

就我的目的而言,mmap() 似乎让我尽可能接近磁盘的内核处理。使用 madvise()mincore() 我能够充分利用磁盘的 NCQ 功能,这可以通过改变未完成请求的队列深度来证明,这与发出 10k 读取的总时间成反比.

感谢 64 位内存寻址,使用mmap() 将整个磁盘映射到内存完全没有问题。 (在 32 位平台上,您需要使用 mmap64() 映射所需的磁盘部分)

【讨论】:

    猜你喜欢
    • 2011-12-30
    • 1970-01-01
    • 1970-01-01
    • 2011-03-25
    • 2018-12-15
    • 1970-01-01
    • 2012-04-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多