在 MEX 中超快地将二进制文件写入磁盘
Write binary file to disk super fast in MEX
我需要尽快将大量数据写入磁盘。在 MATLAB 中,我可以使用 fwrite
:
function writeBinaryFileMatlab(data)
fid = fopen('file_matlab.bin', 'w');
fwrite(fid, data, class(data));
fclose(fid);
end
现在我必须做同样的事情,但是来自 MATLAB 调用的 MEX 文件。因此,我设置了一个可以使用 fstream
或 fopen
写入文件的 MEX 函数(受 this SO post 结果的启发)。然而,这比从 MATLAB 调用 fwrite
慢得多,如下所示。为什么会这样,如何提高 MEX 函数的写入速度。
#include "mex.h"
#include <iostream>
#include <stdio.h>
#include <fstream>
using namespace std;
void writeBinFile(int16_t *data, size_t size)
{
FILE *fID;
fID = fopen("file_fopen.bin", "wb");
fwrite(data, sizeof(int16_t), size, fID);
fclose(fID);
}
void writeBinFileFast(int16_t *data, size_t size)
{
ofstream file("file_ostream.bin", std::ios::out | std::ios::binary);
file.write((char *)&data[0], size * sizeof(int16_t));
file.close();
}
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
const mxArray *mxPtr = prhs[0];
size_t nelems = mxGetNumberOfElements(mxPtr);
int16_t *ptr = (int16_t *)mxGetData(mxPtr);
#ifdef USE_OFSTREAM
writeBinFileFast(ptr, nelems);
#else
writeBinFile(ptr, nelems);
#endif
}
然后我使用以下脚本检查性能:
mex -R2018a -Iinclude CXXFLAGS="$CXXFLAGS -O3" -DUSE_OFSTREAM main.cpp -output writefast_ofstream
mex -R2018a -Iinclude CXXFLAGS="$CXXFLAGS -O3" main.cpp -output writefast_fwrite
for k = 1:10
sizeBytes = 2^k * 1024 * 1024;
fprintf('Generating data of size %i MB\n', sizeBytes / 2^20)
M = sizeBytes / 2; % 2 bytes for an int16
sizeMB(k) = sizeBytes / 2^20;
data = int16(rand(M, 1) * 100);
fprintf('TESTING: write matlab\n')
t_matlab(k) = timeit(@() writeBinaryFileMatlab(data));
fprintf('TESTING: write ofstream\n')
t_ofstream(k) = timeit(@() writefast_ofstream(data), 0);
fprintf('TESTING: write fwrite\n')
t_fwrite(k) = timeit(@() writefast_fwrite(data), 0);
end
% and plot result
figure(14); clf;
plot((sizeMB), t_matlab)
hold on
plot((sizeMB), t_ofstream)
plot((sizeMB), t_fwrite)
legend('Matlab', 'ofstream', 'fwrite')
xticks(sizeMB)
这给了我下面的情节。为什么从 MATLAB 调用 fwrite
比从 MEX 调用快得多?我怎样才能在我的 MEX 函数中达到相同的速度?
我正在使用 Windows 10. 配备 Core i7、SSD 的笔记本电脑。
更新
我尝试了评论中的各种建议,但仍然没有达到 MATLAB 的 fwrite
性能。在此处查看带有源代码的回购协议:https://github.com/rick3rt/saveBinaryDataMex
这是 MSVC 2017 的结果,结合了 rahnema1 的建议:
更新 2
哇,我终于得到了比 MATLAB 更快的东西! 答案成功了:)
这里结合了所有建议方法的数字(完整的 src 可以在 Github 上找到)。
[不幸的是,这只是部分答案。]
这是一个 Windows 问题。我尝试在 macOS 上重现您的结果,并发现了一个不同的、有趣的行为。我修改了您的代码以区分 C fwrite
和 C++ std::fwrite
,并添加了使用较低级别 Posix write
.
编写的代码
这是 C++ 代码:
#include "mex.h"
#include <stdio.h>
#include <cstdio>
#include <fcntl.h>
#include <unistd.h>
void writeBinFile_c(int16_t *data, std::size_t size)
{
::FILE *fID = ::fopen("file_c.bin", "wb");
::fwrite(data, sizeof(int16_t), size, fID);
::fclose(fID);
}
void writeBinFile_std(int16_t *data, std::size_t size)
{
std::FILE *fID = std::fopen("file_std.bin", "wb");
std::fwrite(data, sizeof(int16_t), size, fID);
std::fclose(fID);
}
void writeBinFile_unix(int16_t *data, std::size_t size)
{
int fID = open("file_unix.bin", O_CREAT|O_WRONLY|O_TRUNC);
::write(fID, data, sizeof(int16_t) * size);
::close(fID);
}
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
const mxArray *mxPtr = prhs[0];
std::size_t nelems = mxGetNumberOfElements(mxPtr);
int16_t *ptr = (int16_t *)mxGetData(mxPtr);
double mode = -1;
if (nrhs > 1) {
mode = mxGetScalar(prhs[1]);
}
if (mode == 0) {
writeBinFile_c(ptr, nelems);
} else if (mode == 1) {
writeBinFile_std(ptr, nelems);
} else if (mode == 2) {
writeBinFile_unix(ptr, nelems);
} else {
mexErrMsgTxt("Wrong mode!");
}
}
这是 MATLAB 代码:
mex -R2018a -Iinclude CXXFLAGS="$CXXFLAGS -O3" writefast.cpp
N = 10;
sizeMB = zeros(1,N);
t_matlab = zeros(1,N);
t_fwrite_c = zeros(1,N);
t_fwrite_std = zeros(1,N);
t_unix = zeros(1,N);
for k = 1:10
sizeBytes = 2^k * 1024 * 1024;
fprintf('Generating data of size %i MB\n', sizeBytes / 2^20)
M = sizeBytes / 2; % 2 bytes for an int16
sizeMB(k) = sizeBytes / 2^20;
data = int16(rand(M, 1) * 100);
fprintf('TESTING: matlab\n')
t_matlab(k) = timeit(@() writeBinaryFileMatlab(data));
fprintf('TESTING: ::fwrite\n')
t_fwrite_c(k) = timeit(@() writefast(data, 0), 0);
fprintf('TESTING: std::fwrite\n')
t_fwrite_std(k) = timeit(@() writefast(data, 1), 0);
fprintf('TESTING: Unix write\n')
t_unix(k) = timeit(@() writefast(data, 1), 0);
end
% and plot result
figure
plot((sizeMB), t_matlab)
hold on
plot((sizeMB), t_fwrite_c)
plot((sizeMB), t_fwrite_std)
plot((sizeMB), t_unix)
legend('Matlab', 'C std lib', 'C++ Std lib', 'Unix')
xticks(sizeMB)
set(gca,'xscale','log','yscale','log')
function writeBinaryFileMatlab(data)
fid = fopen('file_matlab.bin', 'w');
fwrite(fid, data, class(data));
fclose(fid);
end
这些是两个 运行 的输出:
请注意时序如何在高达 64 MB 的情况下保持一致,然后出现分歧。在 128 MB 及以上时,时间足以让 timeit
到 运行 工具在内部循环中仅一次,因此您会看到单个 运行 秒的中值时间,而不是平均在多个 运行s 上,就像在 64 MB 及以下时一样。因此对于 128 MB 及以上,我们看到时间在两个不同时间之间翻转,这可能是缓存的影响。但是在不同的 运行 中,是不同的方法变慢或变快,所以我很清楚它们都是一样的。
因此,在 macOS 上,MATLAB 的 fwrite
和 C 库 fwrite
之间没有区别。你看到的一定是Windows问题。
而且我很确定这与缓存有关,因为:
This post on Undocumented MATLAB 讨论了 fwrite
的性能,以及默认情况下,MATLAB 如何在每次调用 fwrite
后刷新缓存。这在这里无关紧要,因为只有一次调用 fwrite
。但是 post 表示 MATLAB 函数处理缓存的方式与 C 库的不同。
The C library fwrite
工作 就好像 它为每个要写入的字节调用 fputc
一样。它可能实际上并没有这样做,但这可能表明 Windows 上出了什么问题。请注意,在 Windows 上,对于 MSVC 和 MinGW 编译器,您使用相同的 C 库 msvcrt
。一定是问题出在了,MATLAB 一定没有用它来写入文件。
如某些 posts 中所示,非常大的缓冲区往往会降低性能。所以缓冲区是一部分一部分写入文件的。对我来说 8 MiB
给出了最好的性能。
void writeBinFilePartByPart(int16_t *int_data, size_t size)
{
size_t part = 8 * 1024 * 1024;
size = size * sizeof(int16_t);
char *data = reinterpret_cast<char *> (int_data);
HANDLE file = CreateFileA (
"windows_test.bin",
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
// Expand file size
SetFilePointer (file, size, NULL, FILE_BEGIN);
SetEndOfFile (file);
SetFilePointer (file, 0, NULL, FILE_BEGIN);
DWORD written;
if (size < part)
{
WriteFile (file, data, size, &written, NULL);
CloseHandle (file);
return;
}
size_t rem = size % part;
for (size_t i = 0; i < size-rem; i += part)
{
WriteFile (file, data+i, part, &written, NULL);
}
if (rem)
WriteFile (file, data+size-rem, rem, &written, NULL);
CloseHandle (file);
}
将输出与@Cris Luengo 提到的 C++ Std lib
方法进行比较:
我需要尽快将大量数据写入磁盘。在 MATLAB 中,我可以使用 fwrite
:
function writeBinaryFileMatlab(data)
fid = fopen('file_matlab.bin', 'w');
fwrite(fid, data, class(data));
fclose(fid);
end
现在我必须做同样的事情,但是来自 MATLAB 调用的 MEX 文件。因此,我设置了一个可以使用 fstream
或 fopen
写入文件的 MEX 函数(受 this SO post 结果的启发)。然而,这比从 MATLAB 调用 fwrite
慢得多,如下所示。为什么会这样,如何提高 MEX 函数的写入速度。
#include "mex.h"
#include <iostream>
#include <stdio.h>
#include <fstream>
using namespace std;
void writeBinFile(int16_t *data, size_t size)
{
FILE *fID;
fID = fopen("file_fopen.bin", "wb");
fwrite(data, sizeof(int16_t), size, fID);
fclose(fID);
}
void writeBinFileFast(int16_t *data, size_t size)
{
ofstream file("file_ostream.bin", std::ios::out | std::ios::binary);
file.write((char *)&data[0], size * sizeof(int16_t));
file.close();
}
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
const mxArray *mxPtr = prhs[0];
size_t nelems = mxGetNumberOfElements(mxPtr);
int16_t *ptr = (int16_t *)mxGetData(mxPtr);
#ifdef USE_OFSTREAM
writeBinFileFast(ptr, nelems);
#else
writeBinFile(ptr, nelems);
#endif
}
然后我使用以下脚本检查性能:
mex -R2018a -Iinclude CXXFLAGS="$CXXFLAGS -O3" -DUSE_OFSTREAM main.cpp -output writefast_ofstream
mex -R2018a -Iinclude CXXFLAGS="$CXXFLAGS -O3" main.cpp -output writefast_fwrite
for k = 1:10
sizeBytes = 2^k * 1024 * 1024;
fprintf('Generating data of size %i MB\n', sizeBytes / 2^20)
M = sizeBytes / 2; % 2 bytes for an int16
sizeMB(k) = sizeBytes / 2^20;
data = int16(rand(M, 1) * 100);
fprintf('TESTING: write matlab\n')
t_matlab(k) = timeit(@() writeBinaryFileMatlab(data));
fprintf('TESTING: write ofstream\n')
t_ofstream(k) = timeit(@() writefast_ofstream(data), 0);
fprintf('TESTING: write fwrite\n')
t_fwrite(k) = timeit(@() writefast_fwrite(data), 0);
end
% and plot result
figure(14); clf;
plot((sizeMB), t_matlab)
hold on
plot((sizeMB), t_ofstream)
plot((sizeMB), t_fwrite)
legend('Matlab', 'ofstream', 'fwrite')
xticks(sizeMB)
这给了我下面的情节。为什么从 MATLAB 调用 fwrite
比从 MEX 调用快得多?我怎样才能在我的 MEX 函数中达到相同的速度?
我正在使用 Windows 10. 配备 Core i7、SSD 的笔记本电脑。
更新
我尝试了评论中的各种建议,但仍然没有达到 MATLAB 的 fwrite
性能。在此处查看带有源代码的回购协议:https://github.com/rick3rt/saveBinaryDataMex
这是 MSVC 2017 的结果,结合了 rahnema1 的建议:
更新 2
哇,我终于得到了比 MATLAB 更快的东西!
[不幸的是,这只是部分答案。]
这是一个 Windows 问题。我尝试在 macOS 上重现您的结果,并发现了一个不同的、有趣的行为。我修改了您的代码以区分 C fwrite
和 C++ std::fwrite
,并添加了使用较低级别 Posix write
.
这是 C++ 代码:
#include "mex.h"
#include <stdio.h>
#include <cstdio>
#include <fcntl.h>
#include <unistd.h>
void writeBinFile_c(int16_t *data, std::size_t size)
{
::FILE *fID = ::fopen("file_c.bin", "wb");
::fwrite(data, sizeof(int16_t), size, fID);
::fclose(fID);
}
void writeBinFile_std(int16_t *data, std::size_t size)
{
std::FILE *fID = std::fopen("file_std.bin", "wb");
std::fwrite(data, sizeof(int16_t), size, fID);
std::fclose(fID);
}
void writeBinFile_unix(int16_t *data, std::size_t size)
{
int fID = open("file_unix.bin", O_CREAT|O_WRONLY|O_TRUNC);
::write(fID, data, sizeof(int16_t) * size);
::close(fID);
}
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
const mxArray *mxPtr = prhs[0];
std::size_t nelems = mxGetNumberOfElements(mxPtr);
int16_t *ptr = (int16_t *)mxGetData(mxPtr);
double mode = -1;
if (nrhs > 1) {
mode = mxGetScalar(prhs[1]);
}
if (mode == 0) {
writeBinFile_c(ptr, nelems);
} else if (mode == 1) {
writeBinFile_std(ptr, nelems);
} else if (mode == 2) {
writeBinFile_unix(ptr, nelems);
} else {
mexErrMsgTxt("Wrong mode!");
}
}
这是 MATLAB 代码:
mex -R2018a -Iinclude CXXFLAGS="$CXXFLAGS -O3" writefast.cpp
N = 10;
sizeMB = zeros(1,N);
t_matlab = zeros(1,N);
t_fwrite_c = zeros(1,N);
t_fwrite_std = zeros(1,N);
t_unix = zeros(1,N);
for k = 1:10
sizeBytes = 2^k * 1024 * 1024;
fprintf('Generating data of size %i MB\n', sizeBytes / 2^20)
M = sizeBytes / 2; % 2 bytes for an int16
sizeMB(k) = sizeBytes / 2^20;
data = int16(rand(M, 1) * 100);
fprintf('TESTING: matlab\n')
t_matlab(k) = timeit(@() writeBinaryFileMatlab(data));
fprintf('TESTING: ::fwrite\n')
t_fwrite_c(k) = timeit(@() writefast(data, 0), 0);
fprintf('TESTING: std::fwrite\n')
t_fwrite_std(k) = timeit(@() writefast(data, 1), 0);
fprintf('TESTING: Unix write\n')
t_unix(k) = timeit(@() writefast(data, 1), 0);
end
% and plot result
figure
plot((sizeMB), t_matlab)
hold on
plot((sizeMB), t_fwrite_c)
plot((sizeMB), t_fwrite_std)
plot((sizeMB), t_unix)
legend('Matlab', 'C std lib', 'C++ Std lib', 'Unix')
xticks(sizeMB)
set(gca,'xscale','log','yscale','log')
function writeBinaryFileMatlab(data)
fid = fopen('file_matlab.bin', 'w');
fwrite(fid, data, class(data));
fclose(fid);
end
这些是两个 运行 的输出:
请注意时序如何在高达 64 MB 的情况下保持一致,然后出现分歧。在 128 MB 及以上时,时间足以让 timeit
到 运行 工具在内部循环中仅一次,因此您会看到单个 运行 秒的中值时间,而不是平均在多个 运行s 上,就像在 64 MB 及以下时一样。因此对于 128 MB 及以上,我们看到时间在两个不同时间之间翻转,这可能是缓存的影响。但是在不同的 运行 中,是不同的方法变慢或变快,所以我很清楚它们都是一样的。
因此,在 macOS 上,MATLAB 的 fwrite
和 C 库 fwrite
之间没有区别。你看到的一定是Windows问题。
而且我很确定这与缓存有关,因为:
This post on Undocumented MATLAB 讨论了
fwrite
的性能,以及默认情况下,MATLAB 如何在每次调用fwrite
后刷新缓存。这在这里无关紧要,因为只有一次调用fwrite
。但是 post 表示 MATLAB 函数处理缓存的方式与 C 库的不同。The C library
fwrite
工作 就好像 它为每个要写入的字节调用fputc
一样。它可能实际上并没有这样做,但这可能表明 Windows 上出了什么问题。请注意,在 Windows 上,对于 MSVC 和 MinGW 编译器,您使用相同的 C 库msvcrt
。一定是问题出在了,MATLAB 一定没有用它来写入文件。
如某些 posts 中所示,非常大的缓冲区往往会降低性能。所以缓冲区是一部分一部分写入文件的。对我来说 8 MiB
给出了最好的性能。
void writeBinFilePartByPart(int16_t *int_data, size_t size)
{
size_t part = 8 * 1024 * 1024;
size = size * sizeof(int16_t);
char *data = reinterpret_cast<char *> (int_data);
HANDLE file = CreateFileA (
"windows_test.bin",
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL);
// Expand file size
SetFilePointer (file, size, NULL, FILE_BEGIN);
SetEndOfFile (file);
SetFilePointer (file, 0, NULL, FILE_BEGIN);
DWORD written;
if (size < part)
{
WriteFile (file, data, size, &written, NULL);
CloseHandle (file);
return;
}
size_t rem = size % part;
for (size_t i = 0; i < size-rem; i += part)
{
WriteFile (file, data+i, part, &written, NULL);
}
if (rem)
WriteFile (file, data+size-rem, rem, &written, NULL);
CloseHandle (file);
}
将输出与@Cris Luengo 提到的 C++ Std lib
方法进行比较: