memcmp VS wmemcmp - сравнение скорости
Подскажите пожалуйста, почему wmemcmp работает раза в 2-5 медленнее memcmp. Вот код для сравнения:
#include <iostream>
#include <ctime>
using namespace std;
int main()
{
wchar_t my_wchar11[] = L"rstuw12345612345ab89abcefghijklmnopqrstuw123457q";
wchar_t my_wchar22[] = L"rstuw12345612345ab89abcefghijklmnopqrstuw123457q";
//----------------------------------------------
wchar_t* my_wchar_p11 = new wchar_t[wcslen(my_wchar11)+1];
wchar_t* my_wchar_p22 = new wchar_t[wcslen(my_wchar22)+1];
memcpy(my_wchar_p11, my_wchar11, sizeof(my_wchar11));
memcpy(my_wchar_p22, my_wchar22, sizeof(my_wchar22));
//----------------------------------------------
//----------------------------------------------------------------------------------------
size_t num = 99999999;
int clock1;
int clock2;
//------------------------------------------------------Сравнение через wmemcmp:-----------------------
clock1 = clock();
int status_wmemcmp = 0;
for (size_t i = 0; i < num; i++)
{
status_wmemcmp = wmemcmp(my_wchar_p11, my_wchar_p22, wcslen(my_wchar22));
}
clock2 = clock();
cout << "time_wmemcmp:" << clock2 - clock1 << endl;
cout << "status_wmemcmp:" << status_wmemcmp << endl;
//------------------------------------------------------------------------------------------------
cout << endl;
//------------------------------------------------------Сравнение через memcmp:----------------------
clock1 = clock();
int status_memcmp = 0;
for (size_t i = 0; i < num; i++)
{
status_memcmp = memcmp(my_wchar_p11, my_wchar_p22, sizeof(my_wchar22)-2);
}
clock2 = clock();
cout << "time_memcmp:" << clock2 - clock1 << endl;
cout << "status_memcmp:" << status_memcmp << endl;
//------------------------------------------------------------------------------------------------------
}
Результаты следующие: VS2017, release, x64:
wmemcmp: 2352 мс
memcmp: 853 мс
Причем на бОльших массивов разница еще больше.
Ответы (2 шт):
переписал код так, чтобы более-менее обмануть компилятор и он не начал почём зря оптимизировать (у меня например memcmp работает вообще 0ms в Release)
результаты:
x64:
time_wmemcmp:2101
status_wmemcmp:0
time_memcmp:913
status_memcmp:0
x86:
time_wmemcmp:2189
status_wmemcmp:0
time_memcmp:952
status_memcmp:0
что и должно быть, поскольку для wchar_t требуется обработать в 2 раза больше информации
мой вердикт - в приведённом автором поста коде компилятор для memcmp просто более хорошо соптимизировал код
код:
const wchar_t my_wchar11[] = L"rstuw12345612345ab89abcefghijklmnopqrstuw123457q";
const wchar_t my_wchar22[] = L"rstuw12345612345ab89abcefghijklmnopqrstuw123457q";
const char my_char11[] = "rstuw12345612345ab89abcefghijklmnopqrstuw123457q";
const char my_char22[] = "rstuw12345612345ab89abcefghijklmnopqrstuw123457q";
//----------------------------------------------------------------------------------------
const size_t num = 99999999;
int clock1;
int clock2;
//------------------------------------------------------Сравнение через wmemcmp:-----------------------
clock1 = clock();
int status_wmemcmp = 0;
const int wlen = wcslen(my_wchar22);
for (size_t i = 0; i < num; i++)
{
const int pos = i % wlen;
status_wmemcmp += wmemcmp(my_wchar11 + pos, my_wchar22 + pos, wlen - pos);
}
clock2 = clock();
cout << "time_wmemcmp:" << clock2 - clock1 << endl;
cout << "status_wmemcmp:" << status_wmemcmp << endl;
//------------------------------------------------------------------------------------------------
cout << endl;
//------------------------------------------------------Сравнение через memcmp:----------------------
clock1 = clock();
const int len = strlen(my_char22);
int status_memcmp = 0;
for (size_t i = 0; i < num; i++)
{
const int pos = i % len;
status_memcmp += memcmp(my_char11 + pos, my_char22 + pos, len - pos);
}
clock2 = clock();
cout << "time_memcmp:" << clock2 - clock1 << endl;
cout << "status_memcmp:" << status_memcmp << endl;
//------------------------------------------------------------------------------------------------------
Это особенность реализации в MSVCRT. memcmp в MSVCRT написана на ассемблере. Исходник у меня в системе лежит в файле c:/Program Files (x86)/Microsoft Visual Studio/2017/BuildTools/VC/Tools/MSVC/14.16.27023/crt/src/x64/memcmp.asm. Чистый ассемблер без единой Си-шной строчки.
wmemcmp в MSVCRT написана на Си. Реализация включена в заголовочный файл c:/Program Files (x86)/Windows Kits/10/Include/10.0.17763.0/ucrt/wchar.h
Реализовано без затей простым циклом:
__inline int __CRTDECL wmemcmp(
_In_reads_(_N) wchar_t const* _S1,
_In_reads_(_N) wchar_t const* _S2,
_In_ size_t _N
)
{
for (; 0 < _N; ++_S1, ++_S2, --_N)
if (*_S1 != *_S2)
return *_S1 < *_S2 ? -1 : 1;
return 0;
}
Для сравнения, в GLIBC несколько ассемблерных реализаций [w]memcmp, оптимизированных под разные расширения процессора, см. репозиторий glibc. Есть и чисто Си-шная реализация, в которой цикл развёрнут. Разворачивание цикла дало прирост в полтора раза по сравнению с простым циклом. Для сравнения я взял Си версию wmemcmp из исходников glibc
Получился вот такой main.cpp
Компиляция в MS VC 19.16.27044: cl.exe /O2 main.cpp
32-х битный компилятор:
time_wmemcmp:2389
status_wmemcmp:0
time_memcmp:674
status_memcmp:0
time_glibc_wmemcmp:1614
status_glibc_wmemcmp:0
64-х битный компилятор
time_wmemcmp:2210
status_wmemcmp:0
time_memcmp:1263
status_memcmp:0
time_glibc_wmemcmp:1789
status_glibc_wmemcmp:0
g++ 7.5.0 64-х битный, на той же самой машине, запускался под WSL: g++ -O2 main.cpp -o g_main.exe
time_wmemcmp:1062500
status_wmemcmp:0
time_memcmp:1000000
status_memcmp:0
time_glibc_wmemcmp:1703125
status_glibc_wmemcmp:0
Интересно, что в 64-х битном компиляторе memcmp в два раза медленнее, чем 32-х битная версия.