考虑一个受CPU限制的应用程序,但也具有高性能I/O要求.
我正在将Linux文件I/O与Windows进行比较,我看不出epoll将如何帮助Linux程序.内核会告诉我文件描述符"准备好读取",但是我仍然需要调用阻塞read()来获取我的数据,如果我想读取兆字节,那么很明显它会阻塞.
在Windows上,我可以创建一个设置了OVERLAPPED的文件句柄,然后使用非阻塞I/O,并在I/O完成时收到通知,并使用该完成函数中的数据.我需要不花费应用程序级别的挂钟时间等待数据,这意味着我可以精确地将我的线程数调整为我的内核数量,并获得100%的高效CPU利用率.
如果我必须在Linux上模拟异步I/O,那么我必须分配一些线程来执行此操作,并且这些线程将花费一些时间来处理CPU事务,并且大量时间阻塞I/O,此外,在这些线程的消息传递中会有开销.因此,我将过度订阅或利用我的CPU核心.
我把mmap()+ madvise()(WILLNEED)视为"穷人的异步I/O",但它仍然没有完全通过那里,因为当它完成时我无法得到通知 - 我有"猜测",如果我猜"错误",我将最终阻止内存访问,等待数据来自磁盘.
Linux似乎在io_submit中启动了异步I/O,它似乎也有一个用户空间POSIX aio实现,但它已经有一段时间了,我知道没有人会担保这些系统的关键,高性能的应用程序.
Windows模型的工作方式大致如下:
步骤1/2通常作为单个事物完成.步骤3/4通常使用工作线程池完成,而不是(必要)与发出I/O相同的线程.这个模型有点类似于boost :: asio提供的模型,除了boost :: asio实际上不会给你异步的基于块的(磁盘)I/O.
Linux中epoll的不同之处在于,在步骤4中,还没有I/O发生 - 它会在步骤4之后提升第1步,如果你确切知道你需要的话,那就是"向后".
编写了大量的嵌入式,桌面和服务器操作系统之后,我可以说这种异步I/O模型对于某些类型的程序来说非常自然.它还具有非常高的吞吐量和低开销.我认为这是Linux I/O模型在API级别上仍然存在的真正缺点之一.
(道歉有些冗长的介绍)
在此期间,prefaults整个大文件(> 400MB)到缓冲区缓存后加快了实际运行的应用程序的开发,我测试是否每次读4MB仍然有超过一次读取只有1MB块任何明显的好处.令人惊讶的是,较小的请求实际上变得更快.这似乎违反直觉,所以我进行了更广泛的测试.
缓冲区缓存在运行测试之前被清除(只是为了笑,我也在缓冲区中运行了一个文件.无论请求大小如何,缓冲区缓存都能提供高达2GB/s的速度,但令人惊讶的是+/- 30%随机方差).
使用的所有读取都与相同的目标缓冲区重叠ReadFile(使用FILE_FLAG_OVERLAPPED和不 使用句柄打开FILE_FLAG_NO_BUFFERING).使用的硬盘有点老,但功能齐全,NTFS的簇大小为8kB.初始运行后磁盘进行了碎片整理(6个碎片与未碎片,零差异).为了更好的数字,我也使用了更大的文件,下面的数字是读取1GB.
结果真的令人惊讶:
4MB x 256 : 5ms per request, completion 25.8s @ ~40 MB/s
1MB x 1024 : 11.7ms per request, completion 23.3s @ ~43 MB/s
32kB x 32768 : 12.6ms per request, completion 15.5s @ ~66 MB/s
16kB x 65536 : 12.8ms per request, completion 13.5s @ ~75 MB/s
Run Code Online (Sandbox Code Playgroud)
因此,这表明提交数千个请求两个簇的长度实际上比提交几百个大的连续读取更好.提交时间(ReadFile返回之前的时间)确实随着请求数量的增加而上升,但异步完成时间几乎减半.
在每种情况下,内核CPU时间大约为5-6%(在四核上,所以应该说20-30%),而异步读取正在完成,这是一个惊人的CPU数量 - 显然操作系统做了一些非也是无比的忙碌等待.在2.6 GHz时,30%的CPU持续25秒,这是"无所事事"的相当多的周期.
知道如何解释这个吗?也许这里有人对Windows重叠IO的内部工作有更深入的了解?或者,您是否可以使用ReadFile读取兆字节的数据?
我可以看到IO调度程序如何通过最小化搜索来优化多个请求,尤其是当请求是随机访问时(它们不是!).我还可以看到,在NCQ中给出一些请求,硬盘如何能够执行类似的优化.
然而,我们谈论的是荒谬的一些荒谬的小要求 - 尽管如此,它们的表现仍然超过2倍的合理要求.
旁注:明显的赢家是内存映射.我几乎倾向于添加"毫不奇怪",因为我是内存映射的忠实粉丝,但在这种情况下,它实际上让我感到惊讶,因为"请求"甚至更小,操作系统应该更不能预测和安排IO.我最初没有测试内存映射,因为它似乎反直觉,甚至可以远程竞争.那么多你的直觉,嘿.
在不同偏移处重复映射/取消映射视图几乎为零时间.使用16MB视图并使用简单的for()循环对每个页面进行错误操作,每页读取一个字节,在9.2秒内完成@~111 …
我能找到的就是如何使用Overlapped I/O的教程,但是我找不到为什么会这样调用它.
是因为例如我可以从套接字读取内容,然后在第一次读取返回读取的字节之前读取其他内容吗?
当在异步模式下打开的System.IO.Pipe.NamedPipeServerStream有更多可用于读取的数据时,我需要找到一种通知方式 - WaitHandle将是理想的选择.我不能简单地使用BeginRead()来获取这样的句柄,因为我可能会被另一个想要写入管道的线程发出信号 - 所以我必须释放管道上的锁并等待写入完成,和NamedPipeServerStream没有CancelAsync方法.我也尝试调用BeginRead(),然后如果线程被发信号通过调用管道上的win32函数CancelIO,但我不认为这是一个理想的解决方案,因为如果在数据到达和处理时调用CancelIO,它将会被删除 - 我仍然希望保留这些数据,但是在写完之后再处理它.我怀疑win32函数PeekNamedPipe可能有用,但我想避免不得不用它连续轮询新数据.
在上面的文字有点不清楚的情况下,这里大致是我想要做的...
NamedPipeServerStream pipe;
ManualResetEvent WriteFlag;
//initialise pipe
lock (pipe)
{
//I wish this method existed
WaitHandle NewDataHandle = pipe.GetDataAvailableWaithandle();
Waithandle[] BreakConditions = new Waithandle[2];
BreakConditions[0] = NewDataHandle;
BreakConditions[1] = WriteFlag;
int breakcode = WaitHandle.WaitAny(BreakConditions);
switch (breakcode)
{
case 0:
//do a read on the pipe
break;
case 1:
//break so that we release the lock on the pipe
break;
}
}
Run Code Online (Sandbox Code Playgroud) 我在Windows中的大部分日常编程工作现在都是各种I/O操作(管道,控制台,文件,套接字......).我很清楚从不同类型的句柄读取和写入的不同方法(同步,异步等待事件完成,等待文件HANDLE,I/O完成端口和可警告的I/O).我们使用其中许多.
对于我们的一些应用程序,只有一种方法来处理所有句柄是非常有用的.我的意思是,程序可能不知道它收到了什么样的句柄,我们想要使用,比方说,I/O完成端口.
首先我会问:
我们假设我有一个句柄:
HANDLE h;
Run Code Online (Sandbox Code Playgroud)
我从某个地方收到了I/O进程.有没有简单可靠的方法来找出它创建的标志?有问题的主要标志是FILE_FLAG_OVERLAPPED.
到目前为止,我所知道的唯一方法是尝试将这样的句柄注册到I/O完成端口(使用CreateIoCompletionPort()).如果成功,则使用FILE_FLAG_OVERLAPPED创建句柄.但是之后只能使用I/O完成端口,因为如果不关闭它HANDLE h本身就不能从中取消注册句柄.
提供一种简单的方法来确定存在FILE_FLAG_OVERLAPPED,我的第二个问题就出现了:
有没有办法如何将这样的标志添加到现有的句柄?这将使最初为同步操作打开的句柄打开以进行异步操作.有没有办法如何创建相反的(删除FILE_FLAG_OVERLAPPED从异步创建同步句柄)?
通过MSDN阅读和google搜索后,我没有找到任何直接的方法.至少会有一些技巧可以做同样的事吗?就像使用CreateFile()函数或类似的东西以相同的方式重新创建句柄一样?某些东西甚至部分记录或根本没有记录?
我需要这个的主要地方是确定进程应该从第三方应用程序发送给它的句柄读取/写入的方式(或改变方式).我们无法控制第三方产品如何创建其句柄.
亲爱的Windows大师:请帮助!
带着敬意
马丁
该ConnectEx功能需要一个"未连接的,以前绑定的插座".实际上,如果我省略了我的示例中的绑定步骤(见下文),ConnectEx将失败并使用WSAEINVAL.
这是我目前的理解:在调用ConnectEx之前,将套接字绑定到INADDR_ANY端口0(除非它已绑定):
struct sockaddr_in addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = 0;
rc = bind(sock, (SOCKADDR*) &addr, sizeof(addr));
if (rc != 0) { ... bind failed; call WSAGetLastError to see why ... }
Run Code Online (Sandbox Code Playgroud)
或者对于IPv6套接字:
struct sockaddr_in6 addr;
ZeroMemory(&addr, sizeof(addr));
addr.sin6_family = AF_INET6;
addr.sin6_addr = in6addr_any;
addr.sin6_port = 0;
rc = bind(sock, (SOCKADDR*) &addr, sizeof(addr));
if (rc != 0) { ... …Run Code Online (Sandbox Code Playgroud) 我试图通过使用FILE_FLAG_OVERLAPPED标志打开CONIN $来使用重叠IO来从控制台读取输入.但是,当我使用它时,ReadFile会阻塞,即使使用OVERLAPPED参数也是如此.
我已经阅读了一些报道这是Windows 7错误的帖子.我正在使用7这样才有可能.
这是我正在使用的代码:
// Create a console window
AllocConsole();
AttachConsole(GetProcessId(GetModuleHandle(NULL)));
HANDLE overlappedConsoleIn = CreateFile(L"CONIN$",
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
NULL);
// Set up the console to work with stdio
FILE *consoleOut = _fdopen(_open_osfhandle((long)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT), "w");
FILE *consoleIn = _fdopen(_open_osfhandle((long)overlappedConsoleIn, _O_TEXT), "r");
*stdout = *consoleOut;
*stdin = *consoleIn;
setvbuf(consoleOut, NULL, _IONBF, 0);
setvbuf(consoleIn, NULL, _IONBF, 0);
std::ios::sync_with_stdio();
// Create a completion event
HANDLE inputEvent = CreateEvent(NULL, true, false, NULL);
BYTE inputBuffer[128];
OVERLAPPED overlappedData;
overlappedData.Offset = 0;
overlappedData.OffsetHigh …Run Code Online (Sandbox Code Playgroud) 我试图通过使用MSDN上描述的OVERLAPPED结构异步调用DeviceIO函数.我使用FSCTL_ENUM_USN_DATA控制代码枚举NTFS驱动器的MFT,但我不能异步运行它.文件句柄是使用FILE_FLAG_OVERLAPPED创建的,但是我是否使用FILE_FLAG_OVERLAPPED的重叠结构没有区别.该功能不会立即返回.在这两种情况下似乎都是同步的.下面的示例显示了C:\驱动器上前100.000个MFT条目的枚举.由于我不太熟悉重叠结构的使用,我可能做错了.我的问题:如何异步执行DeviceIoControl(hDevice,FSCTL_ENUM_USN_DATA,...)?谢谢你的帮助.
#include "stdafx.h"
#include <Windows.h>
typedef struct {
DWORDLONG nextusn;
USN_RECORD FirstUsnRecord;
BYTE Buffer[500];
}TDeviceIoControlOutputBuffer, *PTDeviceIoControlOutputBuffer;
int _tmain(int argc, _TCHAR* argv[])
{
MFT_ENUM_DATA lInputMftData;
lInputMftData.StartFileReferenceNumber = 0;
lInputMftData.MinMajorVersion = 2;
lInputMftData.MaxMajorVersion = 3;
lInputMftData.LowUsn = 0;
lInputMftData.HighUsn = 0;
TDeviceIoControlOutputBuffer lOutputMftData;
DWORD lOutBytesReturned = 0;
HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
OVERLAPPED lOverlapped = { 0 };
lOverlapped.hEvent = hEvent;
LPCWSTR path = L"\\\\.\\C:";
HANDLE hDevice = CreateFile(path, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
if …Run Code Online (Sandbox Code Playgroud) System.IO.File在.NET和.NET Core 中有一系列Read...Async()方法,所有这些方法都返回Task<byte[]>或Task<string>(Task<T>是.NET与Java 的 等价物Future<T>)。
这看起来在很大程度上等同于AsynchronousFileChannelAPI(它们要么消耗CompletionHandler或返回 a Future),但有一个主要区别。
AsynchronousFileChannel 使用托管后台线程执行异步 I/O(该线程可能由默认线程池提供(sun.nio.ch.ThreadPool ) 或ExecutorService在通道创建期间显式指定的)。FileStream另一方面,.NET 中的实现将FileOptions.Asynchronous标志传递给底层操作系统(另请参阅同步和异步 I/O),不产生任何托管后台线程并使用所谓的重叠 I/O。sun.nio.ch.WindowsAsynchronousFileChannelImpl,这正是重叠 I/O之上的抽象层。来自MSDN的关于CreateIoCompletionPort函数中的CompletionKey的评论:
使用CompletionKey参数可帮助您的应用程序跟踪已完成的I/O操作.CreateIoCompletionPort不使用此值进行功能控制; 相反,它在与I/O完成端口关联时附加到FileHandle参数中指定的文件句柄.对于每个文件句柄,此完成键应该是唯一的,并且它在整个内部完成排队过程中伴随文件句柄.当完成数据包到达时,它将在GetQueuedCompletionStatus函数调用中返回.PostQueuedCompletionStatus函数还使用CompletionKey参数来排队您自己的专用完成数据包.
上述言论给我一个问题.为什么使用CompletionKey,因为我们可以将用户上下文与扩展重叠结构中的文件句柄相关联,如下所示:
typedef struct s_overlappedplus
{
OVERLAPPED ol;
int op_code;
/*we can alternatively put user context over here instead of CompletionKey*/
LPVOID user_context;
} t_overlappedplus;
Run Code Online (Sandbox Code Playgroud)
完成后通过CONTAINING_RECORD宏进行检索?
很酷,我只相信CompletionKey是每个句柄的上下文,而扩展的重叠结构是每I/O一个.但是这种设计背后的哲学是什么,在什么情况下可以使用CompletionKey而不是在用户上下文中使用扩展的重叠结构?
windows multithreading winsock overlapped-io io-completion-ports
overlapped-io ×10
asynchronous ×4
windows ×4
winapi ×3
io ×2
winsock ×2
.net ×1
aio ×1
buffered ×1
c# ×1
c++ ×1
createfile ×1
ioctl ×1
java ×1
linux ×1
named-pipes ×1
nio ×1
overlap ×1
posix ×1
sockets ×1
synchronous ×1
terminology ×1
visual-c++ ×1