术语“CPU限制”和“I/O限制”是什么意思?


当前回答

I/O限制是指完成计算所需的时间主要由等待输入/输出操作完成的时间决定的一种情况。

这与受CPU限制的任务相反。当请求数据的速度比消耗数据的速度慢时,或者换句话说,花费在请求数据上的时间比处理数据的时间多时,就会出现这种情况。

其他回答

另一种表达相同想法的方式是:

如果加速CPU并没有加速你的程序,它可能是I/O受限的。 如果加速I/O(例如使用更快的磁盘)没有帮助,那么您的程序可能是CPU受限的。

(我使用“可能是”是因为你需要考虑其他资源。内存就是一个例子。)

IO绑定进程:花更多的时间做IO比计算,有很多 短CPU突发。 CPU绑定进程:花费更多的时间进行计算,很少有很长时间的CPU爆发

CPU限制是指进程的速度受CPU速度的限制。对一小组数字执行计算的任务,例如乘小矩阵,可能会受到CPU的限制。

I/O约束是指进程的速度受I/O子系统的速度限制。处理来自磁盘的数据的任务(例如,计算文件中的行数)可能受到I/O限制。

内存限制是指进程进程的速度受可用内存数量和内存访问速度的限制。处理大量内存内数据的任务,例如乘法大型矩阵,很可能是memory Bound。

缓存约束是指进程进程受可用缓存数量和速度限制的速率。如果一个任务处理的数据超过了缓存的容量,那么它就会被缓存绑定。

I/O绑定比内存绑定慢,缓存绑定比CPU绑定慢。

I/O受限的解决方案不一定是获得更多内存。在某些情况下,访问算法可以围绕I/O、内存或缓存限制进行设计。参见缓存无关算法。

多线程是最重要的

在这个回答中,我将研究区分CPU和IO限制工作的一个重要用例:在编写多线程代码时。

RAM I/O绑定示例:Vector Sum

考虑一个程序,它将单个向量的所有值相加:

#define SIZE 1000000000
unsigned int is[SIZE];
unsigned int sum = 0;
size_t i = 0;
for (i = 0; i < SIZE; i++)
    /* Each one of those requires a RAM access! */
    sum += is[i]

通过为每个核心平均分割数组来实现并行,在普通的现代台式机上用处有限。

例如,在我的Ubuntu 19.04上,联想ThinkPad P51笔记本电脑,CPU:英特尔酷睿i7-7820HQ CPU(4核/ 8线程),RAM: 2倍三星M471A2K43BB1-CRC(2倍16GiB),我得到的结果如下:

图数据。

请注意,运行之间有很多差异。但是我不能进一步增加数组的大小,因为我已经是8GiB了,而且我今天没有心情进行多次运行的统计。然而,在做了许多手动运行之后,这似乎是一个典型的运行。

基准测试代码:

POSIX C线程源代码中使用的图形。 下面是一个c++版本,可以产生类似的结果。 剧情脚本

我不知道足够的计算机架构来完全解释曲线的形状,但有一件事是明确的:由于我使用了所有的8个线程,计算并没有像天真地期望的那样快8倍!出于某种原因,2和3个线程是最优的,增加更多线程只会让事情变得更慢。

将其与CPU约束的工作进行比较,后者实际上要快8倍:在time(1)的输出中,'real', 'user'和'sys'是什么意思?

所有处理器共享一个连接到RAM的内存总线的原因:

CPU 1   --\    Bus    +-----+
CPU 2   ---\__________| RAM |
...     ---/          +-----+
CPU N   --/

所以内存总线很快成为瓶颈,而不是CPU。

这是因为两个数字相加只需要一个CPU周期,而内存读取在2016年的硬件中大约需要100个CPU周期。

因此,CPU对每个字节的输入数据所做的工作太小了,我们称之为io绑定进程。

进一步加速计算的唯一方法是使用新的内存硬件加速单个内存访问,例如多通道内存。

例如,升级到更快的CPU时钟并不是很有用。

其他的例子

