与 cstdio 和 std::fstream 相比,为什么 SDL_RWops 在写入文件时性能如此差?
Why SDL_RWops performs so poorly when writing to file compared to cstdio and std::fstream?
我目前正在将我的爱好项目从 std::fstream
迁移到 SDL_RWops
(因为 SDL_RWops
是我在 Android 上加载资产的唯一简单选择) .
从文件读取完美,但写入文件却非常慢。
考虑以下测试用例:
C 标准 IO - 0.217193 秒
std::FILE *io = std::fopen("o.txt", "w");
for (int i = 0; i < 1024*1024*4; i++)
std::putc('0', io);
std::fclose(io);
C++ 流 - 0.278278 秒
std::ofstream io("o.txt");
for (int i = 0; i < 1024*1024*4; i++)
io << '0';
io.close();
SDL_RWops:- 17.9893 秒
SDL_RWops *io = SDL_RWFromFile("o.txt", "w");
for (int i = 0; i < 1024*1024*4; i++)
io->write(io, "0", 1, 1);
io->close(io);
所有测试用例均使用带有 -O3 的 g++ 5.3.0 (mingw-w64) x86 编译。我用过 SDL 2.0.4.
我也试过 -O0 得到类似的结果(慢 0.02 到 0.25 秒)。
看完这些结果后,我有一个明显的问题:
为什么SDL_RWops写入性能这么差?
我该怎么做才能让它表现更好?
编辑: 这里是windows_file_write()
的代码(来自SDL),io->write
应该指向的地方。它应该 进行缓冲输出,但我不确定它是如何工作的。
static size_t SDLCALL
windows_file_write(SDL_RWops * context, const void *ptr, size_t size, size_t num)
{
size_t total_bytes;
DWORD byte_written;
size_t nwritten;
total_bytes = size * num;
if (!context || context->hidden.windowsio.h == INVALID_HANDLE_VALUE || total_bytes <= 0 || !size)
return 0;
if (context->hidden.windowsio.buffer.left) {
SetFilePointer(context->hidden.windowsio.h,
-(LONG)context->hidden.windowsio.buffer.left, NULL,
FILE_CURRENT);
context->hidden.windowsio.buffer.left = 0;
}
/* if in append mode, we must go to the EOF before write */
if (context->hidden.windowsio.append) {
if (SetFilePointer(context->hidden.windowsio.h, 0L, NULL, FILE_END) ==
INVALID_SET_FILE_POINTER) {
SDL_Error(SDL_EFWRITE);
return 0;
}
}
if (!WriteFile
(context->hidden.windowsio.h, ptr, (DWORD)total_bytes, &byte_written, NULL)) {
SDL_Error(SDL_EFWRITE);
return 0;
}
nwritten = byte_written / size;
return nwritten;
}
简而言之:我设法改进了它。现在我得到 0.316382 秒,只比其他解决方案慢一点。
但这是我一生中做过的最肮脏的黑客攻击之一。如果有更好的解决方案,我将不胜感激。
它是如何完成的:我已经为 SDL_RWFromFile()
滚动了自定义替换:我已经 copy-pasted 从 SDL_rwops.c
实现并删除了所有预处理器分支,就好像只有 [=13] =] 被定义。该函数包含对 SDL_RWFromFP()
的调用,因此我也有 copy-pasted SDL_RWFromFP()
并对其应用了相同的修改。反过来,SDL_RWFromFP()
依赖于 stdio_size()
、stdio_read()
、stdio_write()
、stdio_seek()
和 stdio_close()
(这些是 SDL_rwops.c
的一部分也是),因此我也 copy-pasted 他们。反过来,这些(再次!)依赖于 struct SDL_RWops
内部 "hidden" 联合的某些字段,这些字段在 windows 上使用预处理器禁用。我没有更改 header,而是更改了 copy-pasted 代码以使用 "hidden" 联合的 不同 成员, 存在于 windows。 (这是安全的,因为除了我自己的代码和 copy-pasted 代码之外,没有任何其他内容触及该结构。)进行了一些其他调整以使代码作为 C++ 而不是 C.
这是我得到的:
#if OnWindows
#define hidden_stdio_fp ((FILE * &)context->hidden.windowsio.h)
#define hidden_stdio_autoclose ((SDL_bool &)context->hidden.windowsio.append)
// ** Begin copied code **
static auto stdio_size = [](SDL_RWops * context) -> int64_t
{
int64_t pos, size;
pos = SDL_RWseek(context, 0, RW_SEEK_CUR);
if (pos < 0) {
return -1;
}
size = SDL_RWseek(context, 0, RW_SEEK_END);
SDL_RWseek(context, pos, RW_SEEK_SET);
return size;
};
static auto stdio_seek = [](SDL_RWops * context, int64_t offset, int whence) -> int64_t
{
#ifdef HAVE_FSEEKO64
if (std::fseeko64(hidden_stdio_fp, (off64_t)offset, whence) == 0) {
return std::ftello64(hidden_stdio_fp);
}
#elif defined(HAVE_FSEEKO)
if (std::fseeko(hidden_stdio_fp, (off_t)offset, whence) == 0) {
return std::ftello(hidden_stdio_fp);
}
#elif defined(HAVE__FSEEKI64)
if (std::_fseeki64(hidden_stdio_fp, offset, whence) == 0) {
return std::_ftelli64(hidden_stdio_fp);
}
#else
if (std::fseek(hidden_stdio_fp, offset, whence) == 0) {
return std::ftell(hidden_stdio_fp);
}
#endif
return SDL_Error(SDL_EFSEEK);
};
static auto stdio_read = [](SDL_RWops * context, void *ptr, std::size_t size, std::size_t maxnum) -> std::size_t
{
std::size_t nread;
nread = std::fread(ptr, size, maxnum, hidden_stdio_fp);
if (nread == 0 && std::ferror(hidden_stdio_fp)) {
SDL_Error(SDL_EFREAD);
}
return nread;
};
static auto stdio_write = [](SDL_RWops * context, const void *ptr, std::size_t size, std::size_t num) -> std::size_t
{
std::size_t nwrote;
nwrote = std::fwrite(ptr, size, num, hidden_stdio_fp);
if (nwrote == 0 && std::ferror(hidden_stdio_fp)) {
SDL_Error(SDL_EFWRITE);
}
return nwrote;
};
static auto stdio_close = [](SDL_RWops * context) -> int
{
int status = 0;
if (context) {
if (hidden_stdio_autoclose) {
/* WARNING: Check the return value here! */
if (std::fclose(hidden_stdio_fp) != 0) {
status = SDL_Error(SDL_EFWRITE);
}
}
SDL_FreeRW(context);
}
return status;
};
static auto RWFromFP = [](FILE * fp, SDL_bool autoclose) -> SDL_RWops *
{
SDL_RWops *context = 0;
context = SDL_AllocRW();
if (context != 0) {
context->size = stdio_size;
context->seek = stdio_seek;
context->read = stdio_read;
context->write = stdio_write;
context->close = stdio_close;
hidden_stdio_fp = fp;
hidden_stdio_autoclose = autoclose;
context->type = SDL_RWOPS_STDFILE;
}
return context;
};
static auto SDL_RWFromFile = [](const char *file, const char *mode) -> SDL_RWops *
{
SDL_RWops *context = 0;
if (!file || !*file || !mode || !*mode) {
SDL_SetError("SDL_RWFromFile(): No file or no mode specified");
return 0;
}
FILE *fp = std::fopen(file, mode);
if (fp == 0) {
SDL_SetError("Couldn't open %s", file);
} else {
context = RWFromFP(fp, (SDL_bool)1);
}
return context;
};
// ** End copied code **
#undef hidden_stdio_fp
#undef hidden_stdio_autoclose
#endif
我目前正在将我的爱好项目从 std::fstream
迁移到 SDL_RWops
(因为 SDL_RWops
是我在 Android 上加载资产的唯一简单选择) .
从文件读取完美,但写入文件却非常慢。
考虑以下测试用例:
C 标准 IO - 0.217193 秒
std::FILE *io = std::fopen("o.txt", "w");
for (int i = 0; i < 1024*1024*4; i++)
std::putc('0', io);
std::fclose(io);
C++ 流 - 0.278278 秒
std::ofstream io("o.txt");
for (int i = 0; i < 1024*1024*4; i++)
io << '0';
io.close();
SDL_RWops:- 17.9893 秒
SDL_RWops *io = SDL_RWFromFile("o.txt", "w");
for (int i = 0; i < 1024*1024*4; i++)
io->write(io, "0", 1, 1);
io->close(io);
所有测试用例均使用带有 -O3 的 g++ 5.3.0 (mingw-w64) x86 编译。我用过 SDL 2.0.4.
我也试过 -O0 得到类似的结果(慢 0.02 到 0.25 秒)。
看完这些结果后,我有一个明显的问题:
为什么SDL_RWops写入性能这么差?
我该怎么做才能让它表现更好?
编辑: 这里是windows_file_write()
的代码(来自SDL),io->write
应该指向的地方。它应该 进行缓冲输出,但我不确定它是如何工作的。
static size_t SDLCALL
windows_file_write(SDL_RWops * context, const void *ptr, size_t size, size_t num)
{
size_t total_bytes;
DWORD byte_written;
size_t nwritten;
total_bytes = size * num;
if (!context || context->hidden.windowsio.h == INVALID_HANDLE_VALUE || total_bytes <= 0 || !size)
return 0;
if (context->hidden.windowsio.buffer.left) {
SetFilePointer(context->hidden.windowsio.h,
-(LONG)context->hidden.windowsio.buffer.left, NULL,
FILE_CURRENT);
context->hidden.windowsio.buffer.left = 0;
}
/* if in append mode, we must go to the EOF before write */
if (context->hidden.windowsio.append) {
if (SetFilePointer(context->hidden.windowsio.h, 0L, NULL, FILE_END) ==
INVALID_SET_FILE_POINTER) {
SDL_Error(SDL_EFWRITE);
return 0;
}
}
if (!WriteFile
(context->hidden.windowsio.h, ptr, (DWORD)total_bytes, &byte_written, NULL)) {
SDL_Error(SDL_EFWRITE);
return 0;
}
nwritten = byte_written / size;
return nwritten;
}
简而言之:我设法改进了它。现在我得到 0.316382 秒,只比其他解决方案慢一点。
但这是我一生中做过的最肮脏的黑客攻击之一。如果有更好的解决方案,我将不胜感激。
它是如何完成的:我已经为 SDL_RWFromFile()
滚动了自定义替换:我已经 copy-pasted 从 SDL_rwops.c
实现并删除了所有预处理器分支,就好像只有 [=13] =] 被定义。该函数包含对 SDL_RWFromFP()
的调用,因此我也有 copy-pasted SDL_RWFromFP()
并对其应用了相同的修改。反过来,SDL_RWFromFP()
依赖于 stdio_size()
、stdio_read()
、stdio_write()
、stdio_seek()
和 stdio_close()
(这些是 SDL_rwops.c
的一部分也是),因此我也 copy-pasted 他们。反过来,这些(再次!)依赖于 struct SDL_RWops
内部 "hidden" 联合的某些字段,这些字段在 windows 上使用预处理器禁用。我没有更改 header,而是更改了 copy-pasted 代码以使用 "hidden" 联合的 不同 成员, 存在于 windows。 (这是安全的,因为除了我自己的代码和 copy-pasted 代码之外,没有任何其他内容触及该结构。)进行了一些其他调整以使代码作为 C++ 而不是 C.
这是我得到的:
#if OnWindows
#define hidden_stdio_fp ((FILE * &)context->hidden.windowsio.h)
#define hidden_stdio_autoclose ((SDL_bool &)context->hidden.windowsio.append)
// ** Begin copied code **
static auto stdio_size = [](SDL_RWops * context) -> int64_t
{
int64_t pos, size;
pos = SDL_RWseek(context, 0, RW_SEEK_CUR);
if (pos < 0) {
return -1;
}
size = SDL_RWseek(context, 0, RW_SEEK_END);
SDL_RWseek(context, pos, RW_SEEK_SET);
return size;
};
static auto stdio_seek = [](SDL_RWops * context, int64_t offset, int whence) -> int64_t
{
#ifdef HAVE_FSEEKO64
if (std::fseeko64(hidden_stdio_fp, (off64_t)offset, whence) == 0) {
return std::ftello64(hidden_stdio_fp);
}
#elif defined(HAVE_FSEEKO)
if (std::fseeko(hidden_stdio_fp, (off_t)offset, whence) == 0) {
return std::ftello(hidden_stdio_fp);
}
#elif defined(HAVE__FSEEKI64)
if (std::_fseeki64(hidden_stdio_fp, offset, whence) == 0) {
return std::_ftelli64(hidden_stdio_fp);
}
#else
if (std::fseek(hidden_stdio_fp, offset, whence) == 0) {
return std::ftell(hidden_stdio_fp);
}
#endif
return SDL_Error(SDL_EFSEEK);
};
static auto stdio_read = [](SDL_RWops * context, void *ptr, std::size_t size, std::size_t maxnum) -> std::size_t
{
std::size_t nread;
nread = std::fread(ptr, size, maxnum, hidden_stdio_fp);
if (nread == 0 && std::ferror(hidden_stdio_fp)) {
SDL_Error(SDL_EFREAD);
}
return nread;
};
static auto stdio_write = [](SDL_RWops * context, const void *ptr, std::size_t size, std::size_t num) -> std::size_t
{
std::size_t nwrote;
nwrote = std::fwrite(ptr, size, num, hidden_stdio_fp);
if (nwrote == 0 && std::ferror(hidden_stdio_fp)) {
SDL_Error(SDL_EFWRITE);
}
return nwrote;
};
static auto stdio_close = [](SDL_RWops * context) -> int
{
int status = 0;
if (context) {
if (hidden_stdio_autoclose) {
/* WARNING: Check the return value here! */
if (std::fclose(hidden_stdio_fp) != 0) {
status = SDL_Error(SDL_EFWRITE);
}
}
SDL_FreeRW(context);
}
return status;
};
static auto RWFromFP = [](FILE * fp, SDL_bool autoclose) -> SDL_RWops *
{
SDL_RWops *context = 0;
context = SDL_AllocRW();
if (context != 0) {
context->size = stdio_size;
context->seek = stdio_seek;
context->read = stdio_read;
context->write = stdio_write;
context->close = stdio_close;
hidden_stdio_fp = fp;
hidden_stdio_autoclose = autoclose;
context->type = SDL_RWOPS_STDFILE;
}
return context;
};
static auto SDL_RWFromFile = [](const char *file, const char *mode) -> SDL_RWops *
{
SDL_RWops *context = 0;
if (!file || !*file || !mode || !*mode) {
SDL_SetError("SDL_RWFromFile(): No file or no mode specified");
return 0;
}
FILE *fp = std::fopen(file, mode);
if (fp == 0) {
SDL_SetError("Couldn't open %s", file);
} else {
context = RWFromFP(fp, (SDL_bool)1);
}
return context;
};
// ** End copied code **
#undef hidden_stdio_fp
#undef hidden_stdio_autoclose
#endif