Fel*_*bek 5 c++ performance bstr stdstring
我经常需要将BSTR字符串转换为std::wstring.一个NULL BSTR计数为空BSTR.
我过去常常这样做:
#define CHECKNULLSTR(str) ((str) ? (str) : L"")
std::wstring wstr(CHECKNULLSTR(bstr));
Run Code Online (Sandbox Code Playgroud)
它不处理内部'\0'字符,但它还需要在字符分配足够的内存之前对字符进行计数,因此它应该很慢.我想到了这个优化,它应该处理每个案例,不会截断,也不需要计算:
std::wstring wstr(bstr, bstr + ::SysStringLen(bstr));
Run Code Online (Sandbox Code Playgroud)
为了测试这种变化的影响,我编写了以下测试人员.它表明,在大多数情况下,优化需要的时间是原来的两倍多.在Debug和Release配置中都可以观察到这种变化,我正在使用VC++ 2013.
因此我的问题是,这里发生了什么?"指针对"迭代器构造函数如何比C-String构造函数慢得多?
#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;
}
Run Code Online (Sandbox Code Playgroud)
Iter: 100000
Elapsed (CHECKNULLSTR): 296 ms
Elapsed (Ptr it pair): 577 ms
Speed difference: 194.932432 %
Total length: 150000000
Run Code Online (Sandbox Code Playgroud)
确实有趣且有点令人惊讶.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);
Run Code Online (Sandbox Code Playgroud)
VC12决定不内联append呼叫(可以理解的是,身体非常大),而且正如你所能想象的那样,与炽热相比,所有这些都带来了相当大的开销memcpy.
一种解决方案是使用std::basic_string带有指针和计数的构造函数(Ben Voigt在他的评论中也提到过),如下所示:
std::wstring wstr(CHECKNULLSTR(bstr), ::SysStringLen(bstr));
Run Code Online (Sandbox Code Playgroud)
我刚刚对它进行了测试,它确实为Visual C++ 2013带来了预期的好处 - 它有时只需要第一个版本的一半时间,而在最坏的情况下约为75%(无论如何这些都是近似测量).
当迭代器实际指向与要构造的字符串相同的字符类型时,Visual C++ 2015 CTP6中的标准库实现为构造函数采用迭代器对的优化代码路径,从而产生与指针基本相同的代码. -count变种如上.因此,在这个版本中,你使用这两个构造函数变体中的哪一个并不重要 - 它们都比仅使用指针的版本更快.
| 归档时间: |
|
| 查看次数: |
443 次 |
| 最近记录: |