当区域设置

时间:2016-04-18 05:54:14

标签: c linux windows performance

我正在诊断跨平台(Windows和Linux)应用程序中的边缘情况,其中toupper在Windows上要慢得多。我假设这对于托勒也是一样的。

最初我在每个没有语言环境信息集的情况下使用简单的C程序测试了它,甚至包括头文件,并且性能差异非常小。测试是一个百万次迭代循环,将字符串的每个字符调用到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"); 

注意: 适用于Windows 10的Visual Studio 2015 运行Cent OS的G ++ for Linux

尝试过荷兰设置设置和相同的结果,在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++;
    }
}

对于Windows,将本地信息更改为:

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

您可以在完成的时间内看到分隔符的区域设置更改,完全停止与逗号。

编辑 - 分析数据 application function calls profiling 正如您在上面看到的,大部分时间都来自_toupper_l的子系统调用。 没有设置语言环境信息,toupper调用不会调用子_toupper_l,这使得它非常快。

1 个答案:

答案 0 :(得分:0)

与LANG = C vs. LANG相同(并且相当不错)的性能= Linux所使用的glibc实现所需的其他内容。

您的Linux结果很有意义。您的测试方法可能还可以。使用分析器查看您的microbenchmark在Windows功能中花费的时间。如果Windows实现确实成为问题,也许有一个Windows函数可以转换整个字符串,比如C ++ boost::to_upper_copy<std::string>(除非它更慢,见下文)。

另请注意,提升ASCII字符串可以非常有效地SIMD矢量化。我使用C SSE内在函数为单个向量in another answer编写了一个case-flip函数;它可以适应大写而不是翻盖。如果您花费大量时间来更新超过16个字节的字符串,并且您知道的是ASCII,那么这应该是一个巨大的加速。

实际上,Boost的to_upper_copy() appears to compile to extremely slow code, like 10x slower than 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。

The glibc source是:

#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;
}

来自x86-64 Ubuntu 15.10的/lib/x86_64-linux-gnu/libc.so.6的asm是:

## 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的间接层,但是在第一次调用后触发了懒惰符号查找,它只是你的代码和库中的11个insn之间的一条额外jmp指令。)