matrix multiplication is CPU-bound on RAM and GPUs. The input contains: 2 * N**2 numbers, but: N ** 3 multiplications are done, and that is enough for parallelization to be worth it for practical large N. This is why parallel CPU matrix multiplication libraries like the following exist: http://www.netlib.org/scalapack/pblas_qref.html http://icl.cs.utk.edu/magma/software/ Cache usage makes a big difference to the speed of implementations. See for example this didactic GPU comparison example. See also: Why can GPU do matrix multiplication faster than CPU? BLAS equivalent of a LAPACK function for GPUs Networking is the prototypical IO-bound example. Even when we send a single byte of data, it still takes a large time to reach it's destination. Parallelizing small network requests like HTTP requests can offer a huge performance gains. If the network is already at full capacity (e.g. downloading a torrent), parallelization can still increase improve the latency (e.g. you can load a web page "at the same time"). A dummy C++ CPU bound operation that takes one number and crunches it a lot: serial parallel Sorting appears to be CPU based on the following experiment: Are C++17 Parallel Algorithms implemented already? which showed a 4x performance improvement for parallel sort, but I would like to have a more theoretical confirmation as well The well known Coremark benchmark from EEMBC explicitly checks how well a suite of problems scale. Sample benchmark result clearing showing that: Workload Name (iter/s) (iter/s) Scaling ----------------------------------------------- ---------- ---------- ---------- cjpeg-rose7-preset 526.32 178.57 2.95 core 7.39 2.16 3.42 linear_alg-mid-100x100-sp 684.93 238.10 2.88 loops-all-mid-10k-sp 27.65 7.80 3.54 nnet_test 32.79 10.57 3.10 parser-125k 71.43 25.00 2.86 radix2-big-64k 2320.19 623.44 3.72 sha-test 555.56 227.27 2.44 zip-test 363.64 166.67 2.18 MARK RESULTS TABLE Mark Name MultiCore SingleCore Scaling ----------------------------------------------- ---------- ---------- ---------- CoreMark-PRO 18743.79 6306.76 2.97 the linking of a C++ program can be parallelized to a certain degree: Can gcc use multiple cores when linking?

如何发现如果你是CPU或IO限制

非ram IO绑定像磁盘,网络:ps aux,然后检查CPU% / 100 < n线程。如果是,你是IO绑定,例如阻塞读取只是等待数据和调度器跳过该过程。然后使用sudo iotop等进一步的工具来确定哪个IO是问题所在。

或者,如果执行速度很快,并且对线程数量进行了参数化,那么对于CPU限制的工作,您可以很容易地从时间上看到性能随着线程数量的增加而提高:在time(1)的输出中,'real'、'user'和'sys'是什么意思?

RAM- io绑定:更难判断,因为RAM等待时间包含在CPU%测量中,请参见:

如何检查应用程序是否cpu或内存限制? https://askubuntu.com/questions/1540/how-can-i-find-out-if-a-process-is-cpu-memory-or-disk-bound

一些选项:

Intel Advisor rooline(非免费):https://software.intel.com/en-us/articles/intel-advisor-roofline(存档)“屋顶线图是与硬件限制相关的应用程序性能的可视化表示,包括内存带宽和计算峰值。”

GPUs

当您第一次将输入数据从常规CPU可读RAM传输到GPU时,GPU会遇到IO瓶颈。

因此,对于CPU受限的应用,gpu只能比CPU更好。

然而,一旦数据传输到GPU,它可以比CPU更快地对这些字节进行操作,因为GPU:

有更多的数据本地化比大多数CPU系统,所以数据可以更快地访问某些核心比其他 利用数据并行性并牺牲延迟,跳过尚未准备好立即操作的任何数据。 由于GPU必须处理大量的并行输入数据,因此最好跳过可能可用的下一个数据,而不是像CPU那样等待当前数据可用并阻塞所有其他操作

因此GPU可以比CPU更快,如果你的应用程序:

可以高度并行化:不同的数据块可以在同一时间彼此分开处理 每个输入字节需要足够多的操作(不像向量加法,每个字节只做一次加法) 有大量的输入字节

这些设计选择最初是针对3D渲染的应用,其主要步骤如OpenGL中的什么是shaders,我们需要它们做什么?

顶点着色器:用4x4矩阵乘以一堆1x4向量 片段着色器:计算一个三角形的每个像素的颜色基于它在三角形内的相对位置

因此我们得出结论,这些应用程序是cpu限制的。

随着可编程GPGPU的出现,我们可以观察到几个GPGPU应用程序作为CPU绑定操作的例子:

