【问题标题】:utf8 <-> utf16: codecvt poor performanceutf8 <-> utf16:codecvt 性能不佳
【发布时间】:2014-11-29 13:25:06
【问题描述】:

我正在研究我的一些旧的(并且专门面向 win32)的东西,并考虑使其更现代/更便携 - 即在 C++11 中重新实现一些广泛可重用的部分。其中之一是 utf8 和 utf16 之间的转换。在 Win32 API 中,我使用 MultiByteToWideChar/WideCharToMultiByte,尝试使用此处的示例代码将这些东西移植到 C++11:https://stackoverflow.com/a/14809553。结果是

发布版本(由 MSVS 2013 编译,在 Core i7 3610QM 上运行)

stdlib                   = 1587.2 ms
Win32                    =  127.2 ms

调试构建

stdlib                   = 5733.8 ms
Win32                    =  127.2 ms

问题是 - 代码有问题吗?如果一切似乎都正常 - 这种性能差异是否有充分的理由?

测试代码如下:

#include <iostream>
#include <fstream>
#include <string>
#include <iterator>
#include <clocale>  
#include <codecvt> 

#define XU_BEGIN_TIMER(NAME)                       \
    {                                           \
        LARGE_INTEGER   __freq;                 \
        LARGE_INTEGER   __t0;                   \
        LARGE_INTEGER   __t1;                   \
        double          __tms;                  \
        const char*     __tname = NAME;         \
        char            __tbuf[0xff];           \
                                                \
        QueryPerformanceFrequency(&__freq);     \
        QueryPerformanceCounter(&__t0);         

#define XU_END_TIMER()                             \
        QueryPerformanceCounter(&__t1);         \
        __tms = (__t1.QuadPart - __t0.QuadPart) * 1000.0 / __freq.QuadPart; \
        sprintf_s(__tbuf, sizeof(__tbuf), "    %-24s = %6.1f ms\n", __tname, __tms ); \
        OutputDebugStringA(__tbuf);             \
        printf(__tbuf);                         \
    }   

std::string read_utf8() {
    std::ifstream infile("C:/temp/UTF-8-demo.txt");
    std::string fileData((std::istreambuf_iterator<char>(infile)),
                         std::istreambuf_iterator<char>());
    infile.close();

    return fileData;
}

void testMethod() {
    std::setlocale(LC_ALL, "en_US.UTF-8");
    std::string source = read_utf8();
    {
        std::string utf8;

        XU_BEGIN_TIMER("stdlib") {
            for( int i = 0; i < 1000; i++ ) {
                std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert2utf16;
                std::u16string utf16 = convert2utf16.from_bytes(source);

                std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert2utf8;
                utf8 = convert2utf8.to_bytes(utf16);
            }
        } XU_END_TIMER();

        FILE* output = fopen("c:\\temp\\utf8-std.dat", "wb");
        fwrite(utf8.c_str(), 1, utf8.length(), output);
        fclose(output);
    }

    char* utf8 = NULL;
    int cchA = 0;

    {
        XU_BEGIN_TIMER("Win32") {
            for( int i = 0; i < 1000; i++ ) {
                WCHAR* utf16 = new WCHAR[source.length() + 1];
                int cchW;
                utf8 = new char[source.length() + 1];

                cchW = MultiByteToWideChar(
                    CP_UTF8, 0, source.c_str(), source.length(),
                    utf16, source.length() + 1);

                cchA = WideCharToMultiByte(
                    CP_UTF8, 0, utf16, cchW,
                    utf8, source.length() + 1, NULL, false);

                delete[] utf16;
                if( i != 999 )
                    delete[] utf8;
            }
        } XU_END_TIMER();

        FILE* output = fopen("c:\\temp\\utf8-win.dat", "wb");
        fwrite(utf8, 1, cchA, output);
        fclose(output);

        delete[] utf8;
    }
}

【问题讨论】:

  • 您的 Win32 代码未正确分配缓冲区。 UTF-8 和 UTF-16 在它们的数据长度之间没有一对一的关系。您应该调用MultiByteToWideChar/WideCharToMultiByte 一次以计算必要的缓冲区大小,然后分配缓冲区,然后再次调用以进行实际转换。所以这会稍微影响时间。
  • Win32 因为 Vista 在内部使用 SSE 效果很好,很少 UTF 转码器会这样做。这将很难被击败。
  • @Remy Lebeau:是的,如果我不想分配额外的(真正的临时内存),我需要再调用一次 MultiByteToWideChar/WideCharToMultiByte - 这会将 win32 用例带到大约 127*2 = 250 毫秒。这仍然比 stdlib 快 6.5 倍。
  • 嗯,这很可悲。你所能做的就是羞辱这些家伙,让它变得更好。为此,请在 connect.microsoft.com 上发布此内容
  • @Mehrdad 没有链接。我正在努力优化我自己的 UTF-8 解码器——我的性能比我测试的所有东西都要好,除了 Windows,它根据输入保持大约 2 倍的速度。它把我逼疯了,所以我反编译了他们的二进制文件来看看。

标签: c++ performance c++11 utf-8


【解决方案1】:

在我自己的测试中,我发现wstring_convert 的构造函数调用有很大的开销,至少在 Windows 上是这样。正如其他答案所暗示的那样,您可能很难击败本机 Windows 实现,但请尝试修改您的代码以在循环之外构建转换器。我希望您会看到 5 倍到 20 倍的改进,尤其是在调试版本中。

【讨论】:

  • 这正是我面临的问题。将构造函数设为静态:boom!
  • 现在的问题是——你能从多个线程安全地使用那个静态对象吗? ;)
  • std::wstring_convert 不是线程安全的。您可以使用 thread_local 而不是 static。
【解决方案2】:

Win32 的 UTF8 转码,因为 Vista 在内部使用 SSE 效果很好,其他 UTF 转码器很少这样做。我怀疑即使是最高度优化的可移植代码也无法击败。

但是,如果您为 codecvt 提供的这个数字占用了 10 倍以上的时间,那么它的速度就会异常缓慢,并且表明它是一种幼稚的实现。在编写自己的 UTF-8 解码器时,我能够达到 Win32 的 2-3 倍的性能。这里有很大的改进空间,但你需要自定义实现一个 codecvt 来获得它。

【讨论】:

  • Win32 的 UTF8 转码,因为 Vista 内部使用 SSE 效果很好...... - 你有参考吗?
猜你喜欢
  • 2014-09-11
  • 2011-03-29
  • 2011-12-03
  • 2012-02-13
  • 1970-01-01
  • 2016-07-29
  • 2016-04-17
  • 1970-01-01
  • 2013-05-29
相关资源
最近更新 更多