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 шт):

Автор решения: Zhihar

переписал код так, чтобы более-менее обмануть компилятор и он не начал почём зря оптимизировать (у меня например 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;
    //------------------------------------------------------------------------------------------------------
→ Ссылка
Автор решения: Pak Uula

Это особенность реализации в 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-х битная версия.

→ Ссылка