Windows C Runtime toupper медленнее, когда набор локалей

Я диагностирую кросс-кейс в кросс-платформенном (Windows и Linux) приложении, где toupper существенно медленнее в Windows. Я предполагаю, что это то же самое для tolower.

Изначально я тестировал это с помощью простой программы на каждом из них без информации о локали, или даже включая заголовочный файл, и было очень мало разницы в производительности. Тест был миллион циклов итерации, вызывающий каждый символ для строки функции toupper().

После включения файла заголовка и включения строки ниже он намного медленнее и вызывает множество функций локали библиотеки времени выполнения MS C. Это прекрасно, но удар производительности очень плох. В Linux это никак не влияет на производительность.

setlocale(LC_ALL, ""); // system default locale

Если я устанавливаю следующее, он выполняется так же быстро, как linux, но, похоже, пропускает все языковые функции.

setlocale(LC_ALL, NULL); // should be interpreted as the same as below?
OR
setlocale(LC_ALL, "C"); 

Примечание: Visual Studio 2015 для Windows 10 g++ для Linux работает Cent OS

Попробовали настройки параметров голландского языка и тот же результат, медленный на Windows, не разница в скорости на Linux.

Я делаю что-то неправильно или есть ошибка с настройками языкового стандарта в Windows или это другой способ, которым Linux не делает то, что должен? Я не отлаживал приложение linux, поскольку я не так хорошо знаком с Linux, поэтому не знаю точно, что он делает внутри. Что я должен проверить после сортировки?

Код ниже для тестирования (Linux):

// C++ is only used for timing.  The original program is in C.
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <chrono>
#include <locale.h>

using namespace std::chrono;

void strToUpper(char *strVal);

int main()
{

    typedef high_resolution_clock Clock;
    high_resolution_clock::time_point t1 = Clock::now();

    // set locale
    //setlocale(LC_ALL,"nl_NL");
    setlocale(LC_ALL,"en_US");

    // testing string
    char str[] = "the quick brown fox jumps over the lazy dog";

    for (int i = 0; i < 1000000; i++)
    {
        strToUpper(str);
    }

    high_resolution_clock::time_point t2 = Clock::now();
    duration<double> time_span = duration_cast<duration<double>>(t2 - t1);
    printf("chrono time %2.6f:\n",time_span.count());
}

void strToUpper(char *strVal)
{
    unsigned char *t;
    t = (unsigned char *)strVal;

    while (*t)
    {
        *t = toupper(*t);
        *t++;
    }
}

Для окон изменить локальную информацию на:

// set locale
//setlocale(LC_ALL,"nld_nld");
setlocale(LC_ALL, "english_us");

Вы можете увидеть изменение локали из разделителя в завершенное время, полная остановка против запятой.

EDIT - данные профилирования обработка функций функций приложения Как вы можете видеть, большую часть времени, проведенного в дочерних системных вызовах от _toupper_l. Без информации о языковой настройке вызов toupper НЕ вызывает дочернего _toupper_l, что делает его очень быстрым.

Ответ 1

Идентичная (и довольно хорошая) производительность при использовании LANG = C против LANG = ожидается что-либо еще для реализации glibc, используемой Linux.

Ваши результаты в Linux имеют смысл. Ваш метод тестирования, вероятно, одобрен. Используйте профилировщик, чтобы узнать, сколько времени ваш микрозапуск стоит внутри функций Windows. Если проблема с реализацией Windows оказывается проблемой, может быть, есть функция Windows, которая может конвертировать целые строки, например С++ boost::to_upper_copy<std::string> (если это еще медленнее, см. ниже).


Также обратите внимание, что восходящая строка ASCII может быть очень проста в векторе SIMD. Я написал функцию флип-флага для одного вектора в другом ответе, используя C SSE intrinsics; он может быть адаптирован к верхнему регистру вместо флипса. Это должно быть огромным ускорением, если вы потратите много времени на то, чтобы строки, длина которых больше 16 байтов, и что вы знаете, это ASCII.

На самом деле, Boost to_upper_copy(), похоже, компилируется в чрезвычайно медленный код, например, в 10 раз медленнее, чем toupper. См. Эту ссылку для моего векторизованного strtoupper(dst,src), который является только ASCII, но может быть расширен с помощью резервного копирования, когда обнаружены байты без ASCII src.


Как ваш текущий код обрабатывает UTF-8? Там не так много выигрыша в поддержке не-ASCII-локалей, если вы предполагаете, что все символы являются одним байтом. IIRC, Windows использует UTF-16 для большинства вещей, что является неудачным, потому что оказалось, что мир хочет более 2 ^ 16 кодовых точек. UTF-16 представляет собой кодировку Unicode с переменной длиной, например UTF-8, но без преимущества чтения ASCII. Фиксированная ширина имеет много преимуществ, но, к сожалению, вы не можете предположить, что даже с UTF-16. Java тоже допустил эту ошибку и застрял в UTF-16.


Источник glibc:

#define __ctype_toupper \
     ((int32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOUPPER) + 128)
int toupper (int c) {
    return c >= -128 && c < 256 ? __ctype_toupper[c] : c;
}

Asm из x86-64 Ubuntu 15.10 /lib/x86_64-linux-gnu/libc.so.6:

## disassembly from  objconv -fyasm -v2 /lib/x86_64-linux-gnu/libc.so.6 /dev/stdout 2>&1
toupper:
    lea     edx, [rdi+80H]                          ; 0002E300 _ 8D. 97, 00000080
    movsxd  rax, edi                                ; 0002E306 _ 48: 63. C7
    cmp     edx, 383                                ; 0002E309 _ 81. FA, 0000017F
    ja      ?_01766                                 ; 0002E30F _ 77, 19
    mov     rdx, qword [rel ?_37923]                ; 0002E311 _ 48: 8B. 15, 00395AA8(rel)
    sub     rax, -128                               ; 0002E318 _ 48: 83. E8, 80
    mov     rdx, qword [fs:rdx]                     ; 0002E31C _ 64 48: 8B. 12
    mov     rdx, qword [rdx]                        ; 0002E320 _ 48: 8B. 12
    mov     rdx, qword [rdx+48H]                    ; 0002E323 _ 48: 8B. 52, 48
    mov     eax, dword [rdx+rax*4]                  ; 0002E327 _ 8B. 04 82   ## the final table lookup, indexing an array of 4B ints
?_01766:
    rep ret                                         ; actual objconv output shows the prefix on a separate line

Итак, для начала требуется, чтобы аргумент arg не находился в диапазоне 0 - 0xFF (поэтому эта ветка должна быть предсказуема совершенно не принятой), в противном случае она найдет таблицу для текущей локали, которая включает в себя три вида разметки указателя: одна загрузка из глобальной и одна нить-локальная, и еще одно разыменование. Затем он фактически индексируется в таблицу с 256 входами.

Это вся библиотечная функция; метка toupper в разборке - это то, что вызывает ваш код. (Ну, через слой косвенности через PLT из-за динамической компоновки, но после того, как первый вызов вызывает ленивый поиск символов, это всего лишь одна дополнительная инструкция jmp между вашим кодом и теми 11 insns в библиотеке.)