Image Processing with GLSL shaders? Local image processing operations such as a blur filter are highly parallel in nature. Is it possible to build a heatmap from point data at 60 times per second? Plotting of heatmap graphs if the plotted function is complex enough. https://www.youtube.com/watch?v=fE0P6H8eK4I "Real-Time Fluid Dynamics: CPU vs GPU" by Jesús Martín Berlanga Solving partial differential equations such as the Navier Stokes equation of fluid dynamics: highly parallel in nature, because each point only interacts with their neighbour there tend to be enough operations per byte

参见:

为什么我们还在使用cpu而不是gpu ? gpu的弱点是什么? https://www.youtube.com/watch?v=_cyVDoyI6NE“CPU vs GPU(有什么区别?)-计算机爱好者”

CPython全局解释器锁

作为一个快速的案例研究,我想指出Python全局解释器锁(GIL): CPython中的全局解释器锁(GIL)是什么?

这个CPython实现细节可以防止多个Python线程有效地使用cpu限制的工作。CPython文档说:

CPython implementation detail: In CPython, due to the Global Interpreter Lock, only one thread can execute Python code at once (even though certain performance-oriented libraries might overcome this limitation). If you want your application to make better use of the computational resources of multi-core machines, you are advised to use multiprocessing or concurrent.futures.ProcessPoolExecutor. However, threading is still an appropriate model if you want to run multiple I/O-bound tasks simultaneously.

因此,这里有一个例子,cpu绑定的内容不适合,而I/O绑定的内容适合。

JavaScript async和Node.js worker_threads

这种情况与Python类似。

JavaScript基本上是单线程的。不确定这是否是语言标准的一部分(对于Python来说,它不是,除了参考CPython实现AFAIK之外,甚至没有一个语言标准)。

JavaScript有async关键字,它允许执行暂停,然后它开始执行其他东西。你可以这样写:

async function myfunc(init) {
  ret = init
  for (let i = 0; i < 1000000; i++) {
    ret += i*i + 2*i + 3
  }
  return ret;
}

async function doNetworkRequest(init) {
  // Some native method that gets network data.
}

const [first, second, myfunc_ret1, myfunc_ret2] = await Promise.all([
  doNetworkRequest('example.com'),
  doNetworkRequest('example2.com'),
  myfunc(1),
  myfunc(2),
])

await表示“等待所有这些异步的事情在继续之前完成”。

然而,在CPU上一次只能运行一个异步方法,因此myfunc的CPU密集型工作根本不会因此而加速。

然而,网络访问的原型IO绑定操作可以得到加速,因为这将一个接一个地触发两个网络请求,并在服务器/网络完成工作时等待两者返回。

事实上,该语言中有一个专门用于此的关键字async,这说明:在浏览器主导的JavaScript上下文中,网络请求非常重要。

然而,随着Node.js的出现,人们开始越来越想要并行化CPU工作负载,他们达成了与CPython类似的解决方案:创建单独的进程而不是线程。这是通过worker_threads库完成的,它:

实际上以JavaScript脚本路径作为输入,并启动一个新的解释器实例来运行它 由于进程不共享内存空间,因此可以使用消息传递系统在实例之间传递消息

https://medium.com/@Trott/ using-work-threads-in -node-js-80494136dbb6包含一个很好的例子。

worker_threads的文档再次说明了这个答案中其他地方提到的差异:

worker(线程)对于执行cpu密集型JavaScript操作非常有用。它们对I/ o密集型工作没有太大帮助。Node.js内置的异步I/O操作比Workers更高效。

在浏览器上也有Web Workers,不知道它与Node的实现有什么区别:Web Workers和Worker Threads之间有什么区别?

当你的程序正在等待I/O(即。磁盘读/写或网络读/写等),即使程序停止,CPU也可以自由地执行其他任务。程序的速度主要取决于IO发生的速度,如果你想加快速度,就需要加快I/O。

如果你的程序正在运行大量的程序指令而不等待I/O,那么它就被称为CPU限制。加速CPU将使程序运行得更快。

在任何一种情况下,加速程序的关键可能不是加快硬件,而是优化程序以减少所需的IO或CPU数量,或者让它在执行CPU密集型操作的同时执行I/O。