c++ - 为什么我的 BSTR 到 std::wstring 的转换这么慢?我的测试仪坏了吗?

标签 c++ performance stdstring bstr

我经常需要将 BSTR 字符串转换为 std::wstringNULL BSTR 算作空 BSTR

我以前是这样做的:

#define CHECKNULLSTR(str) ((str) ? (str) : L"")
std::wstring wstr(CHECKNULLSTR(bstr));

它不处理内部'\0' 字符,但在分配足够的内存之前它还需要对字符进行计数,因此速度应该很慢。我想到了这个优化,它应该处理所有情况,不会截断,也不需要计数:

std::wstring wstr(bstr, bstr + ::SysStringLen(bstr));

为了测试此更改的影响,我编写了以下测试程序。它表明在大多数情况下优化花费的时间是原来的两倍多。在调试和发布配置中都可以观察到变化,我使用的是 VC++ 2013。

因此我的问题是,这里发生了什么? “指针对”迭代器构造函数怎么会比 C 字符串构造函数慢这么多?

完整的测试器

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
#include <iostream>

#define CHECKNULLSTR(str) ((str) ? (str) : L"")

ULONGLONG bstrAllocTest(UINT iterations = 10000)
{
    ULONGLONG totallen = 0;
    ULONGLONG start, stop, elapsed1, elapsed2;    
    BSTR bstr = ::SysAllocString( // 15 * 50 = 750 chars
                     L"01234567890123456789012345678901234567890123456789" //  1
                     L"01234567890123456789012345678901234567890123456789" //  2
                     L"01234567890123456789012345678901234567890123456789" //  3
                     L"01234567890123456789012345678901234567890123456789" //  4
                     L"01234567890123456789012345678901234567890123456789" //  5
                     L"01234567890123456789012345678901234567890123456789" //  6
                     L"01234567890123456789012345678901234567890123456789" //  7
                     L"01234567890123456789012345678901234567890123456789" //  8
                     L"01234567890123456789012345678901234567890123456789" //  9
                     L"01234567890123456789012345678901234567890123456789" // 10
                     L"01234567890123456789012345678901234567890123456789" // 11
                     L"01234567890123456789012345678901234567890123456789" // 12
                     L"01234567890123456789012345678901234567890123456789" // 13
                     L"01234567890123456789012345678901234567890123456789" // 14
                     L"01234567890123456789012345678901234567890123456789" // 15
                                );

    start = ::GetTickCount64();
    for (UINT i = 1; i <= iterations; ++i)
    {
        std::wstring wstr(CHECKNULLSTR(bstr));
        size_t len;
        ::StringCchLengthW(wstr.c_str(), STRSAFE_MAX_CCH, &len);
        totallen += len;
    }
    stop = ::GetTickCount64();
    elapsed1 = stop - start;

    start = ::GetTickCount64();
    for (UINT i = 1; i <= iterations; ++i)
    {
        std::wstring wstr(bstr, bstr + ::SysStringLen(bstr));
        size_t len;
        ::StringCchLengthW(wstr.c_str(), STRSAFE_MAX_CCH, &len);
        totallen += len;
    }
    stop = ::GetTickCount64();
    elapsed2 = stop - start;

    wprintf_s(L"Iter:\t%u\n"
              L"Elapsed (CHECKNULLSTR):\t%10llu ms\n"
              L"Elapsed (Ptr iter pair):\t%10llu ms\n"
              L"Speed difference:\t%f %%\n",
              iterations,
              elapsed1,
              elapsed2,
              (static_cast<double>(elapsed2) / elapsed1 * 100));

    ::SysFreeString(bstr);
    return totallen;
}

int wmain(int argc, char* argv[])
{
    ULONGLONG dummylen = bstrAllocTest(100 * 1000);
    wprintf_s(L"\nTotal length:\t%llu", dummylen);
    getchar();
    return 0;
}

我系统上的输出

Iter:   100000
Elapsed (CHECKNULLSTR):        296 ms
Elapsed (Ptr it pair):         577 ms
Speed difference:       194.932432 %

Total length:   150000000

最佳答案

确实很有趣,也有点令人惊讶。 Visual C++ 2013 Update 4 的性能差异取决于两个 std::wstring 构造函数在其标准库中的实现方式。一般来说,采用一对迭代器的构造函数必须处理更多的情况,因为这些迭代器不一定是指针,它们可以指向其他数据类型而不是字符串的字符类型(字符类型只需要从指向的类型构造由迭代器)。但是,我希望实现能够使用优化代码单独处理您的案例。

std::wstring wstr(CHECKNULLSTR(bstr));确实是扫描字符串结束0,然后分配,然后以最快的速度复制字符串数据可能的方法是使用memcpy,它是使用汇编代码实现的。

std::wstring wstr(bstr, bstr +::SysStringLen(bstr)); 确实避免了扫描,因为 ::SysStringLen(非常快,只是读取存储的长度),然后分配,然后使用以下循环复制字符串数据:

for (; _First != _Last; ++_First)
   append((size_type)1, (_Elem)*_First);

VC12 决定不内联 append 调用(这是可以理解的,主体相当大),正如您可以想象的那样,与炽热的 相比,所有这些都会带来相当多的开销>memcpy.


一种解决方案是使用带有指针和计数的std::basic_string 构造函数(Ben Voigt 在他的评论中也提到了),如下所示:

std::wstring wstr(CHECKNULLSTR(bstr), ::SysStringLen(bstr));

我刚刚对其进行了测试,它确实在 Visual C++ 2013 上带来了预期的好处 - 它有时只需要第一个版本的一半时间,在最坏的情况下大约需要 75%(无论如何这些都是近似测量)。


Visual C++ 2015 CTP6 中的标准库实现为采用迭代器对的构造函数优化了代码路径,当迭代器实际上是指向与要构造的字符串相同的字符类型的指针时,生成的代码与上面的指针和计数变体。因此,在这个版本中,您使用这两个构造函数变体中的哪一个并不重要 - 它们都比仅采用指针的版本更快。

关于c++ - 为什么我的 BSTR 到 std::wstring 的转换这么慢?我的测试仪坏了吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29564898/

相关文章:

android - 无法使用 libzip 和 FreeType 直接从 ZIP 加载 TTF 文件

c++ - 如何在 Qt 中用图像填充 Rectangle(Rect 对象)?

.net - 如果.net 中有太多注释代码会影响代码性能吗?

c++ - 遍历对列表,列表在数组中

c++ - 使用 std::string 迭代器查找它的字符串的开始和结束

c++ - 编译/链接过程如何工作?

C++ 调用尚未定义的函数

xml - XQuery 性能 - 无序是答案吗?

java - 这两种做法在 Java 中哪一种更有效?

C++ 从字符串中删除字符