由于项目需要测试windows下的IO性能,因此要写个小程序,按照要求读取磁盘上的文件。在读取文件的时候,测试Windows的IO性能。
主要内容:
- 程序的要求
- 一般的FileStream方式
- 利用kernel32.dll中的CreateFile函数
1. 程序的要求
程序的要求很简单。
(1)命令行程序
(2)有3个参数,读取的文件名,一次读取buffer size,读取的次数count
(3)如果读取次数count未到,文件已经读完,就再次从头读取文件。
使用格式如下:
C:\>****.exe “c:\****.bin” 32768 32768
读取文件“c:\****.bin”,每次读取4K,读取32768次,读取的量大概1G。
2. 一般的FileStream方式
利用FileStream来读取文件,非常简单,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
using
System;
using
System.Collections.Generic;
using
System.Text;
using
System.IO;
using
System.Reflection;
namespace
DirectIO
{
public
class
DIOReader
{
static
void
Main(
string
[] args)
{
long
start = DateTime.Now.Ticks;
if
(args.Length < 3)
{
Console.WriteLine(
"parameter error!!"
);
return
;
}
FileStream input =
null
;
try
{
int
bs = Convert.ToInt32(args[1]);
int
count = Convert.ToInt32(args[2]);
input =
new
FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None, bs);
byte
[] b =
new
byte
[bs];
for
(
int
i = 0; i < count; i++)
{
if
(input.Read(b, 0, bs) == 0)
input.Seek(0, SeekOrigin.Begin);
}
Console.WriteLine(
"Read successed! "
);
Console.WriteLine(DateTime.Now.Ticks - start);
}
catch
(Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
if
(input !=
null
)
{
input.Flush();
input.Close();
// 清除使用的对象
GC.Collect();
GC.Collect();
}
}
}
}
}
|
编译后的exe文件可以按照既定要求执行,但是对于同一文件,第二次读取明显比第一次快很多(大家可以用个1G左右的大文件试试)。第三次读取,第四次读取……和第二次差不多,都很快。
基于上述情况,可以判断是缓存的原因,导致第二次及以后各次都比较快。
但是从代码中来看,已经执行了input.Flush();input.Close();甚至是GC.Collect();
所以可能是Windows系统或者CLR对文件读取操作进行了优化,使用了缓存。
3. 利用kernel32.dll中的CreateFile函数
既然上述方法行不通,就得调查新的方法。通过google的查询,大部分人都是建议用C/C++调用系统API来实现。
不过最后终于找到了用c#实现了无缓存直接读取磁盘上的文件的方法。其实也是通过DllImport利用了kernel32.dll,不完全是托管代码。(估计用纯托管代码实现不了)
参考的文章:How do I read a disk directly with .Net?
还有msdn中的CreateFile API
实现代码就是参考的How do I read a disk directly with .Net?,分为两部分
(1)利用CreateFile API构造的可直接读取磁盘的DeviceStream
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
|
using
System;
using
System.Runtime.InteropServices;
using
System.IO;
using
Microsoft.Win32.SafeHandles;
namespace
DirectIO
{
public
class
DeviceStream : Stream, IDisposable
{
public
const
short
FILE_ATTRIBUTE_NORMAL = 0x80;
public
const
short
INVALID_HANDLE_VALUE = -1;
public
const
uint
GENERIC_READ = 0x80000000;
public
const
uint
NO_BUFFERING = 0x20000000;
public
const
uint
GENERIC_WRITE = 0x40000000;
public
const
uint
CREATE_NEW = 1;
public
const
uint
CREATE_ALWAYS = 2;
public
const
uint
OPEN_EXISTING = 3;
// Use interop to call the CreateFile function.
// For more information about CreateFile,
// see the unmanaged MSDN reference library.
[DllImport(
"kernel32.dll"
, SetLastError =
true
, CharSet = CharSet.Unicode)]
private
static
extern
IntPtr CreateFile(
string
lpFileName,
uint
dwDesiredAccess,
uint
dwShareMode, IntPtr lpSecurityAttributes,
uint
dwCreationDisposition,
uint
dwFlagsAndAttributes, IntPtr hTemplateFile);
[DllImport(
"kernel32.dll"
, SetLastError =
true
)]
private
static
extern
bool
ReadFile(
IntPtr hFile,
// handle to file
byte
[] lpBuffer,
// data buffer
int
nNumberOfBytesToRead,
// number of bytes to read
ref
int
lpNumberOfBytesRead,
// number of bytes read
IntPtr lpOverlapped
//
// ref OVERLAPPED lpOverlapped // overlapped buffer
);
private
SafeFileHandle handleValue =
null
;
private
FileStream _fs =
null
;
public
DeviceStream(
string
device)
{
Load(device);
}
private
void
Load(
string
Path)
{
if
(
string
.IsNullOrEmpty(Path))
{
throw
new
ArgumentNullException(
"Path"
);
}
// Try to open the file.
IntPtr ptr = CreateFile(Path, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, NO_BUFFERING, IntPtr.Zero);
handleValue =
new
SafeFileHandle(ptr,
true
);
_fs =
new
FileStream(handleValue, FileAccess.Read);
// If the handle is invalid,
// get the last Win32 error
// and throw a Win32Exception.
if
(handleValue.IsInvalid)
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
}
public
override
bool
CanRead
{
get
{
return
true
; }
}
public
override
bool
CanSeek
{
get
{
return
false
; }
}
public
override
bool
CanWrite
{
get
{
return
false
; }
}
public
override
void
Flush()
{
return
;
}
public
override
long
Length
{
get
{
return
-1; }
}
public
override
long
Position
{
get
{
throw
new
NotImplementedException();
}
set
{
throw
new
NotImplementedException();
}
}
/// <summary>
/// </summary>
/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and
/// (offset + count - 1) replaced by the bytes read from the current source. </param>
/// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream. </param>
/// <param name="count">The maximum number of bytes to be read from the current stream.</param>
/// <returns></returns>
public
override
int
Read(
byte
[] buffer,
int
offset,
int
count)
{
int
BytesRead = 0;
var
BufBytes =
new
byte
[count];
if
(!ReadFile(handleValue.DangerousGetHandle(), BufBytes, count,
ref
BytesRead, IntPtr.Zero))
{
Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
}
for
(
int
i = 0; i < BytesRead; i++)
{
buffer[offset + i] = BufBytes[i];
}
return
BytesRead;
}
public
override
int
ReadByte()
{
int
BytesRead = 0;
var
lpBuffer =
new
byte
[1];
if
(!ReadFile(
handleValue.DangerousGetHandle(),
// handle to file
lpBuffer,
// data buffer
1,
// number of bytes to read
ref
BytesRead,
// number of bytes read
IntPtr.Zero
))
{ Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error()); ;}
return
lpBuffer[0];
}
public
override
long
Seek(
long
offset, SeekOrigin origin)
{
throw
new
NotImplementedException();
}
public
override
void
SetLength(
long
value)
{
throw
new
NotImplementedException();
}
public
override
void
Write(
byte
[] buffer,
int
offset,
int
count)
{
throw
new
NotImplementedException();
}
public
override
void
Close()
{
handleValue.Close();
handleValue.Dispose();
handleValue =
null
;
base
.Close();
}
private
bool
disposed =
false
;
new
void
Dispose()
{
Dispose(
true
);
base
.Dispose();
GC.SuppressFinalize(
this
);
}
private
new
void
Dispose(
bool
disposing)
{
// Check to see if Dispose has already been called.
if
(!
this
.disposed)
{
if
(disposing)
{
if
(handleValue !=
null
)
{
_fs.Dispose();
handleValue.Close();
handleValue.Dispose();
handleValue =
null
;
}
}
// Note disposing has been done.
disposed =
true
;
}
}
}
}
|
注意和原文相比,改动了一个地方。即加了个NO_BUFFERING的参数,并在调用CreateFile时使用了这个参数。
1
|
IntPtr ptr = CreateFile(Path, GENERIC_READ, 0, IntPtr.Zero, OPEN_EXISTING, NO_BUFFERING, IntPtr.Zero);
|
之前没有加这个参数的时候,在xp上测试还是第二次比第一次快很多。
(2)完成指定要求的DIOReader
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
using
System;
using
System.Collections.Generic;
using
System.Text;
using
System.IO;
using
System.Reflection;
namespace
DirectIO
{
public
class
DIOReader
{
static
void
Main(
string
[] args)
{
long
start = DateTime.Now.Ticks;
if
(args.Length < 3)
{
Console.WriteLine(
"parameter error!!"
);
return
;
}
BinaryReader input =
null
;
try
{
int
bs = Convert.ToInt32(args[1]);
int
count = Convert.ToInt32(args[2]);
input =
new
BinaryReader(
new
DeviceStream(args[0]));
byte
[] b =
new
byte
[bs];
for
(
int
i = 0; i < count; i++)
{
if
(input.Read(b, 0, bs) == 0)
input.BaseStream.Seek(0, SeekOrigin.Begin);
}
Console.WriteLine(
"Read successed! "
);
Console.WriteLine(
"Total cost "
+ (
new
TimeSpan(DateTime.Now.Ticks - start)).TotalSeconds +
" seconds"
);
}
catch
(Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
if
(input !=
null
)
{
input.Close();
}
//Console.ReadKey(true);
}
}
}
}
|
这样,就完成了类似linux上Direct IO模式读取文件的操作。
通过这个例子可以看出,C#不仅可以开发上层的应用,也可以结合一些非托管的dll完成更加底层的操作。
本文转自wang_yb博客园博客,原文链接:http://www.cnblogs.com/wang_yb/archive/2011/09/06/2168833.html,如需转载请自行联系原作者