【问题标题】:Why SDL_RWops performs so poorly when writing to file compared to cstdio and std::fstream?与 cstdio 和 std::fstream 相比,为什么 SDL_RWops 在写入文件时表现如此糟糕?
【发布时间】:2016-06-12 14:18:54
【问题描述】:

我目前正在将我的爱好项目从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);

所有测试用例均使用 g++ 5.3.0 (mingw-w64) x86 和 -O3 编译。我用过 SDL 2.0.4。

我也尝试过 -O0,结果相似(慢 0.02 到 0.25 秒)。

查看这些结果后,我有一个明显的问题:
为什么 SDL_RWops 写入性能这么差?
我该怎么做才能让它表现得更好?


编辑:这是windows_file_write()(来自SDL)的代码,这是io-&gt;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;
}

【问题讨论】:

  • 也许SDL_RWops 没有缓冲它的输出。尝试将所有内容放在一个数组中并一次写入。
  • @BenjaminLindley 看起来你是对的。一次写入整个数组给我 0.006 秒。但我不明白为什么 SDL_RWops 不缓冲输出。 SDL 文档暗示它可能会这样做。
  • 关于您的编辑:您可以看出它对于小型写入很慢,因为每个windows_file_write 都直接调用WriteFile。磁盘访问肯定会被 Windows 缓冲(除非在 CreateFile 期间明确要求不缓冲),但它没有缓冲的是 API 调用的数量,因此您会得到很多系统调用开销。
  • 我想知道 SDL_RWFromFile 与使用标准库相比应该有什么优势,但这是另一个问题。
  • 我太好奇了,才发现Can we use std::fstream instead of SDL_RWops in SDL2?有解释

标签: c++ c file sdl sdl-2


【解决方案1】:

简而言之:我设法改进了它。现在我得到 0.316382 秒,这仅比其他解决方案慢一点。
但这是我一生中做过的最肮脏的黑客攻击之一. 如果有更好的解决方案,我将不胜感激。

它是如何完成的:我已经为 SDL_RWFromFile() 推出了自定义替换:我已经从 SDL_rwops.c 复制粘贴了实现并删除了所有预处理器分支,就好像只定义了 HAVE_STDIO_H 一样。该函数包含对SDL_RWFromFP() 的调用,因此我也复制粘贴了SDL_RWFromFP() 并对它应用了相同的修改。反过来,SDL_RWFromFP() 依赖于stdio_size()stdio_read()stdio_write()stdio_seek()stdio_close()(这些也是SDL_rwops.c 的一部分),因此我也复制粘贴了它们。反过来,这些依赖于(再次!)struct SDL_RWops 内部的“隐藏”联合的某些字段,这些字段在使用预处理器的 Windows 上被禁用。我没有更改标题,而是更改了复制粘贴的代码以使用“隐藏”联合的不同成员,这些成员确实存在于 Windows 上。 (这是安全的,因为除了我自己的和复制粘贴的代码之外,没有任何东西会触及该结构。)还进行了一些其他调整以使代码作为 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

【讨论】:

  • 我会接受这个,但我仍在寻找更好的解决方案。如果有人找到了,我会接受他们的回答。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-17
  • 2010-09-06
相关资源
最近更新 更多